-
Notifications
You must be signed in to change notification settings - Fork 316
/
LeftistHeap.scala
200 lines (179 loc) · 5.74 KB
/
LeftistHeap.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
/**
* This file is part of Scalacaster project, https://github.com/vkostyukov/scalacaster
* and written by Vladimir Kostyukov, http://vkostyukov.ru
*
* Heap http://en.wikipedia.org/wiki/Heap_(data_structure)
*
* -Notes-
*
* This is Okasaki's Leftist Heap implemetation that satisfies two properties:
*
* 1. Heap-ordered propery: value(node) <= value(node.left) and also
* value(node) <= value(node.right)
*
* 2. Leftist property: rank(node.left) >= rank(node.right)
*
* , where the 'rank' is rightmost spine (rightmost path from the root to
* an empty node) of the heap. Combining these proprties together, we can
* guarantee at most O(log n) running time for insert/remove operations of
* this heap.
*
* The heart of this implementation is 'merge' function that merges two given
* heaps in logarithmic time, which is incredible performance. So, leftist heaps
* are beautiful in terms of fast merging. The typical use case for such heaps -
* functional map/reduce algorithms where the final result is gathering from
* the pieces by merging them. The 'merge' function can be treated as merging
* two sorted lists (since the values in the right spine are always sorted).
*
*/
abstract sealed class Heap[+A <% Ordered[A]] {
/**
* Min value of this heap.
*/
def min: A
/**
* The left child of this heap.
*/
def left: Heap[A]
/**
* The right child of this heap.
*/
def right: Heap[A]
/**
* The rank of this heap.
*/
def rank: Int
/**
* Whether this heap is empty or not.
*/
def isEmpty: Boolean
/**
* Inserts given element 'x' into this heap.
*
* Exercise 3.2 @ PFDS.
*
* The 'insert' function might be defined through the 'Heap.merge' function, like
*
* insert(x) = Heap.merge(this, Heap.make(x))
*
* We also can define the 'insert' function directly rather then calling merge
* function. But the new function should also work in O(log n) time. This is the
* main restriction.
*
* Tha main idea behind this function is that we can safely insert new node into
* the right sub-heap until it's rank less then it's left sibling. Otherwise,
* we have to _fill_ the left sub-heap enough to have it rank increased. This
* approach gives us a complete heap as output, instead of unbalanced in case
* of pure leftist heaps.
*
* Building the heap with such algorithm guarantees logarithmic running time
* for custom queries on the heap's shape. This is not a popular scenarion,
* but it might be usful for some problems.
*
* Time - O(log n)
* Space - O(log n)
*/
def insert[B >: A <% Ordered[B]](x: B): Heap[B] =
if (isEmpty) Heap.make(x)
else if (left.rank > right.rank) Heap.bubble(min, left, right.insert(x))
else Heap.bubble(min, left.insert(x), right)
/**
* Removes the minimum element from this heap.
*
* Time - O(log n)
* Space - O(log n)
*/
def remove: Heap[A] = Heap.merge(left, right)
/**
* Fails with message.
*/
def fail(m: String) = throw new NoSuchElementException(m)
}
case object Leaf extends Heap[Nothing] {
def min: Nothing = fail("An empty heap.")
def left: Heap[Nothing] = fail("An empty heap.")
def right: Heap[Nothing] = fail("An empty heap.")
def rank: Int = 0
def isEmpty = true
}
case class Branch[A <% Ordered[A]](min: A,
left: Heap[A],
right: Heap[A],
rank: Int) extends Heap[A] {
def isEmpty = false
}
object Heap {
/**
* An empty heap.
*/
def empty[A]: Heap[A] = Leaf
/**
* Makes a heap's node with pre-calculated rank value.
*/
def make[A <% Ordered[A]](x: A, l: Heap[A] = Leaf, r: Heap[A] = Leaf) =
Branch(x, l, r, r.rank + 1)
/**
* A smart constructor for heap's branch.
*
* In order to satisfy the leftist property, we have to check the children's ranks
* and do the swap if needed.
*
* Time - O(1)
* Space - O(1)
*/
def swap[A <% Ordered[A]](x: A, l: Heap[A] = Leaf, r: Heap[A] = Leaf): Heap[A] =
if (l.rank < r.rank) Heap.make(x, r, l)
else Heap.make(x, l, r)
/**
* Bubbles given value up to the heap's root.
*
* This function fixes the heap-ordered property violation.
*
* Time - O(1)
* Space - O(1)
*/
def bubble[A <% Ordered[A]](x: A, l: Heap[A], r: Heap[A]): Heap[A] = (l, r) match {
case (Branch(y, lt, rt, _), _) if (x > y) =>
Heap.make(y, Heap.make(x, lt, rt), r)
case (_, Branch(z, lt, rt, _)) if (x > z) =>
Heap.make(z, l, Heap.make(x, lt, rt))
case (_, _) => Heap.make(x, l, r)
}
/**
* Merges two given heaps along their right spine.
*
* The 'merge' function make sure that values from right spine will be sorted
* after merging (satisfying the heap-ordered proerty). This gives us guarantee
* that the smallest element of result heap will be at root.
*
* Time - O(log n)
* Space - O(log n)
*/
def merge[A <% Ordered[A]](x: Heap[A], y: Heap[A]): Heap[A] = (x, y) match {
case (_, Leaf) => x
case (Leaf, _) => y
case (Branch(xx, xl, xr, _), Branch(yy, yl, yr, _)) =>
if (xx < yy) Heap.swap(xx, xl, Heap.merge(xr, y))
else Heap.swap(yy, yl, Heap.merge(yr, x))
}
/**
* Exercise 3.3 @ PFDS.
*
* Builds a leftist heap from an unordered linked list.
*
* Time - O(n)
* Space - O(log n)
*/
def fromList[A <% Ordered[A]](ls: List[A]): Heap[A] = {
def loop(hs: List[Heap[A]]): Heap[A] = hs match {
case hd :: Nil => hd
case _ => loop(pass(hs))
}
def pass(hs: List[Heap[A]]): List[Heap[A]] = hs match {
case hd :: nk :: tl => Heap.merge(hd, nk) :: pass(tl)
case _ => hs
}
if (ls.isEmpty) Heap.empty
else loop(ls.map(Heap.make(_)))
}
}