Shift Down 的具体操作步骤
只能取出根节点处的元素,即第 1 个元素(索引为 1)。
步骤:将最后一个元素放到第1个元素的位置,这样做交换和移动的次数最少,并且保持了完全二叉树的性质,但是此时并不满足最大堆的性质。(想清楚为什么要这么做)
保持了完全二叉树的性质,但是此时 完全二叉树不满足最大堆的性质,我们就须要将第1 个元素逐层下移来进行判断。
具体的步骤:如果存在右孩子,就把右孩子和左孩子比较,比出最大的那个,再和自己比较,如果比自己大,就交换位置,这样的过程直到“自己比左右两个孩子都大”为止。
把当前元素和它的左右孩子,三个数进行比较,找出最大的,谁大跟谁换。
public int extractElement() {
int num = data[1];
assert this.count >= 0;
swap(data, 1, count);
this.count--;
shiftDown(1);
return num;
}
说明:最大堆的最后一个元素是 data[count]。
/**
* 只要有左右孩子,左右孩子只要比自己大,就交换
*
* @param h
*/
private void shiftDown(int h) {
while (2 * h <= count) { // 如果这个元素有左边的孩子
int k = 2 * h;
if (k + 1 <= count && data[k + 1] > data[k]) {// 如果有右边的孩子,大于左边的孩子,就好像左边的孩子不存在一样样
k = k + 1;
}
if (data[h] < data[k]) {
swap(data, h, k);
}
h *= 2;
}
}
下面是我在练习的时候的一种写法,虽然没有问题,但是增大了判断的次数。
图:练习时候的一种写法
说明:在完全二叉树中,如何表示有孩子?结论:有左孩子就够了。
提示:如果我们明白这个 Shift Down 的过程的话,代码其实可以很快地写出来。
一定要注意:这里的循环条件是 2*k <= count ,等于号不能漏掉,自己手画一个完全二叉树就清楚了。
优化的思路:
逐渐下移、上移的过程可以不用交换,借用插入排序优化的思路,多次赋值,一次交换。
是不是还可以用 for 循环来做这件事情?
下面我们进行功能测试:
@Test
public void test03() {
int capacity = 10;
MaxHeap maxHeap = new MaxHeap(capacity);
Random random = new Random();
int nextInt;
for (int i = 0; i < capacity; i++) {
// 得到一个 [0,99] 或者说 [0,100) 区间之内的随机数
nextInt = random.nextInt(100);
maxHeap.insert(nextInt);
}
System.out.println(Arrays.toString(maxHeap.getData()));
while (!maxHeap.isEmpty()) {
int extractMax = maxHeap.extractMax();
System.out.printf("%d ", extractMax);
}
}
写到这里,我们可以直接输出来检验一下,自己写出的最大堆数据结构是否符合最大堆的性质。因为每一次从最大堆取出的总是数组中最大的元素,所以可以将最大堆用于排序,使用排序的工具检验最大堆的正确性。