Skip to content

Commit e30467b

Browse files
committed
Improvements to Heap
- renamed "elements" to "nodes" to clarify the difference between array and heap. This also unifies code and comments, which were sometimes referring to the array as "nodes". - made nodes publicly readable but only settable within the heap itself. To change the nodes/operate on the heap, the provided functions should be used, to guarantee the heaps structure. - renamed isOrderedBefore to orderCriteria. The variable name isOrderedBefore did not clearly explain what the variable is for/what the closure stored within does - added method to get node at a specific index - replace method should not force the replaced value to be higher/lower than its parent. Instead replace should remove the node to be replaced and insert the given value as a new one in order to preserve the heap's structure. Asserting crashes the whole program. - added @discardableResult to removeAt. If remove is @discardableResult removeAt should be as well in for consistency. - replaced while true in shiftDown with a recursive call. while true is bad programming style. - simplified indexOf method - added a removeNode method for heaps with equatable data type - improved, adjusted and added descriptions - made functions private where fileprivate was not needed - added tests for new functions and tested everything - updated README where necessary (to conform to new code, only a few changes)
1 parent b88a4f6 commit e30467b

File tree

6 files changed

+172
-140
lines changed

6 files changed

+172
-140
lines changed

Heap/Heap.swift

100644100755
Lines changed: 95 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -4,58 +4,55 @@
44
//
55

66
public struct Heap<T> {
7+
78
/** The array that stores the heap's nodes. */
8-
var elements = [T]()
9+
private(set) 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
35+
self.orderCriteria = sort
2936
buildHeap(fromArray: array)
3037
}
3138

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-
*/
41-
4239
/**
43-
* Converts an array to a max-heap or min-heap in a bottom-up manner.
40+
* Creates 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 buildHeap(fromArray 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
/**
@@ -89,69 +86,79 @@ public struct Heap<T> {
8986
* value (for a min-heap).
9087
*/
9188
public func peek() -> T? {
92-
return elements.first
89+
return nodes.first
90+
}
91+
92+
/** Returns the node at given index */
93+
public func node(at i: Int) -> T? {
94+
guard i < nodes.count else { return nil }
95+
return nodes[i]
9396
}
9497

9598
/**
9699
* Adds a new value to the heap. This reorders the heap so that the max-heap
97100
* or min-heap property still holds. Performance: O(log n).
98101
*/
99102
public mutating func insert(_ value: T) {
100-
elements.append(value)
101-
shiftUp(elements.count - 1)
103+
nodes.append(value)
104+
shiftUp(nodes.count - 1)
102105
}
103106

107+
/**
108+
* Adds a sequence of values to the heap. This reorders the heap so that
109+
* the max-heap or min-heap property still holds. Performance: O(log n).
110+
*/
104111
public mutating func insert<S: Sequence>(_ sequence: S) where S.Iterator.Element == T {
105112
for value in sequence {
106113
insert(value)
107114
}
108115
}
109116

110117
/**
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.
118+
* Allows you to change an element. This reorders the heap so that
119+
* the max-heap or min-heap property still holds.
113120
*/
114121
public mutating func replace(index i: Int, value: T) {
115-
guard i < elements.count else { return }
122+
guard i < nodes.count else { return }
116123

117-
assert(isOrderedBefore(value, elements[i]))
118-
elements[i] = value
119-
shiftUp(i)
124+
removeAt(i)
125+
insert(value)
120126
}
121127

122128
/**
123129
* Removes the root node from the heap. For a max-heap, this is the maximum
124130
* value; for a min-heap it is the minimum value. Performance: O(log n).
125131
*/
126132
@discardableResult public mutating func remove() -> T? {
127-
if elements.isEmpty {
128-
return nil
129-
} else if elements.count == 1 {
130-
return elements.removeLast()
131-
} else {
132-
// Use the last node to replace the first one, then fix the heap by
133-
// shifting this new first node into its proper position.
134-
let value = elements[0]
135-
elements[0] = elements.removeLast()
136-
shiftDown()
137-
return value
133+
if !nodes.isEmpty {
134+
if nodes.count == 1 {
135+
return nodes.removeLast()
136+
} else {
137+
// Use the last node to replace the first one, then fix the heap by
138+
// shifting this new first node into its proper position.
139+
let value = nodes[0]
140+
nodes[0] = nodes.removeLast()
141+
shiftDown(0)
142+
return value
143+
}
138144
}
145+
return nil
139146
}
140147

141148
/**
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.
149+
* Removes an arbitrary node from the heap. Performance: O(log n).
150+
* Note that you need to know the node's index.
144151
*/
145-
public mutating func removeAt(_ index: Int) -> T? {
146-
guard index < elements.count else { return nil }
152+
@discardableResult public mutating func removeAt(_ index: Int) -> T? {
153+
guard index < nodes.count else { return nil }
147154

148-
let size = elements.count - 1
155+
let size = nodes.count - 1
149156
if index != size {
150-
elements.swapAt(index, size)
151-
shiftDown(index, heapSize: size)
157+
nodes.swapAt(index, size)
158+
shiftDown(from: index, until: size)
152159
shiftUp(index)
153160
}
154-
return elements.removeLast()
161+
return nodes.removeLast()
155162
}
156163

157164
/**
@@ -160,68 +167,64 @@ public struct Heap<T> {
160167
*/
161168
mutating func shiftUp(_ index: Int) {
162169
var childIndex = index
163-
let child = elements[childIndex]
170+
let child = nodes[childIndex]
164171
var parentIndex = self.parentIndex(ofIndex: childIndex)
165172

166-
while childIndex > 0 && isOrderedBefore(child, elements[parentIndex]) {
167-
elements[childIndex] = elements[parentIndex]
173+
while childIndex > 0 && orderCriteria(child, nodes[parentIndex]) {
174+
nodes[childIndex] = nodes[parentIndex]
168175
childIndex = parentIndex
169176
parentIndex = self.parentIndex(ofIndex: childIndex)
170177
}
171178

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

179182
/**
180183
* Looks at a parent node and makes sure it is still larger (max-heap) or
181184
* smaller (min-heap) than its childeren.
182185
*/
183-
mutating func shiftDown(_ index: Int, heapSize: Int) {
184-
var parentIndex = index
186+
private mutating func shiftDown(from index: Int, until endIndex: Int) {
187+
let leftChildIndex = self.leftChildIndex(ofIndex: index)
188+
let rightChildIndex = leftChildIndex + 1
185189

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
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 = index
195+
if leftChildIndex < endIndex && orderCriteria(nodes[leftChildIndex], nodes[first]) {
196+
first = leftChildIndex
197+
}
198+
if rightChildIndex < endIndex && orderCriteria(nodes[rightChildIndex], nodes[first]) {
199+
first = rightChildIndex
205200
}
201+
if first == index { return }
202+
203+
nodes.swapAt(index, first)
204+
shiftDown(from: first, until: endIndex)
205+
}
206+
207+
private mutating func shiftDown(_ index: Int) {
208+
shiftDown(from: index, until: nodes.count)
206209
}
210+
207211
}
208212

209213
// MARK: - Searching
210214

211215
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)
216+
217+
/** Get the index of a node in the heap. Performance: O(n). */
218+
public func index(of node: T) -> Int? {
219+
return nodes.index(where: { $0 == node })
217220
}
218221

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 }
222+
/** Removes the first occurrence of a node from the heap. Performance: O(n log n). */
223+
@discardableResult public mutating func remove(node: T) -> T? {
224+
if let index = index(of: node) {
225+
return removeAt(index)
226+
}
225227
return nil
226228
}
229+
227230
}

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)