优先队列(Priority Queue) 是一种抽象数据结构,它支持以最高(或最低)优先级为先进行元素的插入和取出操作。
在优先队列中,每次检索得到的都是当前权重最大(或最小)的元素。
为了实现优先队列,最直接的思路是维护一个内部元素有序的数组:每次取数就是第一个数,存数时将元素放入合适位置,并调整序列。
堆(Heap)结构是实现优先队列的首选。
堆(Heap)本质上是一个完全二叉树结构:
(当然也可以是多叉树,但没有必要)
这里用“大根堆”为例,从图中可以看到,每一个节点都比它的子节点更大。 既然“优先队列”可以理解为一种特殊的“队列”,那么我们先用堆实现这个队列的出和入:
从优先队列中取数,显然堆顶的数就是要的那个最大者。
但是将这个数取出后还不能结束,因为需要维护堆的性质。
为了维护堆性质,一般通过将末尾的元素放到堆顶,然后将其不断与左右儿子进行替换,直到他比两个儿子都大或儿子不存在为止,称为“下滤”。
与取数类似,重要的是维护堆的性质。将新数放到最后(上图中的第一个黑色节点中)后,将这个数进行“上浮”。
为了实现堆,其实不需要真的写一个二叉树,用数组就可以做到。
如上图,左边是堆的数组表示,右边是其对应的二叉树。
数组下标从1开始,任意一个下标i,其左右儿子下标刚好就是"i*2"和“i*2+1”。这样在后续代码实现时,代码写起来就会简单很多。
用数组模拟堆还有一个好处,就是可以“原地建堆”。 对于一个元素随机的数组,只需要O(n)的时间复杂度就可以完成随机数组向堆的转化。
具体做法为“从后向前”遍历,对于每一个非叶子节点,就将其进行“下滤”,这样以它为根的子树就变成一个小堆。往前遍历即可。
在进行代码实现之前,做一个问题练习: 对于一个随机队列"[3, 32, 6, 43, 5, 8, 0, 9]",经过一轮反向扫描下滤建大根堆操作,得到的堆的序列是什么? 将答案告知AI教师。
现在,让我们通过编程实践来掌握堆排序的实现。假设我们需要对城市中发生的一系列事件按照紧急程度(以数值大小表示优先级)排序,从而依次处理最高优先级的事件。这相当于将一组数字按从大到小排序的过程,与堆排序的机制完全一致。
请你实现一个堆排序算法 heap_sort(arr),将传入的数组利用堆排序方法排序(从大到小)。
你可以通过实现max_heapify和build_max_heap等函数来完成这一任务。
完成编码后,我们将对算法的性能进行测试,比较不同规模输入下堆排序运行时间的增长情况。
请在下方代码编辑区完成 max_heapify、build_max_heap 和 heap_sort 函数的实现。
import random
import time
def max_heapify(arr, n, i):
"""
维护最大堆性质:假设结点 i 的左右子树已经是最大堆,
调整结点 i 使以其为根的子树成为最大堆
参数:
arr: 存储堆的数组
n: 堆的有效大小(长度)
i: 需要下滤调整的节点索引
"""
# TODO: 在此处实现 "下滤" 操作,将 arr[i] 下沉到正确位置
def build_max_heap(arr):
"""
将无序数组原地建成最大堆,从后往前进行下滤
参数:
arr: 待调整的数组
"""
# TODO: 调用 max_heapify 将 arr 调整为堆
def heap_sort(arr):
"""
利用堆排序算法排序数组(降序)
参数:
arr: 待排序数组
返回:
排序后的数组(从大到小)
"""
# TODO: 完成堆排序的实现
n = len(arr)
# 1. 原地建堆
build_max_heap(arr)
# 2. 依次将当前堆顶(最大值)交换到数组末尾,并缩小堆的范围,然后下滤
#性能测试:对比不同规模输入的堆排序用时
def measure(sort_func, data):
start = time.perf_counter_ns()
sort_func(data.copy())
end = time.perf_counter_ns()
return (end - start) / 10**6 # 毫秒
sizes = [1000, 5000, 10000]
print("堆排序性能测试:")
for n in sizes:
data = [random.randint(0, 1000000) for _ in range(n)]
t = measure(heap_sort, data)
print(f"数据规模 n={n}: 排序耗时 {t:.2f} ms")
请完成并运行上述代码,观察不同输入规模下算法的执行时间。理论上,堆排序的时间复杂度为
相比之下,简单的
这印证了堆排序的效率优势:在最坏情况和平均情况下它都能维持
此外,堆排序是一种原地排序(只需要常数级别的额外空间),这也是相对于归并排序的一个优势。综合来看,利用优先队列实现的堆排序在效率和空间上都表现出色,是一种成熟可靠的排序方法。
前面的内容介绍了多种基于元素比较的排序算法(比较排序),包括快速排序、堆排序等。接下来,我们讨论一个重要的理论结果:
在比较模型下,任意排序算法的最优时间复杂度下界为
决策树是描述比较排序过程的一种抽象模型。
在排序过程中,每进行一次比较(例如“
整个排序算法的运行过程可以被看作是在这样一棵决策树上从根节点走向某个叶节点的过程。决策树的每个叶节点对应一种可能的输入集合及其确定的输出顺序。
当有
对于一棵二叉决策树,若包含
可以看出,当
例如,对于$ n=3$的简单情况,