堆排序详解(python实现),附树结构概念及性质

堆是完全二叉树结构,了解堆排序前需要先了解树的概念及其性质。
在次简要说明下其性质,详细定义及说明请参考二叉树

定义:

  1. 一种非线性结构,是n(n>=0)个元素的集合;
  2. 只有一个没有前驱结点(父结点)的元素称为根;
  3. 树中除根结点外,其余元素只能有一个前驱结点(父结点),可以有零个或多个后继结点(子结点),二叉树最多有两个子结点;

性质:

  1. 在二叉树的第i层上至多有2i-1个结点(i >= 1);

  2. 深度为k的二叉树,至多有2k-1个结点;

  3. 含有n(n >= 1)个结点的完全二叉树的深度为math.ceil(log2(n+1)),不小于对数值的最小整数向上取整;

  4. 如果有一颗n个结点的完全二叉树,结点按照层序编号,如下图所示:
    在这里插入图片描述
    4.1 如果i=1,则结点i是二叉树的根,无双亲; 如果i>1,则双亲为int(i/2),向下取整。就是子结点的编号整除2得到的就是父结点的编号,如果父结点是i,那么左孩子结点就是2i,右孩子结点就是2i+1;

    4.2 如果2i>n,则结点i无左孩子,即结点i为叶子结点;否则其左孩子节点存在编号为2i;

    4.3 如果2i+1>n,则结点i无右孩子,注意在这里并不能说明结点i有没有左孩子;否则右孩子结点存在编号为2i+1;

堆排序

核心算法步骤:

  1. 构建完全二叉树
  2. 构建大顶堆,根为列表中最大的数
  3. 排序
1、构建完全二叉树

假设给定数组[30,20,80,40,50,10,60,70,90],以此构建完全二叉树如下图所示:

在这里插入图片描述
为了数组的索引和树的编码对应,在数组的首位增加一个占位字符0,调整后数组为[0,30,20,80,40,50,10,60,70,90]此时数字的索引和树的结点编号一致;

堆结点调整思路:
1、度数为2的结点A,如果它的左右孩子结点的最大值比A大,将最大值和结点A交换;
2、度数为1的结点A,如果它的左孩子结点的值比A大,则和结点A交换;
3、如果结点A被交换到新的位置,需要和其孩子结点重复上面的交换逻辑。

#初始数组,首位增加的占位,保证索引和树结点编号对应
origin = [0,30,20,80,40,50,10,60,70,90]

def heap_adjust(n,i,array:list):
	'''
	调整结点
	n:待比较的数据的个数
	i:当前结点的编号(数据的索引)
	array:待排序的数据
	'''
	#根据性质4.2,限制循环条件为:存在左孩子节点
	while 2 * i <= n:
		#根据性质4.2,当左孩子存在时,节点编号为2*i,同时假设左孩子节点为最大孩子节点
		lchild_index, max_chile_index = 2 * i
		#根据性质4.3,当n<2*i时说明有右孩子节点,同时判断
		if 2 * i > n and array[lchild_index +1] > array[lchild_index]:
			max_child_index = lchild_index
		if array[max_child_index] > array[i]:
			array[max_child_index],array[i] = array[i],array[max_child_index]
			i = max_child_index
		else:
			break

到目前为止解决了单个结点的调整,接下来需要使用循环从起始结点开始以此解决比起始结点小的结点

2、构建大顶堆

起始结点选择:
根据性质4.1,当i=n时,取得最后一个有子结点的结点,所以调整的起始结点就是n//2,保证所有结点都有孩子结点;

下一结点:
由于之前构造了一个占位0,所以树结点的编号和列表的索引正好相对应,所以每循环以此,索引-1就是下一个需要调整的结点,直到索引为1.

# 因为列表中加了占位的0,所以元素个数要-1
total = len(origin) - 1
def max_heap(total,array:list):
	#从最后一个有子结点的结点开始,每次执行后树结点编号-1
	for i in range(total//2,0,-1):
		heap_adjust(total,i,array:list)
	return array
3、排序

排序思路:

  • 每次都让堆顶的元素和最后一个元素交换,然后排除最后一个元素(进入有序区);
  • 剩余元素再次调整构建大顶堆;
  • 重复前两步,直至剩余一个元素,此时列表的元素就是按照升序排列的。
def sort(total,array:list):
	#循环至列表内只有一个元素
	while total > 1:
		array[1],array[total] = array[total],array[1]
		#每次交换后,列表总数-1
		total -= 1
		#重新调整堆顶
		heap_adjust(total,i,array)
	return array
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值