Skip to content

Commit dd986a4

Browse files
committed
Merge pull request kodecocodes#4 from kevinrandrup/master
Added Heap
2 parents 404c896 + 72fd4fb commit dd986a4

File tree

4 files changed

+387
-1
lines changed

4 files changed

+387
-1
lines changed

Heap/Heap.swift

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
//
2+
// Heap.swift
3+
// Written for the Swift Algorithm Club by Kevin Randrup
4+
//
5+
6+
/**
7+
* A heap is a type of tree data structure with 2 charactersitics:
8+
* 1. Parent nodes are either greater or less than each one of their children (called max heaps and min heaps respectively)
9+
* 2. Only the top item is accessible (greatest or smallest)
10+
*
11+
* This results in a data structure that stores n items in O(n) space. Both insertion and deletion take O(log(n)) time (amortized).
12+
*/
13+
protocol Heap {
14+
typealias Value
15+
mutating func insert(value: Value)
16+
mutating func remove() -> Value?
17+
var count: Int { get }
18+
var isEmpty: Bool { get }
19+
}
20+
21+
/**
22+
* A MaxHeap stores the highest items at the top. Calling remove() will return the highest item in the heap.
23+
*/
24+
public struct MaxHeap<T : Comparable> : Heap {
25+
26+
typealias Value = T
27+
28+
/** 10
29+
* 7 5
30+
* 1 2 3
31+
* Will be represented as [10, 7, 5, 1, 2, 3]
32+
*/
33+
private var mem: [T]
34+
35+
init() {
36+
mem = [T]()
37+
}
38+
39+
init(array: [T]) {
40+
self.init()
41+
//This could be optimized into O(n) time using the Floyd algorithm instead of O(nlog(n))
42+
mem.reserveCapacity(array.count)
43+
for value in array {
44+
insert(value)
45+
}
46+
}
47+
48+
public var isEmpty: Bool {
49+
return mem.isEmpty
50+
}
51+
52+
public var count: Int {
53+
return mem.count
54+
}
55+
56+
/**
57+
* Inserts the value into the Heap in O(log(n)) time
58+
*/
59+
public mutating func insert(value: T) {
60+
mem.append(value)
61+
shiftUp(index: mem.count - 1)
62+
}
63+
64+
public mutating func insert<S : SequenceType where S.Generator.Element == T>(sequence: S) {
65+
for value in sequence {
66+
insert(value)
67+
}
68+
}
69+
70+
/**
71+
* Removes the max value from the heap in O(logn)
72+
*/
73+
public mutating func remove() -> T? {
74+
//Handle empty/1 element cases.
75+
if mem.isEmpty {
76+
return nil
77+
}
78+
else if mem.count == 1 {
79+
return mem.removeLast()
80+
}
81+
82+
83+
// Pull the last element up to replace the first one
84+
let value = mem[0]
85+
let last = mem.removeLast()
86+
mem[0] = last
87+
88+
//Downshift the new top value
89+
shiftDown()
90+
91+
return value
92+
}
93+
94+
//MARK: Private implmentation
95+
96+
/**
97+
* Returns the parent's index given the child's index.
98+
* 1,2 -> 0
99+
* 3,4 -> 1
100+
* 5,6 -> 2
101+
* 7,8 -> 3
102+
*/
103+
private func parentIndex(childIndex childIndex: Int) -> Int {
104+
return (childIndex - 1) / 2
105+
}
106+
107+
private func firstChildIndex(index: Int) -> Int {
108+
return index * 2 + 1
109+
}
110+
111+
@inline(__always) private func validIndex(index: Int) -> Bool {
112+
return index < mem.endIndex
113+
}
114+
115+
/**
116+
* Restore the heap property above a given index.
117+
*/
118+
private mutating func shiftUp(index index: Int) {
119+
var childIndex = index
120+
let child = mem[childIndex]
121+
while childIndex != 0 {
122+
let parentIdx = parentIndex(childIndex: childIndex)
123+
let parent = mem[parentIdx]
124+
//If the child doesn't need to be swapped up, return
125+
if child <= parent {
126+
return
127+
}
128+
//Otherwise, swap the child up the tree
129+
mem[parentIdx] = child
130+
mem[childIndex] = parent
131+
132+
//Update childIdx
133+
childIndex = parentIdx
134+
}
135+
}
136+
137+
/**
138+
* Maintains the heap property of parent > both children
139+
*/
140+
private mutating func shiftDown(index index: Int = 0) {
141+
var parentIndex = index
142+
var leftChildIndex = firstChildIndex(parentIndex)
143+
144+
//Loop preconditions: parentIndex and left child index are set
145+
while (validIndex(leftChildIndex)) {
146+
let rightChildIndex = leftChildIndex + 1
147+
let highestIndex: Int
148+
149+
//If we have valid right and left indexes, choose the highest one
150+
if (validIndex(rightChildIndex)) {
151+
let left = mem[leftChildIndex]
152+
let right = mem[rightChildIndex]
153+
highestIndex = (left > right) ? leftChildIndex : rightChildIndex
154+
} else {
155+
highestIndex = leftChildIndex
156+
}
157+
158+
//If the child > parent, swap them
159+
let parent = mem[parentIndex]
160+
let highestChild = mem[highestIndex]
161+
if highestChild <= parent { return }
162+
163+
mem[parentIndex] = highestChild
164+
mem[highestIndex] = parent
165+
166+
//Set the loop preconditions
167+
parentIndex = highestIndex
168+
leftChildIndex = firstChildIndex(parentIndex)
169+
}
170+
}
171+
}

