Skip to content

Commit 7cb6cf6

Browse files
authored
Merge pull request kodecocodes#685 from DXXL/master
Improvements to Heap
2 parents b88a4f6 + 39b7311 commit 7cb6cf6

File tree

7 files changed

+157
-151
lines changed

7 files changed

+157
-151
lines changed

Heap Sort/HeapSort.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
extension Heap {
22
public mutating func sort() -> [T] {
3-
for i in stride(from: (elements.count - 1), through: 1, by: -1) {
4-
elements.swapAt(0, i)
5-
shiftDown(0, heapSize: i)
3+
for i in stride(from: (nodes.count - 1), through: 1, by: -1) {
4+
nodes.swapAt(0, i)
5+
shiftDown(from: 0, until: i)
66
}
7-
return elements
7+
return nodes
88
}
99
}
1010

Heap/Heap.swift

100644100755
Lines changed: 90 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -4,65 +4,62 @@
44
//
55

66
public struct Heap<T> {
7+
78
/** The array that stores the heap's nodes. */
8-
var elements = [T]()
9+
var nodes = [T]()
910

10-
/** Determines whether this is a max-heap (>) or min-heap (<). */
11-
fileprivate var isOrderedBefore: (T, T) -> Bool
11+
/**
12+
* Determines how to compare two nodes in the heap.
13+
* Use '>' for a max-heap or '<' for a min-heap,
14+
* or provide a comparing method if the heap is made
15+
* of custom elements, for example tuples.
16+
*/
17+
private var orderCriteria: (T, T) -> Bool
1218

1319
/**
1420
* Creates an empty heap.
1521
* The sort function determines whether this is a min-heap or max-heap.
16-
* For integers, > makes a max-heap, < makes a min-heap.
22+
* For comparable data types, > makes a max-heap, < makes a min-heap.
1723
*/
1824
public init(sort: @escaping (T, T) -> Bool) {
19-
self.isOrderedBefore = sort
25+
self.orderCriteria = sort
2026
}
2127

2228
/**
2329
* Creates a heap from an array. The order of the array does not matter;
2430
* the elements are inserted into the heap in the order determined by the
25-
* sort function.
31+
* sort function. For comparable data types, '>' makes a max-heap,
32+
* '<' makes a min-heap.
2633
*/
2734
public init(array: [T], sort: @escaping (T, T) -> Bool) {
28-
self.isOrderedBefore = sort
29-
buildHeap(fromArray: array)
30-
}
31-
32-
/*
33-
// This version has O(n log n) performance.
34-
private mutating func buildHeap(array: [T]) {
35-
elements.reserveCapacity(array.count)
36-
for value in array {
37-
insert(value)
38-
}
39-
}
40-
*/
35+
self.orderCriteria = sort
36+
configureHeap(from: array)
37+
}
4138

4239
/**
43-
* Converts an array to a max-heap or min-heap in a bottom-up manner.
40+
* Configures the max-heap or min-heap from an array, in a bottom-up manner.
4441
* Performance: This runs pretty much in O(n).
4542
*/
46-
fileprivate mutating func buildHeap(fromArray array: [T]) {
47-
elements = array
48-
for i in stride(from: (elements.count/2 - 1), through: 0, by: -1) {
49-
shiftDown(i, heapSize: elements.count)
43+
private mutating func configureHeap(from array: [T]) {
44+
nodes = array
45+
for i in stride(from: (nodes.count/2-1), through: 0, by: -1) {
46+
shiftDown(i)
5047
}
5148
}
5249

5350
public var isEmpty: Bool {
54-
return elements.isEmpty
51+
return nodes.isEmpty
5552
}
5653

5754
public var count: Int {
58-
return elements.count
55+
return nodes.count
5956
}
6057

6158
/**
6259
* Returns the index of the parent of the element at index i.
6360
* The element at index 0 is the root of the tree and has no parent.
6461
*/
65-
@inline(__always) func parentIndex(ofIndex i: Int) -> Int {
62+
@inline(__always) internal func parentIndex(ofIndex i: Int) -> Int {
6663
return (i - 1) / 2
6764
}
6865

@@ -71,7 +68,7 @@ public struct Heap<T> {
7168
* Note that this index can be greater than the heap size, in which case
7269
* there is no left child.
7370
*/
74-
@inline(__always) func leftChildIndex(ofIndex i: Int) -> Int {
71+
@inline(__always) internal func leftChildIndex(ofIndex i: Int) -> Int {
7572
return 2*i + 1
7673
}
7774

@@ -80,7 +77,7 @@ public struct Heap<T> {
8077
* Note that this index can be greater than the heap size, in which case
8178
* there is no right child.
8279
*/
83-
@inline(__always) func rightChildIndex(ofIndex i: Int) -> Int {
80+
@inline(__always) internal func rightChildIndex(ofIndex i: Int) -> Int {
8481
return 2*i + 2
8582
}
8683

@@ -89,139 +86,138 @@ public struct Heap<T> {
8986
* value (for a min-heap).
9087
*/
9188
public func peek() -> T? {
92-
return elements.first
89+
return nodes.first
9390
}
9491

9592
/**
9693
* Adds a new value to the heap. This reorders the heap so that the max-heap
9794
* or min-heap property still holds. Performance: O(log n).
9895
*/
9996
public mutating func insert(_ value: T) {
100-
elements.append(value)
101-
shiftUp(elements.count - 1)
97+
nodes.append(value)
98+
shiftUp(nodes.count - 1)
10299
}
103100

101+
/**
102+
* Adds a sequence of values to the heap. This reorders the heap so that
103+
* the max-heap or min-heap property still holds. Performance: O(log n).
104+
*/
104105
public mutating func insert<S: Sequence>(_ sequence: S) where S.Iterator.Element == T {
105106
for value in sequence {
106107
insert(value)
107108
}
108109
}
109110

110111
/**
111-
* Allows you to change an element. In a max-heap, the new element should be
112-
* larger than the old one; in a min-heap it should be smaller.
112+
* Allows you to change an element. This reorders the heap so that
113+
* the max-heap or min-heap property still holds.
113114
*/
114115
public mutating func replace(index i: Int, value: T) {
115-
guard i < elements.count else { return }
116+
guard i < nodes.count else { return }
116117

117-
assert(isOrderedBefore(value, elements[i]))
118-
elements[i] = value
119-
shiftUp(i)
118+
remove(at: i)
119+
insert(value)
120120
}
121121

122122
/**
123123
* Removes the root node from the heap. For a max-heap, this is the maximum
124124
* value; for a min-heap it is the minimum value. Performance: O(log n).
125125
*/
126126
@discardableResult public mutating func remove() -> T? {
127-
if elements.isEmpty {
128-
return nil
129-
} else if elements.count == 1 {
130-
return elements.removeLast()
127+
guard !nodes.isEmpty else { return nil }
128+
129+
if nodes.count == 1 {
130+
return nodes.removeLast()
131131
} else {
132132
// Use the last node to replace the first one, then fix the heap by
133133
// shifting this new first node into its proper position.
134-
let value = elements[0]
135-
elements[0] = elements.removeLast()
136-
shiftDown()
134+
let value = nodes[0]
135+
nodes[0] = nodes.removeLast()
136+
shiftDown(0)
137137
return value
138138
}
139139
}
140140

141141
/**
142-
* Removes an arbitrary node from the heap. Performance: O(log n). You need
143-
* to know the node's index, which may actually take O(n) steps to find.
142+
* Removes an arbitrary node from the heap. Performance: O(log n).
143+
* Note that you need to know the node's index.
144144
*/
145-
public mutating func removeAt(_ index: Int) -> T? {
146-
guard index < elements.count else { return nil }
145+
@discardableResult public mutating func remove(at index: Int) -> T? {
146+
guard index < nodes.count else { return nil }
147147

148-
let size = elements.count - 1
148+
let size = nodes.count - 1
149149
if index != size {
150-
elements.swapAt(index, size)
151-
shiftDown(index, heapSize: size)
150+
nodes.swapAt(index, size)
151+
shiftDown(from: index, until: size)
152152
shiftUp(index)
153153
}
154-
return elements.removeLast()
154+
return nodes.removeLast()
155155
}
156156

157157
/**
158158
* Takes a child node and looks at its parents; if a parent is not larger
159159
* (max-heap) or not smaller (min-heap) than the child, we exchange them.
160160
*/
161-
mutating func shiftUp(_ index: Int) {
161+
internal mutating func shiftUp(_ index: Int) {
162162
var childIndex = index
163-
let child = elements[childIndex]
163+
let child = nodes[childIndex]
164164
var parentIndex = self.parentIndex(ofIndex: childIndex)
165165

166-
while childIndex > 0 && isOrderedBefore(child, elements[parentIndex]) {
167-
elements[childIndex] = elements[parentIndex]
166+
while childIndex > 0 && orderCriteria(child, nodes[parentIndex]) {
167+
nodes[childIndex] = nodes[parentIndex]
168168
childIndex = parentIndex
169169
parentIndex = self.parentIndex(ofIndex: childIndex)
170170
}
171171

172-
elements[childIndex] = child
173-
}
174-
175-
mutating func shiftDown() {
176-
shiftDown(0, heapSize: elements.count)
172+
nodes[childIndex] = child
177173
}
178174

179175
/**
180176
* Looks at a parent node and makes sure it is still larger (max-heap) or
181177
* smaller (min-heap) than its childeren.
182178
*/
183-
mutating func shiftDown(_ index: Int, heapSize: Int) {
184-
var parentIndex = index
179+
internal mutating func shiftDown(from index: Int, until endIndex: Int) {
180+
let leftChildIndex = self.leftChildIndex(ofIndex: index)
181+
let rightChildIndex = leftChildIndex + 1
185182

186-
while true {
187-
let leftChildIndex = self.leftChildIndex(ofIndex: parentIndex)
188-
let rightChildIndex = leftChildIndex + 1
189-
190-
// Figure out which comes first if we order them by the sort function:
191-
// the parent, the left child, or the right child. If the parent comes
192-
// first, we're done. If not, that element is out-of-place and we make
193-
// it "float down" the tree until the heap property is restored.
194-
var first = parentIndex
195-
if leftChildIndex < heapSize && isOrderedBefore(elements[leftChildIndex], elements[first]) {
196-
first = leftChildIndex
197-
}
198-
if rightChildIndex < heapSize && isOrderedBefore(elements[rightChildIndex], elements[first]) {
199-
first = rightChildIndex
200-
}
201-
if first == parentIndex { return }
202-
203-
elements.swapAt(parentIndex, first)
204-
parentIndex = first
183+
// Figure out which comes first if we order them by the sort function:
184+
// the parent, the left child, or the right child. If the parent comes
185+
// first, we're done. If not, that element is out-of-place and we make
186+
// it "float down" the tree until the heap property is restored.
187+
var first = index
188+
if leftChildIndex < endIndex && orderCriteria(nodes[leftChildIndex], nodes[first]) {
189+
first = leftChildIndex
205190
}
191+
if rightChildIndex < endIndex && orderCriteria(nodes[rightChildIndex], nodes[first]) {
192+
first = rightChildIndex
193+
}
194+
if first == index { return }
195+
196+
nodes.swapAt(index, first)
197+
shiftDown(from: first, until: endIndex)
206198
}
199+
200+
internal mutating func shiftDown(_ index: Int) {
201+
shiftDown(from: index, until: nodes.count)
202+
}
203+
207204
}
208205

209206
// MARK: - Searching
210207

211208
extension Heap where T: Equatable {
212-
/**
213-
* Searches the heap for the given element. Performance: O(n).
214-
*/
215-
public func index(of element: T) -> Int? {
216-
return index(of: element, 0)
209+
210+
/** Get the index of a node in the heap. Performance: O(n). */
211+
public func index(of node: T) -> Int? {
212+
return nodes.index(where: { $0 == node })
217213
}
218214

219-
fileprivate func index(of element: T, _ i: Int) -> Int? {
220-
if i >= count { return nil }
221-
if isOrderedBefore(element, elements[i]) { return nil }
222-
if element == elements[i] { return i }
223-
if let j = index(of: element, self.leftChildIndex(ofIndex: i)) { return j }
224-
if let j = index(of: element, self.rightChildIndex(ofIndex: i)) { return j }
215+
/** Removes the first occurrence of a node from the heap. Performance: O(n log n). */
216+
@discardableResult public mutating func remove(node: T) -> T? {
217+
if let index = index(of: node) {
218+
return remove(at: index)
219+
}
225220
return nil
226221
}
222+
227223
}

Heap/README.markdown

100644100755
Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ A heap is not a replacement for a binary search tree, and there are similarities
3838

3939
**Balancing.** A binary search tree must be "balanced" so that most operations have **O(log n)** performance. You can either insert and delete your data in a random order or use something like an [AVL tree](../AVL%20Tree/) or [red-black tree](../Red-Black%20Tree/), but with heaps we don't actually need the entire tree to be sorted. We just want the heap property to be fulfilled, so balancing isn't an issue. Because of the way the heap is structured, heaps can guarantee **O(log n)** performance.
4040

41-
**Searching.** Whereas searching is fast in a binary tree, it is slow in a heap. Searching isn't a top priority in a heap since the purpose of a heap is to put the largest (or smallest) node at the front and to allow relatively fast inserts and deletes.
41+
**Searching.** Whereas searching is fast in a binary tree, it is slow in a heap. Searching isn't a top priority in a heap since the purpose of a heap is to put the largest (or smallest) node at the front and to allow relatively fast inserts and deletes.
4242

4343
## The tree inside an array
4444

@@ -148,7 +148,7 @@ There are two primitive operations necessary to make sure the heap is a valid ma
148148

149149
Shifting up or down is a recursive procedure that takes **O(log n)** time.
150150

151-
Here are other operations that are built on primitive operations:
151+
Here are other operations that are built on primitive operations:
152152

153153
- `insert(value)`: Adds the new element to the end of the heap and then uses `shiftUp()` to fix the heap.
154154

@@ -190,7 +190,7 @@ The `(16)` was added to the first available space on the last row.
190190

191191
Unfortunately, the heap property is no longer satisfied because `(2)` is above `(16)`, and we want higher numbers above lower numbers. (This is a max-heap.)
192192

193-
To restore the heap property, we swap `(16)` and `(2)`.
193+
To restore the heap property, we swap `(16)` and `(2)`.
194194

195195
![The heap before insertion](Images/Insert2.png)
196196

@@ -214,7 +214,7 @@ What happens to the empty spot at the top?
214214

215215
![The root is gone](Images/Remove1.png)
216216

217-
When inserting, we put the new value at the end of the array. Here, we do the opposite: we take the last object we have, stick it up on top of the tree, and restore the heap property.
217+
When inserting, we put the new value at the end of the array. Here, we do the opposite: we take the last object we have, stick it up on top of the tree, and restore the heap property.
218218

219219
![The last node goes to the root](Images/Remove2.png)
220220

@@ -226,7 +226,7 @@ Keep shifting down until the node does not have any children or it is larger tha
226226

227227
![The last node goes to the root](Images/Remove4.png)
228228

229-
The time required for shifting all the way down is proportional to the height of the tree which takes **O(log n)** time.
229+
The time required for shifting all the way down is proportional to the height of the tree which takes **O(log n)** time.
230230

231231
> **Note:** `shiftUp()` and `shiftDown()` can only fix one out-of-place element at a time. If there are multiple elements in the wrong place, you need to call these functions once for each of those elements.
232232
@@ -279,7 +279,7 @@ In code:
279279
```swift
280280
private mutating func buildHeap(fromArray array: [T]) {
281281
elements = array
282-
for i in (elements.count/2 - 1).stride(through: 0, by: -1) {
282+
for i in stride(from: (nodes.count/2-1), through: 0, by: -1) {
283283
shiftDown(index: i, heapSize: elements.count)
284284
}
285285
}

0 commit comments

Comments
 (0)