二叉堆
来自算法(第四版)优先队列堆实现的笔记,大部分图片皆来源于此
当一棵二叉树的每个结点都大于等于它的两个子结点时,它被称为堆有序。 根结点是堆有序的二叉树中的最大结点。
如图所示:
在一个堆中,位置 k 的结点的父结点的位置为[k/2],而它的两个子结点的位置则分别为 2k 和 2 k+1。
从 a[k] 向上一层,就令 k 等于 k/2 ,向下一层则令 k 等于 2k 或 2k+1
堆的操作会首先进行一些简单的改动,打破堆的状态,然后再遍历堆并按照要求将堆的状态恢复,叫做堆的有序化
有序化的两种情况:
- 当某个结点的优先级上升(或是在堆底加入一个新的元素)时,我们需要由下至上恢复堆的顺序
- 当某个结点的优先级下降(例如,将根节点或父节点替换为一个较小的元素)时,我们需要由上至下恢复堆的顺序
由下至上的堆有序化(上浮)
因为某个结点变得比它的父结点更大而被打破,那么我们就需要通过交换它和它的父结点来修复堆
进行交换后,这个结点比它的两个子节点都大,但这个结点仍然可能比它现在的父节点更大,就继续重复的操作,直到恢复秩序。
因为k结点的父节点位置为k/2,如果k结点大于它的父节点那么堆的有序状态才会被打破,因此只要该结点不大于它的父节点,堆的有序状态就恢复了
代码实现
private void swim(int k) {
while (k > 1 && less(k / 2, k)) {
// 当k结点大于父节点k/2时,堆的有序状态被打破,则交换他们之间的位置
exch(k / 2, k);
k = k / 2;
}
}
图示
由上至下的堆有序化(下沉)
因为某个结点变得比它的两个子节点或是其中之一更小而被打破,那么我们就需要通过将它和它的两个子节点中较大者交换来恢复堆,直到该结点的子节点都比它更小或者到达了堆底。
代码实现
private void sink(int k) {
while (2 * k <= N) {
// 获取该结点的子节点之一
int j = 2 * k;
if (j < N && less(j, j + 1)) {
// 获取子节点中较大者
j++;
}
if (!less(k, j)) {
// 如果子节点已经比结点更小,那么堆已经有序化,无需进行下面的交换操作
break;
}
// 交换结点与子节点较大者的位置
exch(k, j);
k = j;
}
}
图示
实现优先队列(基于最大堆)
API
MaxPQ<Key extends Comparable<Key>>
| 返回 | 方法名 | 描述 |
|---|---|---|
| void | insert(Key v) | 想优先队列插入一个元素 |
| Key | max() | 返回当前最大的元素(根节点) |
| Key | delMax() | 删除并返回最大元素 |
| boolean | isEmpty() | 返回队列是否为空 |
| int | size() | 返回队列当前元素个数 |
| MaxPQ() | 创建一个优先队列 |
时间复杂度
| 方法名 | 时间复杂度 |
|---|---|
| insert(Key v) | logN |
| delMax() | logN |
插入元素
将新元素添加到数组尾部,增加堆大小并让这个新元素上浮到合适的位置
图示
代码实现
public void insert(Key v) {
pq[++N] = v;
swim(N);
}
删除最大元素
删除数组顶端最大的元素,并与数组的最后一个元素交换,减小堆大小并进行下沉操作
图示
代码实现
public Key delMax() {
// 获取根节点的最大元素
Key max = pq[1];
// 与最后一个结点交换
exch(1, N);
// 防止对象游离
pq[N--] = null;
// 下沉
sink(1);
return max;
}
完整代码实现
public class MaxPQ<Key extends Comparable<Key>> {
private static final int DEFAULT_CAPACITY = 10;
// pq[1..N]
private Key[] pq;
private int N;
/**
* 创建一个优先队列
*/
public MaxPQ() {
pq = (Key[]) new Comparable[DEFAULT_CAPACITY + 1];
}
/**
* @return 根节点
*/
public Key max() {
return pq[1];
}
/**
* 删除根节点的元素,并与数组的最后一个元素交换,减小堆大小并进行下沉操作
*
* @return 删除并返回最大元素
*/
public Key delMax() {
// 获取根节点
Key max = pq[1];
// 与最后一个结点交换
exch(1, N);
// 防止对象游离
pq[N--] = null;
// 下沉
sink(1);
return max;
}
/**
* @return 返回队列是否为空
*/
public boolean isEmpty() {
return N == 0;
}
/**
* @return 返回优先队列中的元素个数
*/
public int size() {
return N;
}
private boolean less(int i, int j) {
return pq[i].compareTo(pq[j]) < 0;
}
private void exch(int i, int j) {
Key t = pq[i];
pq[i] = pq[j];
pq[j] = t;
}
/**
* 将新元素添加到数组尾部,增加堆大小并让这个新元素上浮到合适的位置
*
* @param v
*/
public void insert(Key v) {
pq[++N] = v;
swim(N);
}
/**
* 由下至上的堆有序化(上浮)
*
* @param k 结点
*/
private void swim(int k) {
while (k > 1 && less(k / 2, k)) {
exch(k / 2, k);
k /= 2;
}
}
/**
* 由下至上的堆有序化(下沉)
*
* @param k 结点
*/
private void sink(int k) {
while (2 * k <= N) {
// 获取k结点的子节点之一
int j = 2 * k;
if (j < N && less(j, j + 1)) {
// 获取子节点中较大者
j++;
}
if (!less(k, j)) {
// 如果子节点已经比结点更小,那么堆已经有序化,无需进行下面的交换操作
break;
}
// 交换结点与子节点较大者的位置
exch(k, j);
k = j;
}
}
}