十分钟简明易懂搞定堆排序算法,附Python源码

什么是堆(heap)

是一个数组,它可以被看成一个近似的完全二叉树。树上的每一个节点对应数组中的一个元素,而且是从左向右填充。
从上述描述,我们可以做出总结:

  • 从存储的角度来看,堆是一个数组
  • 从结构的角度来看,堆是一棵完全二叉树

举例:

有一个数组A=[2, 9, 5, 3, 6, 7, 19, 1, 4, 8],那么可以把这个数组看作如下图所示的二叉树:
在这里插入图片描述

从上述定义中,自然会引申出一个问题:给定一个数组元素的索引,如何求得它的父节点左孩子节点右孩子节点的索引?

规律其实很好找,大家可以自己寻找一下,并参考文后代码中的getPrarentgetLeftChildgetRightChild三个工具函数。这三个函数虽然简单,但却是遍历堆的重要工具。

什么是最大堆(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]进行对换,重复这个过程,便可以完成排序。

这里存在两个问题:

  1. 对于任意一个数组A,如何将其组织成一个最大堆,以便开启上述排序过程的第一步?
  2. 将最大堆的第一个元素和最后一个元素对换位置后,如何重新将剩下的元素组织成最大堆?

下面的讨论将解决这两个问题。

维护最大堆的性质

我们先考虑一个简化问题:假设一个节点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))
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值