如何在 Swift 中使用最小堆实现 PriorityQueue?
How to implement PriorityQueue using min-heap in Swift?
首先,Swift标准库中有没有内置类似"PriorityQueue"的class?找不到了,就自己写了。
当我从队列中弹出项目时,我希望该项目目前是最小的。对于大多数项目来说,这是正确的,但其中一些(很少)没有放在正确的位置,我似乎无法找出原因。可以在 Playground 和 运行(包括测试)中复制粘贴以下代码片段。
class PriorityQueue<T> {
private var arr: [T] = []
private let compare: (_ a: T, _ b: T) -> Bool
init(_ compare: @escaping (_ a: T, _ b: T) -> Bool) {
self.compare = compare
}
func insert(_ a: T) {
arr.insert(a, at: 0)
heapify(0)
}
func pop() -> T? {
if arr.isEmpty {
return nil
}
let res: T = arr.removeFirst()
if !arr.isEmpty {
arr.insert(arr.removeLast(), at: 0)
heapify(0)
}
return res
}
private func heapify(_ i: Int) {
let l: Int = 2 * i + 1, r: Int = 2 * i + 2
var smallest: Int = i
if l < arr.count, compare(arr[l], arr[smallest]) {
smallest = l
}
if r < arr.count, compare(arr[r], arr[smallest]) {
smallest = r
}
if smallest == i {
return
}
let temp: T = arr[i]
arr[i] = arr[smallest]
arr[smallest] = temp
heapify(smallest)
}
}
// Test case
let size: Int = 15
var arr: [Int] = Array(repeating: 0, count: size)
let queue: PriorityQueue<Int> = PriorityQueue { [=11=] < }
for i in 0..<size {
arr[i] = Int.random(in: 1...10000)
queue.insert(arr[i])
}
let sorted: [Int] = arr.sorted()
for i in 0..<size {
let n: Int = queue.pop() ?? 0
print(n, sorted[i], n == sorted[i])
}
95% 的时间,项目以正确的顺序从队列中弹出,但偶尔,我会得到这样的结果:
579 579 真
1372 1372 真
1762 1762 真
2113 2113 真
2332 2332 真
2562 2418 假
2701 2562 假
3137 2701 假
2418 3137 假
4615 4615 真
6085 6085 真
6820 6820 真
7382 7382 真
8878 8878 真
9220 9220 真
我刚刚弄清楚为什么我错了,错误发生在 func insert(_ a: T)
,我只是在前面加上新元素,然后尝试从第一个元素堆化数组。
函数应该重写:
func insert(_ a: T) {
arr.append(a)
for i in stride(from: arr.count / 2 - 1, through: 0, by: -1) {
heapify(i)
}
}
但是,这不是很有效,因为我们正在破坏整个堆化数组并重新堆化大部分堆化的内容。因此,我不是在元素前面添加元素,而是附加它,这样,我们最多会破坏堆的一个子树。从那里,我们尝试从下到上堆化数组:
private func heapifyUp(_ i: Int) {
if i == 0 {
return
}
let parent: Int, sibling: Int
if i.isMultiple(of: 2) {
parent = (i - 2) / 2
sibling = parent * 2 + 1
} else {
parent = (i - 1) / 2
sibling = parent * 2 + 2
}
var smallest: Int = parent
if compare(arr[i], arr[smallest]) {
smallest = i
}
if sibling < arr.count, compare(arr[sibling], arr[smallest]) {
smallest = sibling
}
if smallest == parent {
return
}
let temp: T = arr[parent]
arr[parent] = arr[smallest]
arr[smallest] = temp
heapifyUp(parent)
heapify(smallest)
}
我犯的错误是我假设在我添加一个元素之前,数组的其余部分(除了新元素)仍然是堆化的,这是不正确的。考虑这种情况:
您有一个堆化数组:[5, 8, 6, 9, 12, 7],当您添加一个新元素 10 并调用 heapify(0)
时,数组变为:[5, 6, 8 , 10, 9, 12, 7],其中8是12和7的父级,但8不小于7.
向二叉堆中添加一个项目非常简单。您将该项目追加到数组的末尾,并将其从堆中冒泡到适当的位置。我不知道Swift,所以我给你伪代码。假设支持数组(或列表)被称为 arr
:
insert (val)
append val to arr
i = arr.count - 1
while (i > 0)
parent = (i-1)/2
if (arr[i] < arr[parent])
swap(arr[i], arr[parent])
i = parent
else
break
我会考虑这样写这个 heapifyUp:
mutating private func heapifyUp(_ index: Int) {
var childIndex = index
let child = items[childIndex]
var parentIndex = getParentIndex(childIndex)
while childIndex > 0 && orderCriteria(child, items[parentIndex]) {
items[childIndex] = items[parentIndex]
childIndex = parentIndex
parentIndex = getParentIndex(childIndex)
}
items[childIndex] = child
}
orderCriteria 就是:
private var orderCriteria: (T, T) -> Bool
您可以这样初始化您的堆:
public init(sort: @escaping (T, T) -> Bool) {
self.orderCriteria = sort
}
这样您就可以通过定义 orderCriteria 将其用作 MinHeap 或 MaxHeap。
您的 insert/add 函数变为:
mutating public func add(_ item: T) {
items.append(item)
heapifyUp(items.count - 1)
}
希望对您有所帮助!我认为您的解决方案也非常相似。请注意,我使用的 items
是您的 arr
并且您的 heapify() 函数似乎也可以正常工作,但我的是迭代版本而您的是递归版本。老实说,虽然我没有测试过你的。
首先,Swift标准库中有没有内置类似"PriorityQueue"的class?找不到了,就自己写了。
当我从队列中弹出项目时,我希望该项目目前是最小的。对于大多数项目来说,这是正确的,但其中一些(很少)没有放在正确的位置,我似乎无法找出原因。可以在 Playground 和 运行(包括测试)中复制粘贴以下代码片段。
class PriorityQueue<T> {
private var arr: [T] = []
private let compare: (_ a: T, _ b: T) -> Bool
init(_ compare: @escaping (_ a: T, _ b: T) -> Bool) {
self.compare = compare
}
func insert(_ a: T) {
arr.insert(a, at: 0)
heapify(0)
}
func pop() -> T? {
if arr.isEmpty {
return nil
}
let res: T = arr.removeFirst()
if !arr.isEmpty {
arr.insert(arr.removeLast(), at: 0)
heapify(0)
}
return res
}
private func heapify(_ i: Int) {
let l: Int = 2 * i + 1, r: Int = 2 * i + 2
var smallest: Int = i
if l < arr.count, compare(arr[l], arr[smallest]) {
smallest = l
}
if r < arr.count, compare(arr[r], arr[smallest]) {
smallest = r
}
if smallest == i {
return
}
let temp: T = arr[i]
arr[i] = arr[smallest]
arr[smallest] = temp
heapify(smallest)
}
}
// Test case
let size: Int = 15
var arr: [Int] = Array(repeating: 0, count: size)
let queue: PriorityQueue<Int> = PriorityQueue { [=11=] < }
for i in 0..<size {
arr[i] = Int.random(in: 1...10000)
queue.insert(arr[i])
}
let sorted: [Int] = arr.sorted()
for i in 0..<size {
let n: Int = queue.pop() ?? 0
print(n, sorted[i], n == sorted[i])
}
95% 的时间,项目以正确的顺序从队列中弹出,但偶尔,我会得到这样的结果:
579 579 真
1372 1372 真
1762 1762 真
2113 2113 真
2332 2332 真
2562 2418 假
2701 2562 假
3137 2701 假
2418 3137 假
4615 4615 真
6085 6085 真
6820 6820 真
7382 7382 真
8878 8878 真
9220 9220 真
我刚刚弄清楚为什么我错了,错误发生在 func insert(_ a: T)
,我只是在前面加上新元素,然后尝试从第一个元素堆化数组。
函数应该重写:
func insert(_ a: T) {
arr.append(a)
for i in stride(from: arr.count / 2 - 1, through: 0, by: -1) {
heapify(i)
}
}
但是,这不是很有效,因为我们正在破坏整个堆化数组并重新堆化大部分堆化的内容。因此,我不是在元素前面添加元素,而是附加它,这样,我们最多会破坏堆的一个子树。从那里,我们尝试从下到上堆化数组:
private func heapifyUp(_ i: Int) {
if i == 0 {
return
}
let parent: Int, sibling: Int
if i.isMultiple(of: 2) {
parent = (i - 2) / 2
sibling = parent * 2 + 1
} else {
parent = (i - 1) / 2
sibling = parent * 2 + 2
}
var smallest: Int = parent
if compare(arr[i], arr[smallest]) {
smallest = i
}
if sibling < arr.count, compare(arr[sibling], arr[smallest]) {
smallest = sibling
}
if smallest == parent {
return
}
let temp: T = arr[parent]
arr[parent] = arr[smallest]
arr[smallest] = temp
heapifyUp(parent)
heapify(smallest)
}
我犯的错误是我假设在我添加一个元素之前,数组的其余部分(除了新元素)仍然是堆化的,这是不正确的。考虑这种情况:
您有一个堆化数组:[5, 8, 6, 9, 12, 7],当您添加一个新元素 10 并调用 heapify(0)
时,数组变为:[5, 6, 8 , 10, 9, 12, 7],其中8是12和7的父级,但8不小于7.
向二叉堆中添加一个项目非常简单。您将该项目追加到数组的末尾,并将其从堆中冒泡到适当的位置。我不知道Swift,所以我给你伪代码。假设支持数组(或列表)被称为 arr
:
insert (val)
append val to arr
i = arr.count - 1
while (i > 0)
parent = (i-1)/2
if (arr[i] < arr[parent])
swap(arr[i], arr[parent])
i = parent
else
break
我会考虑这样写这个 heapifyUp:
mutating private func heapifyUp(_ index: Int) {
var childIndex = index
let child = items[childIndex]
var parentIndex = getParentIndex(childIndex)
while childIndex > 0 && orderCriteria(child, items[parentIndex]) {
items[childIndex] = items[parentIndex]
childIndex = parentIndex
parentIndex = getParentIndex(childIndex)
}
items[childIndex] = child
}
orderCriteria 就是:
private var orderCriteria: (T, T) -> Bool
您可以这样初始化您的堆:
public init(sort: @escaping (T, T) -> Bool) {
self.orderCriteria = sort
}
这样您就可以通过定义 orderCriteria 将其用作 MinHeap 或 MaxHeap。
您的 insert/add 函数变为:
mutating public func add(_ item: T) {
items.append(item)
heapifyUp(items.count - 1)
}
希望对您有所帮助!我认为您的解决方案也非常相似。请注意,我使用的 items
是您的 arr
并且您的 heapify() 函数似乎也可以正常工作,但我的是迭代版本而您的是递归版本。老实说,虽然我没有测试过你的。