前言
在 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 接近 n,Heap 会优先采用 Floyd 构堆法,复杂度 O(n + k);否则逐一插入 O(k log n) 。
- 值语义 + 写时复制(COW)
Heap 使用值语义,采用 Swift 的写时复制机制:当实例唯一时,原地变更;否则先复制后修改,与 Array、Set 等容器行为相同。
- 不稳定、不保证 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:保证唯一性和顺序但不支持优先弹出;