6.1-1:
在高度为h的堆中, 元素最多为2^h, 元素最少为2^(h+1) - 1;
理由如下:
最大堆可以理解为一个二叉树, 高度为h的2叉树最多有 2^(h+1) - 1个,最少的情况是在最后一层只有一个元素,则此时的元素个数为 2^h 个
6.1-2:
因为在高位h的堆中, 元素的个数在 2^h 个到 2^(h+1)-1 个之间, 所以如果元素的个数为n, 则高度一定为 lgn 向下取整,此时对数的底数是2
6.1-3:
因为在最大堆中, 所有结点都要满足, A[parent(i)] >= A[i], 所以对于最大堆中的任一子树, 其根结点的值应该大于该子数的左右孩子的值
6.1-4:
假设最大堆的所有元素不同,则最小的元素应该在最后一层的最后一个元素的位置上
6.1-5:
;已排好序的数组就是一个最小堆或者最大堆
6.1-6:
是一个最大堆
6.1-7:
在长度为n的数组中, 因为要对于每一个有孩子的结点都要满足,i*2 <= n, 所以对于倒数第二层的最后一个有子结点, 满足, i*2 <= n, 因为只有这样,才能满足其孩子在数组内, 所以对于其之后的数组中的元素, 都应该在叶子结点上, 并且其父亲都是 n/2+1 到 n
6.2-1:
当i = 3时, A[3]违背了最大堆的性质, 因为其值不大于其孩子, 通过交换A[3] 和 A[6] 的值, 结点3恢复了最大堆的性质, 但又导致结点6违反了最大堆的性质, 递归调用MAX-HEAPIFY(A, 6), 此时i = 6, 通过交换 A[6]和A[13] 的值, 使得 A[6] 恢复了最大堆的性质, 递归调用MAX-HEAPIFY(A, 13), 此时 i = 13, 此时不在有新的数据交换
6.2-2:
伪代码如下:
MIN-HEAPIFY(A, i): //维护下标为i的结点作为根的子树 的最小堆的性质
l = LEFT(i); //记录左孩子的下标
r = RIGHT(i); //记录右孩子的下标
if l <= A.HEAP-SIZE and A[l] >= A[i]:
minimum = i;
else:
minimum = l;
if r <= A.HEAP-SIZE and A[r] <= A[minimum]:
minimum = r;
if minimum != i:
exchange A[i] and A[minimum];
MIN-HEAPIFY(A, minimum);
6.2-3:
当元素A[i] 的值比孩子的值都大时, 没有任何效果,但是此时不能保证 A[i] 的左右孩子都维持堆的特性
6.2-4:
当i > A.heap-size/2 时, 表明此时 A[i] 已经是叶子结点了, 所以肯定都维持了堆的特性
6.2-5:
迭代的算法如下:
MAX-HEAPIFY-CIRCLE(A, i):
while(i < A.heap-size/2):
//当i为叶子结点时退出
l = A.left(i); //记录左孩子的下标
r = A.right(i); //记录右孩子的下标
if r <= A.heap-size and A[r] >= A[i]:
largest = r; //记录最大的值的下标
else:
largest = i;
if l <= A.heap-size and A[l] >= A[largest]:
largest = l;
if i == largest:
break;
else:
exchange A[i] and A[largest];
6.3-1:
循环开始时, i = 9/2向下取整得 i = 4, 执行MAX-HEAPIFY(A,i) 之后, A[i]为根得树维持了堆得性质, 然后i减一; i = 3 时, 通过将 A[3] 和 A[6]的 值互换之后, A[3]为根的树也维持了堆的性质, i减一, 此时 i = 2, 通过执行MAX-HEAPIFY(A, i) 之后, 此时 A[2] 也维持了堆的性质, i减一 , 此时i = 1, 通过执行MAX-HEAPIFY(A, i) 之后, 已A[1] 为根的树也维持了堆的性质, i减一, 此时 i = 0, 退出循环, 而此时, 整个树已经维持了堆的性质
6.3-2:
如果以1开始递增, 则在执行依次 MAX-HEAPIFY 之后, 以后每次执行完 MAX-HEAPIFY 之后不能保证整棵树保持堆的性质
6.4-1:
首先通过 BUILD-MAX-HEAP方法构建一个最大堆,然后每次将堆顶的数和最后一个堆中的最后一个数进行交换,交换之后堆的大小减一,交换之后可能导致当前堆不满足最大堆的性质, 因此每次都还需要调用 MAX-HEAPIFY 方法来保证每次交换之后堆都能保持是最大堆
6.4-2:
初始化: 当第一次循环开始时,即 i = A.length, 此时子数组 A[1, i] 就是原来的数组, 因为在第一句时已经将原来的数组设置成最大堆, 而因为剩余的元素是0个, 所以没有最大的元素在其中
保持: 因为第i-1 次循环结束之后, 都由 MAX-HEAPIFY 保证了当前的堆是最大堆, 为第i次循环奠定了基础, 同时,每次都是和堆顶的元素交换, 而堆顶的元素是当前堆的最大元素, 所以每次交换之后, 子数组 A[i+1, n] 包含了数组中已经排序的i-1个元素
终止:过程终止时, i = 1, 此时 子数组 A[1, 1] 只有一个元素, 所以此时 A[1, 1] 也是一个最大堆, 并且子数组 A[2, A.length] 中保存的都是已经排序的最大值,而此时堆顶元素是原数组中的最小元素, 所以最终保证了结论的正确性
6.5-3:
HEAP-MINIMUM 伪代码如下:
HEAP-MINIMUM (A):
return A[1]; //对于最小堆,返回堆顶元素就是最小元素
HEAP-EXTRACT0-MIN 伪代码如下:
HEAP-EXTRACT-MIN (A):
if A.heap-size < 1:
error "堆下溢"
min = A[1];
A[1] = A[A.heap-size];
A.heap-size = A.heap-size - 1;
MIN-HEAPIFY (A, 1);
return min;
HEAP-DECREASE-KEY 伪代码如下:
HEAP-DECREASE-KEY (A, i, key): //将结点i的key值下降到key的值并调整至正确位置
if key > A[i]:
error "新的key值大于当前key值"
A[i] = key;
while i > 1 and A[parent(i)] > A[i]:
exchange A[parent(i)] and A[i];
i = parent(i);
MIN-HEAP-INSERT 伪代码如下:
MIN-HEAP-INSERT(A, key):
A.heap-size = A.heap-size + 1;
A[A.heap-size] = 正无穷;
HEAP-DECREASE-KEY (A, A.heap-size, key);
6.5-4:
因为HEAP-INCREASE-KEY 要求整个堆是最大堆, 如果在 A.heap-size 处插入负无穷,整个堆仍然是最大堆, 这样才能用 HEAP-INCREASE-KEY
6.5-5:
假定在调用 HEAP-INCREASE-KEY 方法时, 堆 A[1, A.heap-size] 是一个最大堆
初始化:如果新插入的key小于当前的A[i] 那么将无法插入key值, 此时的数组A[i, A.heap-size] 没有变化,仍然是一个最大堆, 反之, 新插入的key值大于当前的 A[i] , 那么除了A[i] 和 A[parent(i)] 之外, 其他的结点仍然满足最大堆, 如果此时A[i] > A[parent(i)] 那么, 此时不满足最大堆的性质, 但是因为之前A[parent(i)] > A[i] ,A[i] 大于A[i] 的子结点, 所以交换之后 A[i] 满足了最大堆的性质
保持:如果当前的A[i] 满足了最大堆的性质, 那么让i = parent(i) , 此时对于现在的 A[i] 仍然需要比较父结点的值, 同前面, 此交换 A[i] 和 A[parent(i)] 之后, 此时的i满足了最大堆的性质, 执行 i = parent(i) 之后为下次循环奠定了基础
结束:当结束时, 要么A[parent(i)] > A[i] 此时满足了最大堆的性质, 要么此时 i = 1, A[i] 为根节点,为整个堆中最大的元素, 此时整个堆仍然满足最大堆的性质
6.5-6:
while (parent(i)>=1 and key > A[parent(i)]):
A[i] = A[parent(i)];
i = parent(i);
A[i] = key;
6.5-7:
可以维护一个counter, 初始化为一个normal值, 然后每次执行依次 MAX-HEAP-INSERT 之前就让counter-1 作为key值给新插入的元素来实现一个队列, 反之, 在调用 MAX-HEAP-INSERTION 之前让 counter+1 作为新的key值给新插入的元素, 来实现栈的操作
6.5-8:
伪代码如下:
MAX-HEAP-DELETE(A, i):
if i < 1:
error "堆下溢"
exchange A[i] and A[A.heap-size]; //让A[i] 和当前堆的最后一个元素交换
A.heap-size = A.heap-size - 1; //将最后一个元素移除堆
MAX-HEAPIFY(A, i); //维持新的A[i] 的最大堆的性质, 将A[i]移动到适当的位置
6.5-9:
首先对于k个有序链表, 取出这k个有序链表中的最小的元素, 构建成最小堆, 此时时间复杂度是 O(k), 对于链表中的每一个结点, 需要维护一个当前所在列表的引用
然后取出最小堆的堆顶元素, 作为整个结果的最小元素, 然后取出刚才在堆顶的那个链表的下一个元素, 调整堆为最小堆, 然后再次取出堆顶元素, 作为次小的元素, 如果有一个队列空了, 就删除这个结点。因为每次堆的大小都是k, 所以每次调整为最小堆的时间都是 lgk , 所以总共有n结点, 每次调整堆的时间为 O(k), 所以总共的时间为 O(nlgk)