什么是堆(heap)
堆是一个数组,它可以被看成一个近似的完全二叉树。树上的每一个节点对应数组中的一个元素,而且是从左向右填充。
从上述描述,我们可以做出总结:
- 从存储的角度来看,堆是一个数组
- 从结构的角度来看,堆是一棵完全二叉树
举例:
有一个数组
A=[2, 9, 5, 3, 6, 7, 19, 1, 4, 8]
,那么可以把这个数组看作如下图所示的二叉树:
从上述定义中,自然会引申出一个问题:给定一个数组元素的索引,如何求得它的父节点、左孩子节点、右孩子节点的索引?
规律其实很好找,大家可以自己寻找一下,并参考文后代码中的getPrarent
、getLeftChild
,getRightChild
三个工具函数。这三个函数虽然简单,但却是遍历堆的重要工具。
什么是最大堆(maxHeap)
最大堆是一种特殊的堆结构,在最大堆中,除了根以外,所有的节点都必须满足:A[Parent(i)] <= A[i]
,也就是说,在最大堆中,所有节点的父节点的值都必须大于子节点的值。
举例:
有一个数组
A=[9, 8, 2, 6, 5, 1]
,按照堆的定义,其树状结构如下图所示:
明显的,这棵树的每个节点的值都大于它的左右节点的值,因此这是一个最大堆
如何利用最大堆排序(以升序为例)
根据最大堆的性质可知,如果一个数组A
已经是最大堆,那么其根节点,也就是数组的第一个元素肯定是最大的。假设该数组长度为n
,这时我们可以把数组第一个元素A[0]
和最后一个元素A[n-1]
进行交换,就完成了第一轮排序。接下来,假设我们可以找到一种方法,让剩下的n-1
个元素重新组织成最大堆,那么就可以再次将A[0]
和A[n-2]
进行对换,重复这个过程,便可以完成排序。
这里存在两个问题:
- 对于任意一个数组
A
,如何将其组织成一个最大堆,以便开启上述排序过程的第一步? - 将最大堆的第一个元素和最后一个元素对换位置后,如何重新将剩下的元素组织成最大堆?
下面的讨论将解决这两个问题。
维护最大堆的性质
我们先考虑一个简化问题:假设一个节点i
的左子树和右子树都已经是最大堆了,如何保证以该节点为根节点的子树是最大树呢?
很显然,我们需要分别比较节点i
和它的左孩子和右孩子节点的值。如果i
的值是最大的,那么这个过程直接就可以停止了。如果节点i
的值并不是最大的,例如右孩子节点的值才是最大的,那么就要交换节点i
和它的右孩子节点。但是,这样做之后,由于右子树根节点的值发生了变更,那么右子树的最大堆性质就可能遭到了破坏,因此需要递归调用上述过程,一直到某个节点满足最大堆的性质为止。该过程请参考下面代码中的maxHeapify
函数。
如何构建一个最大堆
现在来回来上述问题1:对于任意一个数组A
,如何将其组织成一个最大堆?
首先,将A
看成一个堆。通过寻找规律可知,第n/2 + 1
及以后的元素都是叶子节点。我们可以从第n/2
个节点开始向前回溯,利用maxHeapify
函数,便可以将A
改造成一个最大堆。具体过程请参考下面代码中的buildMaxHeap
函数。
最终排序过程
经过上一小节的操作,我们已经将一个任意堆A
组织成了一个最大堆,这时第一个节点已经是堆中的最大元素了,我们将第一个节点和最后一个节点对换,就能完成第一轮排序。现在只要解决上述问题2,也就是如何将数组A
的前n-1
个元素重新组织成最大堆,然后将新的最大堆的根节点对换到后面去,就可以实现整个排序过程了。
其实这个问题也很简单,由于节点对换后,根节点的左右子树仍是最大堆,我们继续调用maxHeapify
函数就可以了,唯一需要注意的是,在重新组织最大堆时,需要将已经对换到后面的元素排除在外,也就是说,每次对数组前面的n-1
,n-2
,...
个元素调用maxHeapify
函数即可,具体过程请参考下面代码中的heapSort
函数,参与构建堆的元素个数通过maxHeapify
中的heapsize
参数控制。
代码
最新代码请参考本人github
# heap sort
# Utility Function to get parent node by index
# i is the index of array member
def getParent(i):
idx = (i-1)//2
if idx >= 0:
return idx
else:
return None
# Utility Function to get left child node by index
def getLeftChild(i, arr):
idx = 2*i + 1
if idx < len(arr):
return idx
else:
return None
# Utility Function to get right child node by index
def getRightChild(i, arr):
idx = 2*i + 2
if idx < len(arr):
return idx
else:
return None
# Given an array A and an index i
# assume left tree of i and right tree of i are max heaps
# make tree with root i to be a max heap
# heapsize is used to control how much elements to be processed
def maxHeapify(A, i, heapsize):
largest = i
leftIdx = getLeftChild(i, A)
if leftIdx is not None and leftIdx < heapsize and A[leftIdx] > A[i]:
largest = leftIdx
rightIdx = getRightChild(i, A)
if rightIdx is not None and rightIdx < heapsize and \
A[rightIdx] > A[largest]:
largest = rightIdx
# notice that the process need to be continued
# only if larest is not i
# otherwise, the process stops
if largest is not i:
tmp = A[i]
A[i] = A[largest]
A[largest] = tmp
maxHeapify(A, largest, heapsize)
# for every node which is not leaf node
# adjust it to a max heap
def buildMaxHeap(A):
for i in range(len(A)//2, -1, -1):
maxHeapify(A, i, len(A))
# final function for heapsort
def heapSort(A):
# firstly, reconstruct A to be a max heap
buildMaxHeap(A)
heapsize = len(A)
# find max element for every step
for i in range(len(A) - 1, 0, -1):
tmp = A[0]
A[0] = A[i]
A[i] = tmp
heapsize = heapsize - 1
maxHeapify(A, 0, heapsize)
if __name__ == '__main__':
print("--->Test utility functions...")
A = [4, 1, 3, 2, 16, 9, 10, 14, 8, 7]
print("original A is: ", str(A))
for idx, val in enumerate(A):
print("<-------index: % d, value:: %d------->" % (idx, val))
parentIdx = getParent(idx)
if parentIdx is not None:
print("parent node of %d: %d" % (A[idx], A[parentIdx]))
else:
print("This is Root Node")
leftChildIdx = getLeftChild(idx, A)
if leftChildIdx is not None:
print("left node of %d: %d" % (A[idx], A[leftChildIdx]))
else:
print('\n')
continue
rightChildIdx = getRightChild(idx, A)
if rightChildIdx is not None:
print("right node of %d: %d" % (A[idx], A[rightChildIdx]))
print('\n')
print("--->Test maxHeapify...")
A = [16, 4, 10, 14, 7, 9, 3, 2, 8, 1]
print("original A is: ", str(A))
print("lfet and right tree of index 1 are both max heap befor process")
maxHeapify(A, 1, len(A))
print("after call maxHeapify to process index 1:")
print(A)
print('\n')
print("--->Test buildMaxHeap...")
A = [4, 1, 3, 2, 16, 9, 10, 14, 8, 7]
print("original A is: ", str(A))
buildMaxHeap(A)
print("processed A is: ", str(A))
print('\n')
print("--->Test heapSort...")
A = [4, 1, 3, 2, 16, 9, 10, 14, 8, 7]
print("original A: ", str(A))
heapSort(A)
print("sorted A: ", str(A))