队列是一种先进先出的数据结构(First in first out)。但是假如说某些元素优先级更高时,我们就没有办法把它优先拿出队列。所以就有优先级队列。
在JDK1.8中的PriorityQueue底层使用了堆的数据结构,而堆实际就是在完全二叉树的基础上进行了调整。
什么是堆?
堆是一种数据结构。堆通常是一个完全二叉树的数组对象。
-
堆中某个节点的值总是不大于或不小于其父节点的值;
-
堆总是一棵完全二叉树。
小根堆:
堆是一种非线性数据结构,36后面记录的4是9的左孩子。
下标为i的节点,左孩子下标:2 * i + 1, 有孩子下标: 2 * i + 2, 父亲节点下标:( i - 1)/ 2
如果i为0,则i表示的节点为根节点,否则i节点的双亲节点为 (i - 1)/2
如果2 * i + 1 小于节点个数,则节点i的左孩子下标为2 * i + 1,否则没有左孩子
如果2 * i + 2 小于节点个数,则节点i的右孩子下标为2 * i + 2,否则没有右孩子
如果创建堆?
如图,假如我们要创建一个大根堆。大根堆的特性是每个节点的值都大于它的子节点的值。那根节点的左右也得是一个大根堆。图上是一颗树,
这两个也是一棵树,所以我们就可以从56这个节点开始调整,让它成为一个大根堆,再是6为根的这颗树,再到23. 而56的下标是数组的长度减去1出意2.这是固定的, 每个完全二叉树最后的那棵小树的下标都是(array.lenth - 1) / 2.
public class PriorityQueue {
public int[] elem;
public int usedSize;
public void createHeap(int[] array) {
elem = Arrays.copyOf(array, array.length);
usedSize = array.length;
for(int parent = (array.length-1-1)/2; parent >= 0; parent--){
shiftDown(parent, usedSize);
}
}
知道从哪个节点开始调整之后,调用shiftDown方法.
/**
*
* @param root 是每棵子树的根节点的下标
* @param len 是每棵子树调整结束的结束条件
* 向下调整的时间复杂度:O(logn)
*/
private void shiftDown(int root,int len) {
//先取得左孩子得下标
int child = root * 2 + 1;
while(child < len){
//如果有右孩子,取左右孩子最大值得下标
if(child + 1 < len && elem[child] < elem[child+1]){
child++;
}
//比较孩子和父亲,如果孩子比父亲大,交换,并让root和child向下移动继续比较
if(elem[root] < elem[child]){
swap(root, child);
root = child;
child = root * 2 + 1;
}else{
break;
}
}
}
建堆得时间复杂度:
节点数 移动层数
第一层: 2^0 h-1
第二层: 2^1 h-2
第三层: 2^2 h-3
第h-1层:2^(h-2) 1
为什么只到h-1层,因为最后一层无法向下移动。
T(n) = 2^0 * (h-1) + 2^1 * (h -2) + 2^3 * (h-3) +... + 2^(h-2) * 1
2*T(n) = 2^1*(h-1)+ 2^2 * (h -2) + 2^4 * (h-3) +... + 2^(h-1) * 1
错位相减用第二行减去第一行
推到完之后T(n) = 2^h - 1 - h
n = 2^h - 1 h = log(n-1)
T(n) = n - log(n-1)
log(n-1) 可以看成一个常量,对结果的影响不大。当n越大,log(n-1)的值增长会越来越慢
所以用向下调整的方式整理成堆的时间复杂度为O(n)
如何插入元素
新插入得元素放在尾部,只需要依次和父节点进行比较,大就交换,小就不需要动了。
/**
* 入队:仍然要保持是大根堆
* @param val
*/
public void push(int val) {
if(isFull()){
elem = Arrays.copyOf(elem, elem.length*2);
}
elem[usedSize] = val;
usedSize++;
shiftUp(usedSize-1);
}
private void shiftUp(int child) {
int parent = (child-1)/2;
/**
* 不能用parent进行判断,当child走到1或2时,parent为1,在进行一次调整,child = 0; parent = 0 * 2 + 1出现错误
*/
while(child > 0){
if(elem[child] > elem[parent]){
swap(child, parent);
child = parent;
parent = (child-1)/2;
}else{
break;
}
}
}
移除堆顶元素
将堆顶元素和最后一个元素互换,再用向下调整方法调整堆顶元素即可
/**
* 出队【删除】:每次删除的都是优先级高的元素
* 仍然要保持是大根堆
*/
public void pollHeap() {
swap(0, usedSize-1);
usedSize--;
shiftDown(0, usedSize);
}