堆 Heap
- 堆是一个完全二叉树
大顶堆
- 每个非叶子结点都要大于或者等于其左右孩子结点 ,称为大顶堆
- 根节点一定是大顶堆中的最大值
小顶堆
- 每个非叶子结点都要小于或者等于其左右孩子结点 ,称为小顶堆
- 根节点一定是小顶堆中的最小值
堆排序Heap Sort
构建完全二叉树
- 将待排序的数据,构建成一个完全二叉树
- 并根据性质5对元素进行编号,放入顺序的数据结构中
- 构造一个列表,在原有数据的第一个数据前边加一个数据0
构建大顶堆
- 将这组数据构建成完全二叉树之后,再将其转换成一个大顶堆
核心算法
- 调整数据,根节点和孩子结点相比较,大的交换到根节点
- 度数为2 的结点A,如果它的左右孩子结点的最大值比A大,将这个最大值和该结点交换
- 度数为1 的结点A,如果它的左孩子结点的值比A大,则交换
- 如果进行上述交换之后,再次符合上两步的条件,则继续重复上两步
起点结点的选择
- 选择一个根节点作为起点,再进行调整
- 从完全二叉树的最后一个结点的父节点开始,即最后一层的最右边叶子结点的父结点开始
- 结点数为n,则起始结点的编号为 n//2 (性质5)
下一个结点的选择
- 有了起点,那下一个结点该是哪一个?
- 从起始结点开始向左找其同层结点,到头后再从上一层的最右边结点开始继续向左边结点开始继续向左逐个查找,直至根节点
目标
确保每个非叶子结点的值都比其左右孩子结点的值大
排序
- 将大顶堆根节点这个最大值和最后一个叶子结点交换,此时最后一个叶子结点就是最大值,将这个叶子结点排除在待排序结点之外
- 从根节点开始,重新调整为大顶后,重复上一步
- 堆顶和最后一个进行交换,并排除最后一个结点
总结
- 堆排序是利用堆性质的一种选择排序,在堆顶选出最大值或最小值
- 时间复杂度
- 堆排序的时间复杂度是O(nlog n)
- 空间复杂度
- 只是使用了一个交换的空间,所以空间复杂度为O(1)
- 稳定性
- 不稳定的排序算法
代码实现
import math
def print_tree(array):
''''
构建满二叉树
'''
unit_width = len(str(max(array))) # 数组中最大数的长度,作为数组每个数据所占的单位长度
array = [0] + array
length = len(array) # 数组长度
depth = math.ceil(math.log2(length)) # 树的深度,即层数
index = 1 # 遍历数组,将每一个数据都去居中
space = " "*unit_width # 需要补的单位空格
for i in range(depth-1, -1, -1):
pre = 2**i-1 # 每一行第一个数的前边有多少个单位的数据可填
print(pre * space, end='')
offset = 2**(depth-i-1) # 构建切片索引,1,2,4,8……,即每一行的数据个数
line = array[index : index+offset] # 切片,得到每一行的数据
interval = (2 * pre +1) * space # 后边补的空格
print(interval.join(map(lambda x : "{:{}}".format(x,unit_width),line)))
index += offset # 因为每次循环进来,都要切片,所以索引要变,起始索引变为上一次切片的后边界值
return array
import random
origin =random.sample(range(1,49),k=15)
print(origin)
origin = print_tree(origin)
total = len(origin)-1
def heap_adjust(n, i, array: list):
'''
调整当前节点
在一个小树中进行调整,将最大的数据调整到小树的根结点
先假设最大值的索引是小树的左孩子
再比较左孩子与右孩子的大小,如果右孩子大于左孩子,就将最大索引换为有孩子的索引
最后用最大孩子与根相比,如果孩子大与根,则根与对应孩子换位置,此时根索引就是最大索引
因为有while循环,所以,继续进行上边的几步,此时的i 已经变了
:param n: total
:param i: total//2
:param array:
:return:
'''
while 2 * i <= n:
# 孩子结点判断,2*i为左孩子,2*i+1为右孩子
lchild_index = 2*i # 左孩子索引
# print("根",array[i])
# print("左孩子",array[lchild_index])
max_child_index = lchild_index # 先默认最大值所在的索引是左孩子
if 2*i < n and array[lchild_index + 1] > array[lchild_index]:
# 如果左孩子索引小于总长度,即代表有右孩子,,右孩子大于左孩子,则进行换
max_child_index = lchild_index + 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(array)
# print(origin)
# print(print_tree(array[1:]))
# heap_adjust(total, total//2, origin)
# 构建大顶堆
def max_heap(total, array:list):
'''
从最后一个根索引开始倒着遍历
调用heap_adjust()函数,从此次的根开始调整大根堆
'''
# print(array,"===")
for i in range(total//2,0, -1): # 确定根的索引从最后一个根开始
heap_adjust(total, i , array) # 调用调整当前节点的函数,将不规则的树调整成大顶堆
return array
max_heap(total,origin)
#排序
def sort(total, array:list):
'''
在上边两个函数中,已经构建出大顶堆,接下来就是排序
排序:①将大顶堆的堆顶与新树的最后一个结点进行调换,树的尾部作为有序区,有序区变大,无序区变小
② 再次调用第一个函数,调整部分树的分支,再次成为大顶堆的新树
③ 重复 ①、②
优化:当树剩两个数据,且堆顶小于左孩子,则不用再比较了
:param total: 数组array的索引,倒着来
:param array:
:return:
'''
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
sort(total, origin)
print(origin[1:])