堆排序是面试笔试涉及到的比较多的排序方法。排序方法有很多,当被问到时不能只会冒泡,我们的目的是掌握所有主流排序方法,吊打面试官。
堆排序(英语:Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆是一个近似完全二叉树的结构,并同时满足堆积的性质:即子节点的键值或索引总是小于(或者大于)它的父节点。
百度百科
完全二叉树(complete binary tree)简单定义:除最后一层,其他层节点都达到最大值;最后一层节点向左对齐;(并不要求左节点要大于右节点)
每个结点的值都大(小)于或等于其左右孩子结点的值,称为大(小)顶堆;大小顶堆只是父子节点大小顺序相反,所以我们只讨论大顶堆;
完全二叉树效率很高,堆排序就是根据完全二叉树的特性来进行排序。简单地说,就是将列表中的数据找到最大的作为根节点,比它小的作为左右节点。
由图可算出节点索引特性:
左子节点索引值=父节点索引值*2+1
右子节点索引值=父节点索引值*2+2
算法步骤:
-
根据特性写出递归建堆方法
-
根据建堆方法找出最大值作为堆顶元素(此时堆顶元素在列表首位,后面值的顺序也基本按二叉树性质构建完毕)
-
将列表首位和末尾交换
-
将列表剩余数值重新建堆,最大值与列表倒数第二个交换,如此循环
如图:(底部文字为:通过两次判断找到最大值,实现一次列表值交换。后续动画只展示列表交换)
有几个需要注意的地方:
-
构建堆时为了获取最大值,我们先比较父节点和左子节点的值,然后比较最大节点和右子节点的值,这样对后续构建也更方便,但前面也说了,完全二叉树并不要求左子节点小于右。
-
构建最大堆时不需要从最后一位往前循环,因为子节点最小也是2n+1。比如list=[1,2,3],如果把3的索引2传进去,左子节点索引为5,超出列表范围,进行了无意义的比较。其实只需要把1的索引0传进去就可以了。也就是len(list) //2 - 1,所以图片中从第三个开始算。
-
为什么从后往前循环:
可以从例子来看;对于[1,2,3,4,7,5,6],我们以3为基准和5,6比较,然后以2为基准和4,7比较,最后1为基准和2,3位置交换后的数比较,这样覆盖了全部数据,同时将最大值逐步送到第一位。
python代码:
# 构建堆
# current_index:父节点索引 end_index:构建值索引,超出部分不参与构建
def heapify(list, current_index, end_index):
# 存储最大值所在索引
max_index = current_index
# 左右子节点
left_child_node = 2 * current_index + 1
right_child_node = 2 * current_index + 2
if left_child_node < end_index:
# 出现更大的值,更新最大索引
if list[left_child_node] > list[current_index]:
max_index = left_child_node
if right_child_node < end_index:
if list[right_child_node] > list[max_index]:
max_index = right_child_node
# 最大值不是父节点索引值,交换
if current_index != max_index:
list[current_index], list[max_index] = list[max_index], list[current_index]
print("构建:" , list)
heapify(list, max_index, end_index)
# 构建最大堆
def buildMaxHeap(list):
# 从后往前找最大值,不需要从列表最后一个开始(下面//整除被当成注释了,好尴尬)
for i in range(len(list) // 2 -1, -1,-1):
heapify(list, i, len(list))
# 堆排序
def heapSort(list):
buildMaxHeap(list)
print("最大堆构建完毕")
for i in range(len(list)-1,0 ,- 1):
list[0], list[i] = list[i], list[0]
print('交换:', list)
heapify(list, 0, i)
return list
list = [1,2,3,4,7,5,6]
print(list)
res = heapSort(list)
print('result:', list)
打印结果:
[1, 2, 3, 4, 7, 5, 6]
构建: [1, 2, 6, 4, 7, 5, 3]
构建: [1, 7, 6, 4, 2, 5, 3]
构建: [7, 1, 6, 4, 2, 5, 3]
构建: [7, 4, 6, 1, 2, 5, 3]
最大堆构建完毕
交换: [3, 4, 6, 1, 2, 5, 7]
构建: [6, 4, 3, 1, 2, 5, 7]
构建: [6, 4, 5, 1, 2, 3, 7]
交换: [3, 4, 5, 1, 2, 6, 7]
构建: [5, 4, 3, 1, 2, 6, 7]
交换: [2, 4, 3, 1, 5, 6, 7]
构建: [4, 2, 3, 1, 5, 6, 7]
交换: [1, 2, 3, 4, 5, 6, 7]
构建: [3, 2, 1, 4, 5, 6, 7]
交换: [1, 2, 3, 4, 5, 6, 7]
构建: [2, 1, 3, 4, 5, 6, 7]
交换: [1, 2, 3, 4, 5, 6, 7]
result: [1, 2, 3, 4, 5, 6, 7]
堆排序就讲到这里,我是Yme,希望能帮到你!
算法的路上一起努力,你不能改变过去,但你可以改变未来。