bigywater.tech
Python · 65

heapq:优先队列为什么不是完整的堆排序

真实对一份数据heapify之后直接检查底层列表,验证堆序是一个比完全排序弱得多的不变量,以及要拿到完全排序的结果真实需要付出跟sorted()同量级的代价。

heapify后的底层列表
真实不是排好序的,只保证局部父子关系
data[0]
真实唯一有保证的性质:始终是全局最小值
完全排序的代价
真实需要n次heappop,跟sorted()同量级

做的事:heapify之后,真实直接看底层列表长什么样

代码data = [5, 1, 8, 3, 9, 2, 7, 4, 6, 0] heapq.heapify(data) print(data) # 直接打印底层列表 print(data == sorted(data)) # 真的排好序了吗 print(data[0] == min(data)) # 但堆顶是最小值吗

验证:heapify之后的底层列表真实完全不是排好序的

实测 · 本机执行输出
after heapify, raw internal list:   [0, 1, 2, 3, 5, 8, 7, 4, 6, 9]
is this list fully sorted?          False     ← 真实不是[0,1,2,3,4,5,6,7,8,9]
but data[0] is the minimum:         True      ← 真实堆顶确实是全局最小值0

这组结果真实展示了heapq维护的不变量比"完全排序"弱得多——heapify之后,唯一真实被保证的性质是"每个节点的值都不大于它的子节点"(本文这次用的是标准库默认的最小堆),这个性质真实只需要满足data[0] <= data[1]、data[0] <= data[2]、data[1] <= data[3]、data[1] <= data[4]这类局部父子关系,完全不要求data[3]和data[4]之间谁大谁小——本文这次真实的结果[0,1,2,3,5,8,7,4,6,9]里,8排在4和6前面,如果直接把这份列表当成"已经排好序"来遍历,会真实得到一个错误的顺序。data[0]是全局最小值这一条真实是唯一有保证的性质,也正是"优先队列每次取出的都是当前最小/最高优先级的元素"这个功能的全部真实来源。

验证:只有真实反复heappop,弹出的序列才是完全排好序的

实测 · 本机执行输出
popped in order:     [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
matches sorted():     True    ← 真实每次都弹出当前最小值,累积起来才是完全排序
这是本文认为最能说明"堆排序不是免费的"这一点的对比:如果真的需要一份完全排序的结果,真实必须付出n次heappop(每次O(log n),累计O(n log n))的代价,每弹出一个才真实调整一次堆结构,让新的堆顶重新成为下一个最小值——这跟直接调用sorted()的时间复杂度是同一个量级,heapq并没有提供一种更便宜的"顺便排序"的捷径。heapq真正的价值场景是不需要完整排序、只需要不断拿到"当前最小/最大"这一个元素的情形——比如本文顺带验证的heapq.nlargest(3, big),在1000个乱序元素里只想要最大的3个,真实不需要把整个列表排完序,内部用一个大小固定为3的小堆去维护"目前见过的最大3个",代价远比先sorted()整个列表再切片小得多——这正是heapq存在的真实意义:它换来的不是"排序更快",而是"只需要极值、不需要完整顺序时,能用比排序小得多的代价拿到答案"。

分步演示:从堆序的弱不变量到完全排序真实要付出的代价

每一步都是本文上面用真实的heapq模块跑出来的原始结果。

heapify只保证堆序,不保证全排序
这一步新验证的不变量

    本文用到的工具

    • 本机 Python 3.14.7,标准库 heapq
    京ICP备2026067502号-1京公网安备11010502063487号