您的位置:首页 > 产品设计 > UI/UE

<java.util>PriorityQueue代码分析

2016-07-08 16:28 267 查看
java.uti.PriorityQueue代码分析

在leetcode.com上刷题的时候看到一个题,其中就应用到了最有队列这个概念,于是看了jdk1.8中源码的实现,感觉还是比较易懂的就拿出来分享一下。

PriorityQueue的实质就是一个平衡二叉堆.。来看一下jdk中的介绍:

Priority queue represented as a balanced binary heap: the two

* children of queue
are queue[2*n+1] and queue[2*(n+1)].

数组下标为n的元素的子节点为下标为2*n+1和下标为2*(n+1)的元素。

要点1:底层数据结构为一个默认大小为11的数组(DEFAULT_INITIAL_CAPACITY = 11;)

要点2:构造参数中通常传入一个Comparator对象,用来实现调用者自定义的排序策略,如果此对象为null,就根据元素自身的自然排序。

add/offer:数据结构一般关心增删改查排序查找扩容等问题。PriorityQueue新增API为add,直接调用了offer方法。

新增的策略如下:

1.当插入第一个元素时,直接插入:queue[0] = e;

2.插入第二个元素时,调用siftUp()方法来进行调整。

3.siftUp方法根据传入的Comparator对象是否为空,分别调用

siftUpUsingComparator(k,x)方法和siftUpComparable(k, x)方法,两者内容基本相同。

4.以siftUpUsingComparator(k,x)方法为例,传入参数为将要插入的数组下标k,将要插入的数据x。

源码如下

private void siftUpUsingComparator(int k, E x) {
while (k > 0) {
int parent = (k - 1) >>> 1;
Object e = queue[parent];
if (comparator.compare(x, (E) e) >= 0)
break;
queue[k] = e;
k = parent;
}
queue[k] = x;
}


因为PriorityQueue代表着一个有序的二叉堆,则新插入的数据需要和自己的直系父节点(引用二叉树概念,指自身和根节点的连线上的所有节点)进行比较。

int parent = (k - 1) >>> 1;//首先找到自身节点的父节点,在搜寻父节点索引的时候,使用移位操作。因为2n+1/2 = n, 2(n+1)/2 = n,所以不管你是两个子节点中的哪一个,你都能准确找到自身父节点的下标。

if (comparator.compare(x, (E) e) >= 0)

break;

当准备插入元素和父元素之间已经是符合比较器的规则了,那就跳出循环,直接插入即可。

否则的话,就将准备插入的元素和父元素之间进行调换,我们将新晋的父元素成为e;

调换之后,除非e已经成为quene[0]即根元素,否则要根据规则一直比调换下去。源码如下

private E removeAt(int i) {
// assert i >= 0 && i < size;
modCount++;
int s = --size;
if (s == i) // removed last element
queue[i] = null;
else {
E moved = (E) queue[s];
queue[s] = null;
siftDown(i, moved);
if (queue[i] == moved) {
siftUp(i, moved);
if (queue[i] != moved)
return moved;
}
}
return null;
}
如果删除的是最后一个元素,直接将最后一个元素置为null即可。
如果不是最后一个元素,策略就是将最后一个元素和将要删除的元素进行对调后,调用方法siftDown(i,moved) 。i为将要删除元素在数组中的索引,moved为最后一个元素。
同理siftDown方法根据传入比较器对象是否为null调用不同方法。
siftDownUsingComparator源码如下
private void siftDownUsingComparator(int k, E x) {
int half = size >>> 1;
while (k < half) {
int child = (k << 1) + 1;
Object c = queue[child];
int right = child + 1;
if (right < size &&
comparator.compare((E) c, (E) queue[right]) > 0)
c = queue[child = right];
if (comparator.compare(x, (E) c) <= 0)
break;
queue[k] = c;
k = child;
}
queue[k] = x;
}


1.如果要删除的节点和最后一个节点是兄弟节点,那么和最后一个节点交换后值为null。

2.将要删除的节点和最后一个节点对调后,执行下调方法,如果执行下调之后,该元素位置不变,则说明该元素是该子树中的最小元素,需要执行上调方法,如果上调之后位置改变了,则将i位置上的元素返回。

3.在交换位置后,要么执行上调方法,要么执行下调方法。

peek

public E peek() {
return (size == 0) ? null : (E) queue[0];
}


始终将头元素给返回出去。

PriorityQueue总结:1.新增和删除的算法复杂度都为o(logn).不管是新增还是删除,都需要在一个类似于二叉树的数据结构中找到自己的位置或删除自己的位置。

2.因为新增时,只要保持大于父节点,小于自身的子节点这样子给特性,并没有比较与相邻兄弟节点的大小关系(与兄弟节点的关系取决于进入队列的先后顺序而不是大小顺序),所以整个底层数据结构数组并不是有序的,只能过保持头元素是有序的。

所以当输出这个队列时,每次输出头元素后,将重新调整位置,保证输入头元素是该堆中的最大或者最小特性。

本文中没有画出示意图,可以参考:http://www.2cto.com/kf/201603/496013.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: