这是我参与8月更文挑战的第11天,活动详情查看:8月更文挑战
堆
定义:堆是一个数组,可以看作是近似的完全二叉树(如下图)。树上每个结点对应数组中的一个元素。除了最底层这棵树是完全满的。而且从左往右填充。树的根结点下标为1,那么给出一个下标为i的结点,我们很容易可以知道其父节点以及左右子树的下标:
parent(i):return i/2
left(i):return 2i
right(i):return 2i+1
堆排序中一般使用最大堆,最小堆一般用于构建优先队列。
一个包含n个元素的堆可以看作是一颗完全二叉树,那么其高度为堆中的基本操作的运行时间至多和树的高度成正比,即时间复杂度为O(lgn).
维护堆的性质
堆维护需要使得一个最大堆或者最小堆满足其规定(最大堆为父结点值大于子结点,最小堆则反)。比如在最大堆中父结点小于子结点,那么就需要进行堆维护。
堆维护函数的伪代码如下:
A.heap_size:表示数组A的有效长度
Right(i):表示获得数组A的右结点的下标
Left(i):表示获得数组A的左结点的下标
MAX-HEAPIFY(A,i)
l = LEFT(i)
r = Right(i)
if l<=A.heap_size and A[l]>A[i] // 判断左子树是否存在且值是否大于当前结点
largest = l
else largest = i
if r<=A.heap_size and A[largest]<A[r] // 判断右子树是否存在且值是否大于当前结点
largest = r
if largest != i
exchange A[i] with A[largest]
MAX-HEAPIFY(A,largest)
上述代码,实现了当前结点和其子结点中选取最大的并放在合适位置的一个过程。但是在调整后以该结点为父结点的子树可能会违反堆的性质,所以还需要对子树进行调整。
例子:
上图反应了当有效长度为10的时候,MAX-HEAPIFY(A,2) 的执行过程。第一张图是初始状态,下标为2的结点违反了最大堆的性质,于是进行调整,调整后变成第二棵树的状态,发现下标为4的结点还是不满足最大堆的性质,于是继续调整直到变成最后一颗树的状态。
我们可以知道大小为n的子树MAX-HEAPIFY
的运行时间为,也就是说高度为h的子树其时间复杂度为。
建堆
建堆可以用自底向上的调用MAX-HEAPIFY()
方法进行建堆,将一个n = A.length
数组转换成最大堆。具体代码如下:
BUILD-MAX-HEAP(A)
A.heap_size = A.length
for i = A.length/2 downto 1
MAX-HEAPIFY(A,i)
我们可以简单的估算下BUILD-MAX-HEAP
的时间复杂度上面已经知道每次调用MAX-HEAPIFY
的时间复杂度为,BUILD-MAX-HEAP
需要次这样的调用,所以可以得到其时间复杂度为但是这个值并不渐进紧确。
我们可以进一步得到一个更加紧确的界。可以发现不同结点运行MAX-HEAPIFY
和该结点树高有关,而且大部分结点的高度都很小。因此,利用如下性质可以得到一个更加紧确的界,具体计算如下图: