CS 136 - Lecture 26

  1. Inserting into a Heap
  2. Deleting the root from a Heap
  3. Sorting with a Heap

Inserting into a Heap

  1. Place number to be inserted at the next free position.
  2. "Percolate" (sift up) it up to correct position

Deleting the Root from a Heap

  1. Save value in root element
  2. Move last element to root
  3. Push down element now in the root position (it was formerly the last element) to its correct position by repeatedly swapping it with the smaller of its two children.
public class VectorHeap implements PriorityQueue
{
    protected Vector data;

    public VectorHeap()
    // post: constructs a new priority queue.
    {
        data = new Vector();
    }

    protected static int parentOf(int i)
    // post: returns index of parent of value at i
    {
        return (i-1)/2;
    }

    protected static int leftChildOf(int i)
    // post: returns index of left child of value at i
    {
        return 2*i+1;
    }

    protected static int rightChildOf(int i)
    // post: returns index of right child of value at i
    {
        return 2*(i+1);
    }

    public Comparable peek()
    // pre: !isEmpty()
    // post: returns minimum value in queue
    {
        return (Comparable)data.elementAt(0);
    }

    public Comparable remove()
    // pre: !isEmpty()
    // post: removes and returns minimum value in queue
    {
        Comparable minVal = peek();
        data.setElementAt(data.elementAt(data.size()-1),0);
        data.setSize(data.size()-1);
        if (data.size() > 1) pushDownRoot(0);
        return minVal;
    }

    public void add(Comparable value)
    // pre: value is non-null comparable object
    // post: adds value to priority queue
    {
        data.addElement(value);
        percolateUp(data.size()-1);
    }

    public boolean isEmpty()
    // post: returns true iff queue has no values
    {
        return data.size() == 0;
    }

    protected void percolateUp(int leaf)
    // pre: 0 <= leaf < size
    // post: takes value at leaf in near-heap,
    //       and pushes up to correct location
    {
        int parent = parentOf(leaf);
        Comparable value = (Comparable)(data.elementAt(leaf));
        while (leaf > 0 && (value.lessThan((Comparable)(data.elementAt(parent)))))
        {
           data.setElementAt(data.elementAt(parent),leaf);
           leaf = parent;
           parent = parentOf(leaf);
        }
        data.setElementAt(value,leaf);
    }

    protected void pushDownRoot(int root)
    // pre: 0 <= root < size
    // post: pushes root down into near-heap
    //       constructing heap
    {
        int heapSize = data.size();
        Comparable value = (Comparable)data.elementAt(root);
        while (root < heapSize) {
          int childpos = leftChildOf(root);
          if (childpos < heapSize)
          {
            if ((rightChildOf(root) < heapSize) &&
            (((Comparable)(data.elementAt(childpos+1))).lessThan
                   ((Comparable)(data.elementAt(childpos)))))
            {
              childpos++;
            }
            // Assert: childpos indexes smaller of two children
            if (((Comparable)(data.elementAt(childpos))).lessThan(value))
            {
              data.setElementAt(data.elementAt(childpos),root);
              root = childpos; // keep moving down
            } else { // found right location
              data.setElementAt(value,root);
              return;
            }
          } else { // at a leaf! insert and halt
            data.setElementAt(value,root);
            return;
          }       
        }
    }

    ...
}
Notice how these heap operations implement a priority queue.

When you add a new element in a priority queue, copy it into the next free position of the heap and sift it up into its proper position.

When you remove the next element from the priority queue, remove the element from the root of heap (first elt, since it has lowest number for priority), move the last element up to the first slot, and then sift it down.

How expensive are sift up and sift down?

Each are log n. This compares very favorably with holding the priority queue as regular queue and inserting new elements into the right position in the queue and removing them from the front.


Sorting with a Heap (HeapSort)

We build a heap with the smallest element at top (taking <= (n/2) log n compares)

Once the heap is established, remove elements one at a time, putting smallest at end, second smallest next to end, etc.

In detail:

Swap top with last element, sift down, do heap sort on remaining n-1 elements.

Ex. 25, 46, 19, 58, 21, 23, 12

public void heapSort (VectorHeap aheap)
{
    int last = aheap.size()-1;      // keeps track of how much of list is now in a heap 

     // Construct the initial heap.  Push down elts starting w/parent of last elt.
    for (int index = (last-1) / 2; index >= 0; index--)
    aheap.pushDownRoot(index);

    // Extract the elements in sorted order. 
    for (int index = last; index > 0; index--)
    {
            aheap.Swap(0, index);
            aheap.pushDownRoot(0);    // Note that elements "sorted out" of the heap are ignored here.
    }
 }
Each sift down takes <= log n steps, so the process of extracting elements in sorted order is O(n log n).

With a little work we can show that the initial "heapifying" of the list can be done in O(n) compares. The key is that we only call pushDownRoot on the first half of the elements of the list. That is, no calls of pushDownRoot are made on subscripts corresponding to leaves of the tree (corresponding to n/2 of the elements). For those elements sitting just above the leaves (n/4 of the elements), we only go through the loop once (and thus we make only two comparisons of priorities). For those in the next layer (n/8 of the elements) we only go through the loop twice (4 comparisons), and so on. Thus we make 2*(n/4) + 4*(n/8) + 6*(n/16) + ... + 2*(log n)*(1) total comparisons. We can rewrite this as log n + n*( 1/21 + 2/22 + 3/23 + ... + log n/2log n). (Of course in the last term, 2log n = n, so this works out as above.) The sum inside the parentheses can be rewritten as Sum for i=1 to log n of (i/2i). This is clearly bounded above by the infinite sum, Sum for i=1 to infinity of i/2i. With some work the infinite sum can be shown to be equal to 2. (The trick is to arrange the terms in a triangle:

         1/2 +  1/4  +  1/8 +  1/16 + ... =  1
                1/4  +  1/8 +  1/16 + ... =  1/2
                        1/8 +  1/16 + ... =  1/4
                               1/16 + ... =  1/8
                                      ... = ..
                 -------------------------------
          Sum for i=1 to infinity of i/2i = 2
Thus n*( 1/21 + 2/22 + 3/23 + ... + log n/2log n) <= 2n, and hence the time to heapify an array is O(n).

Therefore, total time is O(n log n).

No extra space needed!