Swift Collections:Heap 的使用

227 阅读4分钟

前言

在 Swift Collections 中,Heap 是一个提供双端优先队列功能的泛型容器类型,位于 HeapModule 模块内。它通过堆(heap)的数据结构实现,可以高效地同时支持获取和删除最小值/最大值操作。这种设计类型在许多算法中非常常用,比如调度、路径查找、优先任务处理等。

我们需要注意的是:对于要存储在 Heap 元素类型必须要符合 Comparable 协议,因为该结构需要通过该协议定义元素的排序关系,从而确保堆结构的正确性和稳定性。

API 概览

API 整体清晰、简洁:

init()
init(_ elements: some Sequence<Element>)

var count: Int { get }
var isEmpty: Bool { get }
var unordered: [Element] { get }
var min: Element? { get }
var max: Element? { get }

mutating func insert(_ element: Element)
mutating func insert(contentsOf newElements: some Sequence<Element>)

mutating func popMax() -> Element?
mutating func popMin() -> Element?

mutating func removeMax() -> Element
mutating func removeMin() -> Element

mutating func replaceMax(with replacement: Element) -> Element
mutating func replaceMin(with replacement: Element) -> Element
mutating func reserveCapacity(_ minimumCapacity: Int)
  • 构造器

    • init():创建空堆。
    • init(_:):使用一个 Sequence 创建堆。
  • 属性

    • count, isEmpty:基础计数属性。
    • unordered:返回底层数组的内容,顺序不会保证排序,但对调试或遍历有帮助 。
  • 查询

    • min(), max():返回当前最小/最大值,时间复杂度 O(1)。
  • 插入与删除

    • 插入单个元素(insert(_ element: Element)):O(log n);
    • 批量插入(insert(contentsOf newElements: some Sequence<Element>)):当批量规模较大时,会根据 n + k 的复杂度选择最优方式,即整体建堆或多次插入方式;
    • 删除最小/最大值:分别是 popMin() / popMax(),O(log n);
    • 强制移除(非可选性):removeMin() / removeMax()
    • 替换操作:replaceMin(with:), replaceMax(with:),将头部元素替换为新值并调整堆,返回原来的值。

核心特性

  • 支持双端操作

Heap 同时维护最小堆和最大堆两端功能,无需额外结构,这对于一些需求获取最两个极端值的场景非常高效。

  • O(log n) 插入和删除

这是经典堆性能的体现,插入或删除单个元素仅需调整路径高度次数即可。

  • 批量插入优化

对于批量插入 k 个元素,若 k 接近 nHeap 会优先采用 Floyd 构堆法,复杂度 O(n + k);否则逐一插入 O(k log n) 。

  • 值语义 + 写时复制(COW)

Heap 使用值语义,采用 Swift 的写时复制机制:当实例唯一时,原地变更;否则先复制后修改,与 ArraySet 等容器行为相同。

  • 不稳定、不保证 FIFO

相同值的元素会被视为相等,但出栈顺序不保证 FIFO。若需要这一特性,应主动添加辅助字段,如序号 。

  • Sequence 协议

Heap 本身不符合 Sequence/Collection,但可以通过 unordered 访问底层元素,意在提醒用户该顺序非排序状态。

使用示例

import HeapModule

var heap: Heap = [4, 5, 2, 2, 1, 3, 0, 0, 0, 2, 6]
print(heap.min!)  // 0
print(heap.max!)  // 6

heap.insert(7)
print(heap.max!)  // 7

while let x = heap.popMin() {
    print(x)
}
// 输出:0 0 0 1 2 2 2 3 4 5 6 7

上述示例展示了最基本的使用方式,包括批量建堆、插入、获取极值和弹出元素。

自定义用法与扩展

如果元素本身不 Comparable,可以通过 extension 来实现 Comparable,如:

class Person {
    let name: String
    let priority: Int
    init(name: String, priority: Int) {
        self.name = name
        self.priority = priority
    }
}

extension Person: Comparable {
    static func < (lhs: Person, rhs: Person) -> Bool {
        lhs.priority < rhs.priority
    }
    
    static func == (lhs: Person, rhs: Person) -> Bool {
        lhs.priority == rhs.priority
    }
}

比如上面的代码,Person 如何没有实现 Comparable 协议,下面的代码会编译报错:

var p1 = Person(name: "jack1", priority: 1)
var p2 = Person(name: "rose", priority: 2)
var p3 = Person(name: "jackson", priority: 3)
var personH: Heap<Person> = [p1, p2, p3] // 编译报错:Type 'Person' does not conform to protocol 'Comparable'

性能分析

  • 创建:O(n),优于 O(n log n) 的重复插入。
  • 插入弹出:O(log n),性能稳定。
  • 堆顶查询:O(1)。
  • 批量操作:根据批次规模,端到端优化效果明显。
  • 写时复制开销:COW 可以避免无意义复制,在 Swift 常见容器中适配良好。

与其他 Collection 类型比较

  • 对比 Array.sorted():适合一次性排序;若要动态插入,效率退化。
  • 对比 Set:无排序语义;
  • 对比 Deque:双端队列但无优先级;
  • 对比 OrderedSet:保证唯一性和顺序但不支持优先弹出;