python3堆排序_python-堆排序

代码环境:python3.6

二叉树

本文涉及树这种数据结构,所以下面我们先简单回顾下树的相关概念。二叉树:每个节点最多有两个子树的树结构,如下图:满二叉树:每一层都挂满了节点的二叉树,上面图中的二叉树,就是满二叉树。

完全二叉树:假如有 n 层,从 1 到 n-1 层与满二叉树一样,最后一层有两种可能,要么是满节点,要么节点集中在最左边,如下图:叶子节点:没有孩子节点的节点,就是叶子节点,上面完全二叉树图中的节点5、6、7、8就是叶子节点。

二叉堆

堆排序,顾名思义需要用到堆,更具体的说是用到二叉堆。

二叉堆是一种特殊的完全二叉树,可分为以下两种:最大堆:根节点的值最大,任何一个父节点的值,都大于等于它左右孩子节点的值。

最小堆:根节点的值最小,任何一个父节点的值,都小于等于它左右孩子节点的值。

python 中的二叉堆存储在列表中,依靠列表的下标来定位各个节点。

假设父节点的下标是p,那么对应左孩子下标就是2*p+1,对应右孩子的下标就是2*p+2。

下面我们介绍二叉堆的常用两种操作,最大堆和最小堆思路都是一样的,本文我们以最大堆为例。

最大堆删除堆顶节点后的自我调整

根据最大堆的特性,当我们删除一个最大堆的堆顶,堆经过自我调整重新让每一个父节点都比其子节点大,这样第二大的节点就会被交换上来成为新最大堆的堆顶。

这个自我调整的过程,是后面堆排序的核心算法,我们来看看这个具体过程:

假设有最大堆[9, 7, 6, 5, 2, 1, 4, 3],如下图:

当删除值为9的堆顶节点时,为了维持完全二叉树的结构,我们把堆的最后一个节点3补到原本堆顶的位置,如下图:

此时父节点为3,比左孩子节点7小,父节点3下沉:

此时父节点3,比左孩子节点5小,父节点3下沉:

到这里新最大堆调整已经完成。

我们注意到,这个自我调整过程,其实就是最开始补到堆顶位置的节点3下沉的过程。

构建二叉堆

构建二叉堆,也就是把一个无序的完全二叉树调整为二叉堆,本质上就是:让不满足二叉堆特性的非叶子节点依次下沉,下面我们来看看这个过程。

给定一个无序的序列[2, 5, 4, 3, 1, 9, 7, 6]构建最大堆,我们先得到一个无序的完全二叉树如下:

我们从最后一个非叶子节点3开始。此时节点3比其子节点6小,节点3下沉:

轮到非叶子节点4,此时节点4比其子节点9小,节点4下沉:

轮到非叶子节点5,此时节点5比其子节点6小,节点5下沉:

轮到非叶子节点2,此时节点2比其子节点9小,节点2下沉:

注意到节点2依然比其子节点7小,节点2需要继续下沉:

到这里,最大堆构建完成。

可能我们会有个疑问,为什么不从根节点开始比较?其实也是可以的,但我们用这种方法重复上面的下沉过程,会发现多出大量的交换操作。

所以我们在节点下沉过程中,选择的路径是:从最后一个非叶子节点开始,从下往上、从右到左依次进行比较。

那么,如何确定最后一个非叶子节点的下标?

假设给定的序列长度为n,则最后一个孩子节点的下标为n-1,设其父节点下标为p,分两种情况讨论:最终节点为左孩子节点,则有n-1=2*p+1,即p=(n-2)/2

最终节点为右孩子节点,则有n-1=2*p+2,即p=(n-3)/2

根据地板除向下取整的特性,我们选择p=(n-2)//2就都能满足这两种情况。

堆排序算法的实现

从上面最大堆的两种常用操作过程中,我们知道其核心就是父节点与其较大子节点比较,判断父节点是否下沉的方法,下面代码中我们用方法heap_sink表示。

堆排序步骤:构建最大堆;

循环删除当前最大堆的堆顶(实际上会替换到序列最后面),heap_sink生成新最大堆。

def heap_sink(heap, heap_size, parent_index):

"""最大堆-下沉算法"""

child_index = 2 * parent_index + 1

# temp保存需要下沉的父节点,用于最后赋值

temp = heap[parent_index]

while child_index < heap_size:

# 如果有右孩子,且右孩子比左孩子大,则定位到右孩子

if child_index + 1 < heap_size and heap[child_index + 1] > heap[child_index]:

child_index += 1

# 如果父节点的值不小于左右孩子节点的值,可直接跳出循环

if temp >= heap[child_index]:

break

heap[parent_index] = heap[child_index]

parent_index = child_index

child_index = 2 * parent_index + 1

heap[parent_index] = temp

def heap_sort(mylist):

"""堆排序"""

n = len(mylist)

# 1. 无序列表构建成最大堆

for i in range(n - 2 // 2, -1, -1):

heap_sink(mylist, n, i)

# 2. 循环删除堆顶元素,移到列表尾部,调节堆产生新的堆顶

for i in range(n - 1, 0, -1):

mylist[0], mylist[i] = mylist[i], mylist[0]

heap_sink(mylist, i, 0)

if __name__ == "__main__":

mylist = [1, 3, 4, 5, 2, 6, 9, 7]

heap_sort(mylist)

print(mylist)

算法效率时间复杂度:O(nlogn)

空间复杂度:O(1)

稳定性:不稳定

原地排序

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值