lib/compsci/heap.rb in compsci-0.0.2.1 vs lib/compsci/heap.rb in compsci-0.0.3.1

- old
+ new

@@ -1,28 +1,137 @@ require 'compsci/tree' -# A Heap is a partially sorted, complete binary tree with the property: -# * Every node has a value larger (or smaller) than that of its children. +include CompSci + +# A Heap is a partially sorted, complete N-ary tree with the property: +# * Every node has a value larger (or smaller) than that of its children +# (the heap property is satisfied when a parent value equals a child value) # -# This class implements a heap using a simple array for storage. -# Array index math is used to find: -# * The root node (idx 0) -# * The "bottom-most" leaf node (last idx) -# * Parent idx (idx-1 / 2) -# * Child idx (2*idx + 1, 2*idx + 2) -# -# Any Comparable may be used for node values. -# Initialize a heap with a cmp_val, either 1 for a MaxHeap or -1 for a MinHeap. -# The heap property is satisfied when a parent value equals a child value. -# Insertion (push) and removal (pop) are O(log n) where n is the heap size. -# Nodes are inserted at the end of the array, and sift_up is called to +# Implementation details: +# * Any Comparable may be used for node values. +# * Initialize a heap with a cmp_val, either 1 for a MaxHeap or -1 for a +# MinHeap. +# * Insertion (push) and removal (pop) are O(logb n) where n is the heap size +# and b is child_slots (the N in N-ary) +# * Nodes are inserted at the end of the array, and sift_up is called to # reestablish the heap property. -# Nodes are removed from the start of the array, and sift_down is called to +# * Nodes are removed from the start of the array, and sift_down is called to # reestablish the heap property. -# Sift_up and sift_down are O(log n) because they only have to check and swap -# nodes at each layer of the tree, and there are log n layers to the tree. +# * Sift_up and sift_down are O(logb n) because they only have to check and +# swap nodes at each layer of the tree, and there are log(n, base b) layers +# to the tree. # -class CompSci::Heap < CompSci::CompleteBinaryTree +class Heap < CompleteNaryTree + # * defaults to a MaxHeap, with the largest node at the root + # * specify a minheap with minheap: true or cmp_val: -1 + # + def initialize(cmp_val: 1, minheap: false, child_slots: 2) + super(child_slots: child_slots) + cmp_val = -1 if minheap + case cmp_val + when -1, 1 + @cmp_val = cmp_val + else + raise(ArgumentError, "unknown comparison value: #{cmp_val}") + end + end + + # * append to the array + # * sift_up -- O(log n) on heap size + # + def push(node) + @store << node + self.sift_up(@store.size - 1) + end + + # * remove from the front of the array + # * move last node to root + # * sift_down -- O(log n) on heap size + # + def pop + node = @store.shift + replacement = @store.pop + @store.unshift replacement if replacement + self.sift_down(0) + node + end + + # * return what pop would return (avoid sifting) + # + def peek + @store.first + end + + # * called recursively + # * idx represents the node suspected to violate the heap + # * intended to be O(log n) on heap size (log base child_slots) + # + def sift_up(idx) + return self if idx <= 0 + pidx = self.class.parent_idx(idx, @child_slots) + if !self.heapish?(pidx, idx) + @store[idx], @store[pidx] = @store[pidx], @store[idx] # swap + self.sift_up(pidx) + end + self + end + + # * called recursively + # * idx represents the node suspected to violate the heap + # * intended to be O(log n) on heap size (log base child_slots) + # * slower than sift_up because one parent vs multiple children + # + def sift_down(idx) + return self if idx >= @store.size + cidxs = self.class.children_idx(idx, @child_slots) + # promote the heapiest child + cidx = self.heapiest(cidxs) + if !self.heapish?(idx, cidx) + @store[idx], @store[cidx] = @store[cidx], @store[idx] # swap + self.sift_down(cidx) + end + self + end + + # are values of parent and child (by index) in accordance with heap property? + # + def heapish?(pidx, cidx) + (@store[pidx] <=> @store[cidx]) != (@cmp_val * -1) + end + + # return the heapiest idx in cidxs + # + def heapiest(cidxs) + idx = cidxs.first + cidxs.each { |cidx| + idx = cidx if cidx < @store.size and self.heapish?(cidx, idx) + } + idx + end + + # * not used internally + # * checks that every node satisfies the heap property + # * calls heapish? on idx's children and then heap? on them recursively + # + def heap?(idx: 0) + check_children = [] + self.class.children_idx(idx, @child_slots).each { |cidx| + # cidx is arithmetically produced; the corresponding child may not exist + if cidx < @store.size + return false unless self.heapish?(idx, cidx) + check_children << cidx + end + } + check_children.each { |cidx| return false unless self.heap?(idx: cidx) } + true + end +end + +# +# LEGACY BELOW untouched for now +# + +class BinaryHeap < CompleteBinaryTree # defaults to a MaxHeap, with the largest node at the root # specify a minheap with minheap: true or cmp_val: -1 # def initialize(cmp_val: 1, minheap: false) super()