用一道算法题来说明堆的使用场景
leetecode-215
堆的介绍
小根堆:父节点的值小于或等于子节点的值
大根堆:父节点的值大于或等于子节点的值
堆的存储
这个是特别值得一说的,因为跟后面的实现相关。一般都是用数组来存储的,Java中的优先队列就是用Object[]数组存储;尽管堆是一个完全二叉树,但一般不用树结构来存储,正因为是完全二叉树,有非常多特殊的性质,正好可以用数组来存储。
这里最关键的性质是:
1)i结点的父结点下标就为(i–1)/2;
2)i节点左右子结点下标分别为2 * i + 1和2 * i + 2
堆的操作
插入:插入在堆尾,然后从下往上进行调整;调整一遍即可变有序,时间复杂度为o(logn)
private static void siftUp(int[] heap, int i) {
if (i <= 0) return;
// 父节点
int parent = (i - 1) / 2;
if (heap[parent] > heap[i]) {
int t = heap[i];
heap[i] = heap[parent];
heap[parent] = t;
siftUp(heap, parent); // 递归往上
}
}
删除:一般是弹出堆顶元素,然后用末尾元素替换堆顶元素,然后进行向下调整。时间复杂度也为o(logn)
private static void siftDown(int[] heap, int i) {
// 写一个递归的方式
int left = i * 2 + 1;
int right = i * 2 + 2;
int minIndex = getMinIndex(heap, i, left, right);
if (minIndex != i) {
int tem = heap[i];
heap[i] = heap[minIndex];
heap[minIndex] = tem;
// 递归调整子节点
siftDown(heap, minIndex);
}
}
private static int getMinIndex(int[] heap, int i, int left, int right) {
int minIndex = i;
if (left < size && heap[left] < heap[i]) {
minIndex = left;
}
if (right < size && heap[right] < heap[minIndex]) {
minIndex = right;
}
return minIndex;
}
堆的实现
调整堆/构造堆
从i = n/2 - 1节点开始调整,这之后的都是叶节点
private static void heapIfy(int[] heap) {
// 最后一个非叶子节点开始调整
int i = size / 2 - 1;
for (; i >= 0; i--) {
siftDown(heap, i);
}
}
总结
堆这种结构看似很吓人,感觉很难得样子。但真正理解了会发现很好实现得,它本质上就只有两种操作:
1)向下调整
2)向上调整
另外,这两种操作还有非递归实现方式,有兴趣得可以思考并实现一下