Heap/HeapTests.swift

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
//
2+
// HeapTests.swift
3+
// Written for the Swift Algorithm Club by Kevin Randrup
4+
//
5+
6+
import XCTest
7+
@testable import Test
8+
9+
class HeapTests: XCTestCase {
10+
11+
func testIsEmpty() {
12+
var heap = MaxHeap<Int>()
13+
XCTAssertTrue(heap.isEmpty)
14+
heap.insert(1)
15+
XCTAssertFalse(heap.isEmpty)
16+
heap.remove()
17+
XCTAssertTrue(heap.isEmpty)
18+
}
19+
20+
func testInsertRemove() {
21+
/** 9
22+
* 7 5
23+
* 1 2 3
24+
* Should be represented in memory as [9, 5, 7, 1, 3, 2] though we are just testing the effects.
25+
*/
26+
var heap = MaxHeap<Int>()
27+
heap.insert([1, 3, 2, 7, 5, 9])
28+
29+
//Should be removed in order
30+
XCTAssertEqual(9, heap.remove())
31+
XCTAssertEqual(7, heap.remove())
32+
XCTAssertEqual(5, heap.remove())
33+
XCTAssertEqual(3, heap.remove())
34+
XCTAssertEqual(2, heap.remove())
35+
XCTAssertEqual(1, heap.remove())
36+
XCTAssertNil(heap.remove())
37+
}
38+
39+
func testCount() {
40+
var heap = MaxHeap<Int>()
41+
XCTAssertEqual(0, heap.count)
42+
heap.insert(1)
43+
XCTAssertEqual(1, heap.count)
44+
}
45+
46+
func testRemoveEmpty() {
47+
var heap = MaxHeap<Int>()
48+
let removed = heap.remove()
49+
XCTAssertNil(removed)
50+
}
51+
}

