堆的概念
堆是逻辑结构为二叉树存储结构为数组数组的一种数据结构,为什么这么说呢?因为我们就是把堆想象成一棵有特殊功能的二叉树,然后把它按照层序遍历的顺序放到数组中。堆有大堆小堆之分,大堆是指根节点为堆中最大值的堆,小堆是指根节点为堆中最小值的堆。
- 堆中某个节点的值总是小于等于或大于等于其父亲节点的值,前者称为就是大堆,后者为小堆。
- 堆总是一棵完全二叉树。
小堆:
大堆:
可以看到小堆的父亲节点都小于孩子节点,进而也就有了小堆根节点是全堆中最小的值。
同样的大堆的父亲节点都大于孩子节点,进而有了大堆的根节点是全堆中最大的值。
堆的存储方式
因为我们要把堆按照层寻遍历的顺序存在数组中,那么在数组这么一个线性结构中怎么找一个节点的孩子节点,怎么找到一个节点的父亲节点呢?
答案是——根据下标。我们先在一棵二叉树中找以下节点之间的规律:
我们把上面这棵树按照层序遍历顺序存在数组中的下标标记了,我把节点的规律告诉大家,大家可以根据对照理解:
设一个节点的下标为 X, 那么它的左孩子下标为 2X + 1,右孩子下标为2X + 2,父亲节点为 (X - 1) / 2 。
这里解释一下,不用疑惑计算父亲节点的当前节点是它父亲节点左孩子还是右孩子,如果是左孩子的话根据上面左孩子计算父亲节点的公式 2X + 1可以知道左孩子坐标减一除以二刚好是父亲节点下标,如果是右孩子的话减一除二会自动向下取整,算出的下标也刚好是父亲节点的下标。
堆的相关操作
堆的向下调整。
对于集合{27, 15, 19, 18, 28, 34, 65, 49, 25, 37}中的数据,将它建造成堆是这样的:
可以发现这个堆除了根节点以外都满足小堆的性质,那么这种情况我们应该怎么处理?关于堆的第一个操作——向下调整。
//index为需要调整的节点下标
public static void heapifySmallHeap(long[] array, int size, int index){
while (true){
//需要调整节点的左孩子下标
int leftIndex = index * 2 + 1;
//如果不存在左孩子那么
// 因为堆是完全二叉树就一定不存在右孩子
//如果左孩子下标大于等于堆的总结节点数
// 就return
if (leftIndex >= size){
return;
}
//存在左孩子,现在看右孩子
int rightIndex = leftIndex + 1;
//先令左右孩子中值小的节点下标等于
//minIndex,让该值等于smallVal
int minIndex = leftIndex;
long smallVal = array[minIndex];
//先判断右孩子是否存在
if (rightIndex < size){
//右孩子存在的话让右孩子的值和smallVal的值比较
// 从而让small存左右孩子中最小的值
// 让minIndex为最小值节点下标
if (array[rightIndex] < smallVal){
minIndex = rightIndex;
smallVal = array[rightIndex];
}
}
//判断要调整父亲节点和smallVal的大小
if (array[index] <= smallVal){
return;
}
//交换父亲节点和smallVal节点
array[minIndex] = array[index];
array[index] = smallVal;
index = minIndex;
}
}
按照向下调整代码调整一下例子中的堆:
最终在d中堆被调整完成。
这里需要注意的是:在调整以index为根的二叉树时,必须要满足parent的左子树和右子树已经是堆了才可以向下调整。
时间复杂度分析:最坏的情况即上图的情况