优先级队列的模拟实现
JDK1.8中的PriorityQueue底层使用了堆的数据结构,而堆实际就是在完全二叉树的基础之上进行了一些元素的调整。
注意:
new出的空间在堆上,优先级队列底层结构也是堆,但两个堆不是同一个概念
new 堆:指的是一块具有特殊作用的内存空间
优先级队列底层的堆:指的是一种数据结构
堆
-
堆是一颗完全二叉树
-
每个节点都比他的两个孩子小,属于小根堆
-
每个节点都比他的孩子节点大,属于大根堆
-
由于堆是一颗完全二叉树,因此可以层序的规则采用顺序的方式来高效存储(在数组中存储)
-
在堆中一般情况下将根节点叫堆顶元素
小堆:
1. 堆顶元素一定是最小的
2. 每个节点都比其孩子小
3. 每条路径都是升序
注意:普通二叉树是不可以直接用数据按照层序的方式来进行存储
如果一定要按照顺序的方式来进行存储的话,那么要将数中空节点的位置保留出来,否则还原的时候会出错
但是这样存储效率太低了,所以不是完全二叉树不建议用顺序方式来存储
堆的创建
1. 堆的向下调整
注意不能先标记右孩子,而应该先标记左孩子(因为右孩子可能没有)
public static void shiftDown(int[] array, int parent) {
// child先标记parent的左孩子,因为parent可能右左没有右
int child = 2 * parent + 1;
while (child < array.length) {
// 如果右孩子存在,找到左右孩子中较小的孩子,用child进行标记
if(child+1 < array.length&& array[child+1] < array[child]){
child += 1;
}
//
if (array[parent] > array[child]) {
// 将双亲与较小的孩子交换
int temp = array[parent];
array[parent] = array[child];
array[child] = temp;
// parent中大的元素往下移动,可能会造成子树不满足堆的性质,因此需要继续向下调整
parent = child;
child = parent * 2 + 1;
}else{
break;
}
}
}
堆的创建
public static void createHeap(int[] array) {
// 找倒数第一个非叶子节点,从该节点位置开始往前一直到根节点,遇到一个节点,应用向下调整
int root = ((array.length-2)>>1);
for (; root >= 0; root--) {
shiftDown(array, root);
}
}
堆的插入和删除
1. 堆的插入
堆的插入总共需要两个步骤:
- 先将元素放入到底层空间中(注意:空间不够时需要扩容)
- 将最后新插入的节点向上调整,直到满足堆的性质
// 堆的插入(按小堆)
public void shiftUp(int[] array,int child) {
// 找到child的双亲
int parent = (child - 1) / 2;
while (child > 0) {
// 如果双亲比孩子大,parent满足堆的性质,调整结束
if (array[parent] > array[child]) {
break;
}
else{
// 将双亲与孩子节点进行交换
int t = array[parent];
array[parent] = array[child];
array[child] = t;
// 小的元素向下移动,可能到值子树不满足堆的性质,因此需要继续向上调增
child = parent;
parent = (child - 1) / 1;
}
}
}
2. 堆的删除
堆删除一定是删除的堆顶元素
- 将堆顶元素对堆中最后一个元素交换
- 将堆中有效数据个数减少一个
- 对堆顶元素进行向下调整