class Containers::Heap
A Heap
is a container that satisfies the heap property that nodes are always smaller in value than their parent node.
The Containers::Heap
class is flexible and upon initialization, takes an optional block that determines how the items are ordered. Two versions that are included are the Containers::MaxHeap
and Containers::MinHeap
that return the largest and smallest items on each invocation, respectively.
This library implements a Fibonacci heap, which allows O(1) complexity for most methods.
Public Class Methods
If an optional array is passed, the entries in the array are inserted into the heap with equal key and value fields. Also, an optional block can be passed to define the function that maintains heap property. For example, a min-heap can be created with:
minheap = Heap.new { |x, y| (x <=> y) == -1 } minheap.push(6) minheap.push(10) minheap.pop #=> 6
Thus, smaller elements will be parent nodes. The heap defaults to a min-heap if no block is given.
# File lib/containers/heap.rb 38 def initialize(ary=[], &block) 39 @compare_fn = block || lambda { |x, y| (x <=> y) == -1 } 40 @next = nil 41 @size = 0 42 @stored = {} 43 44 ary.each { |n| push(n) } unless ary.empty? 45 end
Public Instance Methods
Changes the key from one to another. Doing so must not violate the heap property or an exception will be raised. If the key is found, an array containing the new key and value pair is returned, otherwise nil is returned.
In the case of duplicate keys, an arbitrary key is changed. This will be investigated more in the future.
Complexity: amortized O(1)
minheap = MinHeap.new([1, 2]) minheap.change_key(2, 3) #=> raise error since we can't increase the value in a min-heap minheap.change_key(2, 0) #=> [0, 2] minheap.pop #=> 2 minheap.pop #=> 1
# File lib/containers/heap.rb 262 def change_key(key, new_key, delete=false) 263 return if @stored[key].nil? || @stored[key].empty? || (key == new_key) 264 265 # Must maintain heap property 266 raise "Changing this key would not maintain heap property!" unless (delete || @compare_fn[new_key, key]) 267 node = @stored[key].shift 268 if node 269 node.key = new_key 270 @stored[new_key] ||= [] 271 @stored[new_key] << node 272 parent = node.parent 273 if parent 274 # if heap property is violated 275 if delete || @compare_fn[new_key, parent.key] 276 cut(node, parent) 277 cascading_cut(parent) 278 end 279 end 280 if delete || @compare_fn[node.key, @next.key] 281 @next = node 282 end 283 return [node.key, node.value] 284 end 285 nil 286 end
Removes all elements from the heap, destructively.
Complexity: O(1)
# File lib/containers/heap.rb 143 def clear 144 @next = nil 145 @size = 0 146 @stored = {} 147 nil 148 end
Deletes the item with associated key and returns it. nil is returned if the key is not found. In the case of nodes with duplicate keys, an arbitrary one is deleted.
Complexity: amortized O(log n)
minheap = MinHeap.new([1, 2]) minheap.delete(1) #=> 1 minheap.size #=> 1
# File lib/containers/heap.rb 300 def delete(key) 301 pop if change_key(key, nil, true) 302 end
Returns true if the heap is empty, false otherwise.
# File lib/containers/heap.rb 154 def empty? 155 @next.nil? 156 end
Returns true if heap contains the key.
Complexity: O(1)
minheap = MinHeap.new([1, 2]) minheap.has_key?(2) #=> true minheap.has_key?(4) #=> false
# File lib/containers/heap.rb 101 def has_key?(key) 102 @stored[key] && !@stored[key].empty? ? true : false 103 end
Does a shallow merge of all the nodes in the other heap.
Complexity: O(1)
heap = MinHeap.new([5, 6, 7, 8]) otherheap = MinHeap.new([1, 2, 3, 4]) heap.merge!(otherheap) heap.size #=> 8 heap.pop #=> 1
# File lib/containers/heap.rb 170 def merge!(otherheap) 171 raise ArgumentError, "Trying to merge a heap with something not a heap" unless otherheap.kind_of? Containers::Heap 172 other_root = otherheap.instance_variable_get("@next") 173 if other_root 174 @stored = @stored.merge(otherheap.instance_variable_get("@stored")) { |key, a, b| (a << b).flatten } 175 # Insert othernode's @next node to the left of current @next 176 @next.left.right = other_root 177 ol = other_root.left 178 other_root.left = @next.left 179 ol.right = @next 180 @next.left = ol 181 182 @next = other_root if @compare_fn[other_root.key, @next.key] 183 end 184 @size += otherheap.size 185 end
Returns the value of the next item in heap order, but does not remove it.
Complexity: O(1)
minheap = MinHeap.new([1, 2]) minheap.next #=> 1 minheap.size #=> 2
# File lib/containers/heap.rb 116 def next 117 @next && @next.value 118 end
Returns the key associated with the next item in heap order, but does not remove the value.
Complexity: O(1)
minheap = MinHeap.new minheap.push(1, :a) minheap.next_key #=> 1
# File lib/containers/heap.rb 132 def next_key 133 @next && @next.key 134 end
Returns the value of the next item in heap order and removes it from the heap.
Complexity: O(1)
minheap = MinHeap.new([1, 2]) minheap.pop #=> 1 minheap.size #=> 1
# File lib/containers/heap.rb 198 def pop 199 return nil unless @next 200 popped = @next 201 if @size == 1 202 clear 203 return popped.value 204 end 205 # Merge the popped's children into root node 206 if @next.child 207 @next.child.parent = nil 208 209 # get rid of parent 210 sibling = @next.child.right 211 until sibling == @next.child 212 sibling.parent = nil 213 sibling = sibling.right 214 end 215 216 # Merge the children into the root. If @next is the only root node, make its child the @next node 217 if @next.right == @next 218 @next = @next.child 219 else 220 next_left, next_right = @next.left, @next.right 221 current_child = @next.child 222 @next.right.left = current_child 223 @next.left.right = current_child.right 224 current_child.right.left = next_left 225 current_child.right = next_right 226 @next = @next.right 227 end 228 else 229 @next.left.right = @next.right 230 @next.right.left = @next.left 231 @next = @next.right 232 end 233 consolidate 234 235 unless @stored[popped.key].delete(popped) 236 raise "Couldn't delete node from stored nodes hash" 237 end 238 @size -= 1 239 240 popped.value 241 end
Inserts an item with a given key into the heap. If only one parameter is given, the key is set to the value.
Complexity: O(1)
heap = MinHeap.new heap.push(1, "Cat") heap.push(2) heap.pop #=> "Cat" heap.pop #=> 2
# File lib/containers/heap.rb 61 def push(key, value=key) 62 raise ArgumentError, "Heap keys must not be nil." unless key 63 node = Node.new(key, value) 64 # Add new node to the left of the @next node 65 if @next 66 node.right = @next 67 node.left = @next.left 68 node.left.right = node 69 @next.left = node 70 if @compare_fn[key, @next.key] 71 @next = node 72 end 73 else 74 @next = node 75 end 76 @size += 1 77 78 arr = [] 79 w = @next.right 80 until w == @next do 81 arr << w.value 82 w = w.right 83 end 84 arr << @next.value 85 @stored[key] ||= [] 86 @stored[key] << node 87 value 88 end
Return the number of elements in the heap.
# File lib/containers/heap.rb 19 def size 20 @size 21 end
Private Instance Methods
# File lib/containers/heap.rb 386 def cascading_cut(node) 387 p = node.parent 388 if p 389 if node.marked? 390 cut(node, p) 391 cascading_cut(p) 392 else 393 node.marked = true 394 end 395 end 396 end
Makes sure the structure does not contain nodes in the root list with equal degrees
# File lib/containers/heap.rb 347 def consolidate 348 roots = [] 349 root = @next 350 min = root 351 # find the nodes in the list 352 loop do 353 roots << root 354 root = root.right 355 break if root == @next 356 end 357 degrees = [] 358 roots.each do |root| 359 min = root if @compare_fn[root.key, min.key] 360 # check if we need to merge 361 if degrees[root.degree].nil? # no other node with the same degree 362 degrees[root.degree] = root 363 next 364 else # there is another node with the same degree, consolidate them 365 degree = root.degree 366 until degrees[degree].nil? do 367 other_root_with_degree = degrees[degree] 368 if @compare_fn[root.key, other_root_with_degree.key] # determine which node is the parent, which one is the child 369 smaller, larger = root, other_root_with_degree 370 else 371 smaller, larger = other_root_with_degree, root 372 end 373 link_nodes(larger, smaller) 374 degrees[degree] = nil 375 root = smaller 376 degree += 1 377 end 378 degrees[degree] = root 379 min = root if min.key == root.key # this fixes a bug with duplicate keys not being in the right order 380 end 381 end 382 @next = min 383 end
remove x from y's children and add x to the root list
# File lib/containers/heap.rb 400 def cut(x, y) 401 x.left.right = x.right 402 x.right.left = x.left 403 y.degree -= 1 404 if (y.degree == 0) 405 y.child = nil 406 elsif (y.child == x) 407 y.child = x.right 408 end 409 x.right = @next 410 x.left = @next.left 411 @next.left = x 412 x.left.right = x 413 x.parent = nil 414 x.marked = false 415 end
make node a child of a parent node
# File lib/containers/heap.rb 324 def link_nodes(child, parent) 325 # link the child's siblings 326 child.left.right = child.right 327 child.right.left = child.left 328 329 child.parent = parent 330 331 # if parent doesn't have children, make new child its only child 332 if parent.child.nil? 333 parent.child = child.right = child.left = child 334 else # otherwise insert new child into parent's children list 335 current_child = parent.child 336 child.left = current_child 337 child.right = current_child.right 338 current_child.right.left = child 339 current_child.right = child 340 end 341 parent.degree += 1 342 child.marked = false 343 end