Maxiphobic Heaps
September 28, 2010
A maxiphobic heap is a binary tree with node defined as four-slot vectors:
(define mh-node vector) ; size item lkid rkid
Four functions provide access to the four slots of the vector; they are defined as macros so they will be inlined:
(define-syntax mh-size (syntax-rules () ((_ mh) (vector-ref mh 0))))
(define-syntax mh-item (syntax-rules () ((_ mh) (vector-ref mh 1))))
(define-syntax mh-lkid (syntax-rules () ((_ mh) (vector-ref mh 2))))
(define-syntax mh-rkid (syntax-rules () ((_ mh) (vector-ref mh 3))))
There is a single, distinguished node representing the empty priority queue, with size zero, and a predicate to identify it:
(define mh-empty (mh-node 0 'mh-empty 'mh-empty 'mh-empty))
(define (mh-empty? mh) (eqv? mh mh-empty))
The fundamental operation is merge:
(define (mh-merge lt? mh1 mh2)
(cond ((mh-empty? mh1) mh2) ((mh-empty? mh2) mh1)
(else (if (lt? (mh-item mh1) (mh-item mh2))
(let ((mhs (mh-biggest (mh-lkid mh1) (mh-rkid mh1) mh2)))
(mh-node (+ (mh-size mh1) (mh-size mh2))
(mh-item mh1) (car mhs)
(apply mh-merge lt? (cdr mhs))))
(let ((mhs (mh-biggest mh1 (mh-lkid mh2) (mh-rkid mh2))))
(mh-node (+ (mh-size mh1) (mh-size mh2))
(mh-item mh2) (car mhs)
(apply mh-merge lt? (cdr mhs))))))))
The merge function first checks for the trivial cases where one or the other maxiphobic heap is empty. If not, it compares the items at the roots of the two trees and merges them according to the algorithm. Merge calls an auxiliary function mh-biggest to determine which sub-trees to merge first:
(define (mh-biggest mh1 mh2 mh3)
(if (< (mh-size mh1) (mh-size mh2))
(if (< (mh-size mh2) (mh-size mh3))
(list mh3 mh1 mh2)
(list mh2 mh1 mh3))
(if (< (mh-size mh1) (mh-size mh3))
(list mh3 mh1 mh2)
(list mh1 mh2 mh3))))
Given merge, insert, find-min and delete are trivial, and are identical to their leftist-heap counterparts:
(define (mh-insert lt? x mh)
(mh-merge lt? (mh-node 1 x mh-empty mh-empty) mh))
(define (mh-find-min mh)
(if (mh-empty? mh)
(error 'mh-find-min "empty maxiphobic heap")
(mh-item mh)))
(define (mh-delete-min lt? mh)
(if (mh-empty? mh)
(error 'mh-delete-min "empty maxiphobic heap")
(mh-merge lt? (mh-lkid mh) (mh-rkid mh))))
To illustrate maxiphobic heaps, we provide this sorting function that inserts each of its inputs into a maxiphobic heap, in random input order, and outputs them in priority order:
(define (mh-sort lt? xs)
(let loop1 ((xs xs) (mh mh-empty))
(if (pair? xs)
(loop1 (cdr xs) (mh-insert lt? (car xs) mh))
(let loop2 ((mh mh) (zs '()))
(if (mh-empty? mh)
(reverse zs)
(loop2 (mh-delete-min lt? mh)
(cons (mh-find-min mh) zs)))))))
Here’s an example:
> (mh-sort > '(3 1 7 2 5 6 4))
(7 6 5 4 3 2 1)
You can run the program at http://programmingpraxis.codepad.org/YZIkmZ02.
Here’s a ruby version from the psuedo code here (http://www.eecs.usma.edu/webs/people/okasaki/sigcse05.pdf)
class Maxiphobic class Heap attr_reader :value attr_accessor :size, :left, :right def initialize(value) @value = value @size = 1 @left = nil @right = nil end end # Return the minimum of the heap or nil if the # heap is nil. def find_minimum return @heap != nil ? @heap.value : nil end # Delete the minimum (root) if the heap is not nil. def delete_minimum @heap = merge(@heap.left, @heap.right) if @heap != nil end # Insert a new value into the heap. Create a new # heap and then set the heap to either the new heap # of a merge of the existing and new heaps. def insert(v) new_heap = Heap.new(v) @heap = (@heap == nil) ? new_heap : merge(@heap, new_heap) end # Merge two heaps. def merge(h1, h2) # Return the other if either of the # two heaps are nil. return h2 if h1 == nil return h1 if h2 == nil # Swap so that we have the smallest # value in h1. if h2.value < h1.value h1, h2 = h2, h1 end # Get the sizes of the heap and the left and right # sub-trees. h1.size = h1.size + h2.size a = h1.left a_size = (a != nil) ? a.size : 0 b = h1.right b_size = (b != nil) ? b.size : 0 c = h2 c_size = (c != nil) ? c.size : 0 # Put the smaller tree in a a, b = b, a if b_size > a_size a, c = c, a if c_size > a_size # Set the left subtree to the smallest and # then merge the other two subtrees. h1.left = a h1.right = merge(b, c) # return the merged trees in h1 return h1 end # Return the sorted values and empty the # heap. def sort! s = [] while (v = find_minimum) != nil do s << v delete_minimum end return s end end maxiphobic = Maxiphobic.new [3, 1, 7, 2, 5, 6, 4].each do |v| maxiphobic.insert(v) end s = maxiphobic.sort! p sIt works, but it seems like it could be improved quite a bit though.
As a side note, the ruby version should work with any objects that support “” for sorting as in …
%w[ goodbye hello test alpha gamma beta delta].each do |v| maxiphobic.insert(v) end s = maxiphobic.sort! p s