一、堆介绍
1、 堆Heap
- 堆是一个完全二叉树
- 每个非叶子结点都要大于或者等于其左右孩子结点的值称为大顶堆
- 每个非叶子结点都要小于或者等于其左右孩子结点的值称为小顶堆
- 根结点一定是大顶堆中的最大值,一定是小顶堆中的最小值
2、 大顶堆
- 完全二叉树的每个非叶子结点都要大于或者等于其左右孩子结点的值称为大顶堆
- 根结点一定是大顶堆中的最大值
3、 小顶堆
- 完全二叉树的每个非叶子结点都要小于或者等于其左右孩子结点的值称为小顶堆
- 根结点一定是小顶堆中的最小值
二、堆排序
1、构建完全二叉树
- 构建一个完全二叉树存放数据,并根据性质5对元素编号,放入顺序的数据结构中
2、构建大顶堆
核心算法
- 度数为2的结点A,如果它的左右孩子结点的最大值比它大的,将这个最大值和该结点交换
- 度数为1的结点A,如果它的左孩子的值大于它,则交换
- 如果结点A被交换到新的位置,还需要和其孩子结点重复上面的过程
起点结点的选择
- 从完全二叉树的最后一个结点的双亲结点开始,即最后一层的最右边叶子结点的父结点开始
- 结点数为n,则起始结点的编号为n//2(性质5)
下一个结点的选择
- 从起始结点开始向左找其同层结点,到头后再从上一层的最右边结点开始继续向左逐个查找,直至根结点
3、大顶堆目标
- 确保每个结点的都比左右结点的值大
4、排序思路
- 每次都要让堆顶的元素和最后一个结点交换,然后排除最后一个元素,形成一个新的被破坏的堆
- 让它重新调整,调整后,堆顶一定是最大的元素
- 再次重复第1、2步直至剩余一个元素
三、堆排序代码实现
import math
def print_tree(array):
'''
i 前空格 元素间
1 3 7=2*3-1 0 2*4+1
2 2 3=2*2-1 7=2*3+1
3 1 1=2*1-1 3=2*1+1
4 0 0=2*0-1 1=2*0+1
'''
length = len(array)
index = 1
# 因为使用时前面补0了,不然应该是math.ceil(math.log2(len(array)+1))
depth = math.ceil(math.log2(length)) # 4
#print(depth)
sep = ' ' * unit_width
for i in range(depth-1, -1, -1):
pre = 2 ** i - 1
print(sep * pre, end='') # 前置空格
offset = 2 ** (depth - i - 1)
line = array[index: index+offset] # 取数字
#print(line)
intervalspace = sep * (2 * pre + 1)
print(intervalspace.join(map(str, line))) # join数据
index += offset
# Heap Sort
# 为了和编码对应,增加一个无用的0在首位
# origin = [0, 50, 10, 90, 30, 70, 40, 80, 60, 20]
origin = [0, 30, 20, 80, 40, 50, 10, 60, 70, 90]
total = len(origin) - 1 # 初始待排序元素个数,即n
print(origin)
print_tree(origin)
print("="*50)
def heap_adjust(n, i, array: list):
'''
调整当前结点(核心算法)
调整的结点的起点在n//2,保证所有调整的结点都有孩子结点
:param n: 待比较数个数
:param i: 当前结点的下标
:param array: 待排序数据
:return: None
'''
while 2 * i <= n:
# 孩子结点判断 2i为左孩子,2i+1为右孩子
lchile_index = 2 * i
# 先假定左孩子大,如果存在右孩子且大则最大孩子索引就是右孩子
max_child_index = lchile_index # n=2i
if n > lchile_index and array[lchile_index + 1] > array[lchile_index]: # n>2i说明还有右孩子
max_child_index = lchile_index + 1 # n=2i+1
# 和子树的根结点比较
if array[max_child_index] > array[i]:
array[i], array[max_child_index] = array[max_child_index], array[i]
i = max_child_index # 被交换后,需要判断是否还需要调整
else:
break
# print_tree(array)
# 构建大顶堆、大根堆
def max_heap(total,array:list):
for i in range(total//2,0,-1):
heap_adjust(total,i,array)
return array
print_tree(max_heap(total,origin))
print("="*50)
def sort(total, array:list):
while total > 1:
array[1], array[total] = array[total], array[1] # 堆顶和最后一个结点交换
total -= 1
if total == 2 and array[total] >= array[total-1]:
break
heap_adjust(total,1,array)
return array
print_tree(sort(total,origin))
print(origin)
四、堆排序总结
1、性质
- 利用堆性质的一种选择排序,在堆顶选出最大值或者最小值
2、时间复杂度
- 堆排序的时间复杂度为O(nlogn)
- 由于堆排序对原始记录的排序状态并不敏感,因此它无论是最好、最坏和平均时间复杂度均为O(nlogn)
3、空间复杂度
- 只是使用了一个交换用的空间,空间复杂度就是O(1)
4、稳定性
- 不稳定的排序算法