Heap/README.md

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
# Heap
2+
3+
Written for the Swift Algorithm Club by Kevin Randrup.
4+
5+
### Intro
6+
7+
A heap is a specialized type of tree with only two operations, `insert()` and `remove()`. Heaps are usually implemented using an array.
8+
9+
In Swift, a Heap protocol would look something like this:
10+
11+
```
12+
protocol Heap {
13+
typealias Value
14+
func insert(value: Value)
15+
func remove() -> Value?
16+
}
17+
```
18+
19+
### Heap Property
20+
21+
In any given heap, ether every parent node is greater than its child nodes or every parent is less than its child nodes. This "heap property" is true for every single node in the tree.
22+
23+
An example:
24+
25+
```
26+
(10)
27+
/ \
28+
(7) (2)
29+
/ \
30+
(5) (1)
31+
```
32+
The heap property for this tree is satisfied because every parent node is greater than its children node. `(10)` is greater than `(7)` and `(3)`. `(7)` is greater than `(3)` and `(1)`.
33+
34+
### So what's the problem with trees?
35+
36+
#### The balancing issue
37+
If you randomly add and remove data, trees will have `O(log(n))` performance. Unfortunatly our data ususally isn't perfectly random so tree can become unbalanced and take more than `O(log(n))` time. There are a lot of ways to combat this issue in trees (see Binary Trees, AVL tree, Red-Black Tree) but with heaps we don't actually need the entire tree to be sorted. We just want the heap property to be fullfilled.
38+
39+
#### The memory issue
40+
Trees also take up more memory than they data they store. You need to allocate memory for nodes and pointers to the left/right child nodes. Compare this to an [array](../Fixed Size Array).
41+
42+
```
43+
(10)
44+
/ \
45+
(7) (2)
46+
/ \
47+
(5) (1)
48+
```
49+
Notice that if you go horizontally accross the tree shown above, the values are in no particular order; it does not matter whether `(7)` is greater than or less that `(2)`, as long as they are both less than `(10)`. The lack of order means we can fill out our tree one row at a time, as long as we maintain the heap property.
50+
51+
#### The solution
52+
53+
Adding only to the end of the heap allows us to implement the Heap with an array; this may seem like an odd way to implement a tree-like structure but it is very efficient in both time and space. This is how we're going to store the array shown above:
54+
55+
```
56+
[10|7|2|5|1]
57+
```
58+
59+
We can use simple path to find the parent or child node for a node at a particular index. Let's create a mapping of the array indexes to figure out how they relate:
60+
61+
|Child Node|Parent Node|
62+
|----------|-----------|
63+
| 1,2 | 0 |
64+
| 3,4 | 1 |
65+
| 5,6 | 2 |
66+
| 7,8 | 3 |
67+
68+
Using integer arithmetic and division the solution is a trivial computation:
69+
70+
```
71+
childOne = (parent * 2) + 1
72+
childTwo = childOne + 1 = (parent * 2) + 2
73+
parent = (child - 1) / 2
74+
```
75+
76+
These equations let us find the parent or child index for any node in `O(1)` time without using nodes; this means we don't need to use extra memory to allocate nodes that contain data and references to the left and right children. We can also append a node to the end of the array in `O(1)` time.
77+
78+
### Insertion
79+
80+
Lets go through an example insertion. Lets insert `(6)` into this heap:
81+
82+
```
83+
(10)
84+
/ \
85+
(7) (2)
86+
/ \
87+
(5) (1)
88+
```
89+
Lets add `(6)` to the last available space on the last row.
90+
91+
```
92+
(10)
93+
/ \
94+
(7) (2)
95+
/ \ /
96+
(5) (1) (6)
97+
```
98+
99+
Unfortunately, the heap property is no longer satisfied because `(2)` is above `(6)` and we want higher numbers above lower numbers. We're just going to swap `(6)` and `(2)` to restore the heap property. We keep swapping our inserted value with its parent until the parent is larger or until we get to the top of the tree. This is called **shift-up** or **sifting** and is done after every insertion. The time required for shifting up is proportional to the height of the tree so it takes `O(log(n))` time.
100+
101+
```
102+
(10)
103+
/ \
104+
(7) (6)
105+
/ \ /
106+
(5) (1) (2)
107+
```
108+
109+
110+
### Removal
111+
112+
The `remove()` method is implemented similar to `insert()`. Lets remove 10 from the previous tree:
113+
114+
```
115+
(10)
116+
/ \
117+
(7) (2)
118+
/ \
119+
(5) (1)
120+
```
121+
122+
So what happens to the empty spot at the top?
123+
124+
```
125+
(??) (10)
126+
/ \
127+
(7) (2)
128+
/ \
129+
(5) (1)
130+
```
131+
Like `insert()`, we're going to take the last object we have, stick it up on top of the tree, and restore the heap property.
132+
133+
```
134+
(1) (10)
135+
/ \
136+
(7) (2)
137+
/
138+
(5)
139+
```
140+
141+
Let's look at how to **shift-down** `(1)`. To maintain the heap property, we want to the highest number of top. We have 2 candidates for swapping places with `(1)`: `(7)` and `(2)`. Choose the highest number between these three nodes to be on top; this is `(7)`, so swapping `(1)` and `(7)` gives us the following tree.
142+
143+
```
144+
(7) (10)
145+
/ \
146+
(1) (2)
147+
/
148+
(5)
149+
```
150+
151+
Keep shifting down until the node doesn't have any children or it's largest than both its children. The time required for shifting all the way down is proportional to the height of the tree so it takes `O(log(n))` time. For our heap we only need 1 more swap to restore the heap property.
152+
153+
```
154+
(7)
155+
/ \
156+
(5) (2)
157+
/
158+
(1)
159+
```
160+
Finally we can return `(10)`.
161+
162+
### Sources
163+
164+
[Heap on Wikipedia](https://en.wikipedia.org/wiki/Heap_%28data_structure%29)

README.markdown

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ Lists:
123123
- Splay Tree
124124
- Threaded Binary Tree
125125
- kd-Tree
126-
- Heap
126+
- [Heap](Heap/) (by [Kevin Randrup](http://www.github.com/kevinrandrup))
127127
- Fibonacci Heap
128128
- Trie
129129

0 commit comments

Comments
 (0)