1.最大堆
父节点的值一定 ≥ \ge ≥其子节点,且堆的根节点是最大的。
2.维护最大堆
约定:
1.用数组A来存储堆(从根节点开始,从上往下,从左往右依次为1,2,3…顺序存储)
2.参数i表示调整堆上该节点,以保持堆的性质(此处的节点值可能小于其儿子节点,如果其存在儿子节点的话)
伪代码:
MAX-HEAPIFY(A, i)
lSon = left-son(i)
rSon = right-son(i)
// 判断左儿子节点
if lSon<A.length and A[i]<A[lSon]
largest = lSon
else
largest = i
// 判断右儿子节点
if rSon<A.length and A[largest]<A{rSon]
largest = rSon
if largest!=i //则需要进行调整
exchange(A[i], A[largest])
MAX-HEAPIFY(A, largest) // 调整新子堆顶
算法分析:
最坏情况下高为H的堆调整所耗时间复杂度为O(H),或写为O(lgn)
对完全二叉树来说,树高度与树节点数的关系: H = l o g 2 n ( 向 下 取 整 ) H = log_{2}^n(向下取整) H=log2n(向下取整)
3.建立最大堆
对于以数组形式存储的树结构,可以借助上面所写的堆维护算法很方便的使之变成一个最大堆结构。
伪代码:
BUILD-MAX-HEAP(A)
n = A.length
for i=floor(n/2) downto 1
MAX-HEAPIFY(A, i)
算法分析:
- 利用的原理是:若数组A长度为n,则从下标floor(n/2)+1到n的元素在树上都表现为叶子节点,从最后一个父节点开始构建堆,一直循环到根节点处即可完成堆的构建。
- 此过程一定要从叶子节点向根节点发起,否则可能会有根节点及其叶子节点符合堆定义,但下面的部分不符合的情况,这种情况前面写过的堆维护算法并不能处理,所以此过程需要自下而上地进行。
- 时间复杂度为O(n)
4.堆排序算法
在上面三步的基础上,我们可以很容易的实现堆排序。
步骤:
- 对数组先调用构建堆函数,构建一个初始的最大堆
- 将数组首元素与当前尾元素交换(此时数组规模减一)
- 对规模减小后的数组第一个元素调用堆维护函数MAX-HEAPIFY(A,1)(因为堆已经建立起来了,我们将收尾元素交换只是使根节点不满足堆的定义,而根的左右儿子节点仍然是最大堆,所以此处我们只需要自上而下调用堆维护函数即可,不需要自下而上重新建立堆)
- 重复此过程直至数组有序
伪代码:
HEAPSORT(A)
// 首先构建好堆
BUILD-MAX-HEAP(A)
for n=A.length downto 2 //只需要到2即可
exchange(A[n], A[1])
MAX-HEAPIFY(A, 1) //维护堆
算法分析:
- 时间复杂度为O(nlgn)
- 堆排序是原址排序算法,不需要额外的内存空间