堆排序
此堆非彼堆,先简单说一下概念和思路。
堆
- 是一个完全二叉树
- 每个非叶子结点都大于或等于其左右子结点的值称为大顶堆
- 每个非叶子结点都小于或等于其左右子结点的值称为小顶堆
- 根结点一定是大顶堆中的最大值,一定是小顶堆中的最小值
构建完全二叉树
- 待排序数字为 30,20,80,40,50,10,60,70,90
- 构造一个列表为 [0,30,20,80,40,50,10,60,70,90]
构建大顶堆
- 度数为2的结点A,如果它的左右子结点的最大值比它大,将这个最大值和该结点交换
- 度数为1的结点A,如果它的子节点的值大于它,则交换
- 如果结点A被交换到新的位置,还需要和其子结点重复上面的过程
起点的选择
- 从完全二叉树的最后一个结点的父结点开始
- 结点数为n,则起始结点的编号为 n//2
下一个节点选择
- 从起始结点开始向左找其同层结点,到头后再从上一层的最右边结点开始向左逐个查找,直至根结点
排序
- 将大顶堆根结点这个最大值和最后一个叶子结点交换,那么最后一个叶子结点就是最大值,将这个叶子结点排除在待排序结点之外
- 从根结点开始(新的根结点),重新调整为大顶堆后,重复上一步
1、堆排序是利用堆性质的一种选择排序,在堆顶选出最大值或者最小值
2、时间复杂度为O(nlogn)
3、由于堆排序对原始记录的排序状态并不敏感,因此它无论是最好、最坏时间复杂度均为O(nlogn)
4、不稳定的排序算法(在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的,否则称为不稳定的)
import math
# 构建完全二叉树
origin = [0, 30, 20, 80, 40, 50, 10, 60, 70, 90]
total = len(origin) - 1
# 打印树
def print_tree(origin, unit_width=2):
length = len(origin)
index = 1
# 由于前面补0了,原本应该是math.ceil(math.log2(len(origin)+1)),这里减一
depth = math.ceil(math.log2(length))
sep = ' ' * unit_width
for i in range(depth-1, -1, -1): # range(3,-1,-1) [3,2,1,0]
# 前半部分空格数 7,3,1,0
pre = 2**i - 1
print(sep * pre, end='')
# 偏移量,1,2,4,8
offset = 2 ** (depth - i - 1)
# 切片获取数据,origin[1,2],origin[2,4],origin[4,8],origin[8,16]
line = origin[index: index+offset]
# 元素间距,0,7,3,1
intervalspace = sep * (2*pre + 1)
print(intervalspace.join(map(str, line)))
index += offset
# 最大堆调整,单个节点调整
def heap_adjust(total, i, origin):
"""
total: 节点总数
i: 当前节点的索引
"""
# 30
# 20 80
# 40 50 10 60
# 70 90
while 2*i <= total:
# 2i为左子节点,2i+1为右子节点
lchild_index = 2*i
max_child_index = lchild_index
# total>2i说明还有右子节点
if total > lchild_index and origin[lchild_index + 1] > origin[lchild_index]:
max_child_index = lchild_index + 1
# 和子树的根节点比较
if origin[max_child_index] > origin[i]:
origin[i], origin[max_child_index] = origin[max_child_index], origin[i]
i = max_child_index
else:
break
# 构建大顶堆
def max_heap(total, origin):
# 从i=total//2开始,到堆顶节点,依次调整
for i in range(total//2, 0, -1): # range(4,0,-1) [4,3,2,1]
heap_adjust(total, i, origin)
return origin
max_heap(total, origin)
# 90
# 70 80
# 40 50 10 60
# 20 30
# 排序
def sort(total, origin):
while total > 1:
# 堆顶和最后一个节点交换
origin[1], origin[total] = origin[total], origin[1]
# 每交换一次就固定一个数
total -= 1
if total == 2 and origin[total] >= origin[total - 1]:
break
# 这时只有堆顶节点不平衡,调整堆顶节点即可
heap_adjust(total, 1, origin)
return origin
print_tree(sort(total, origin))
# 10
# 20 30
# 40 50 60 70
# 80 90