Heap Implementation

Theory

When you pop, since you swap the last leave node to the top, you need to bubbleDown() from the top node

  • This takes $O(logn)$

When you insert, since you add from the bottom, you need to bubbleUp

  • This takes $O(logn)$

When heapify (forming a heap from the array). You have to bubbleDown from the last non-leave node ($\frac{n - 1}{2}$) to the root (i = 0).

  • This takes $O(nlogn)$

Implementation

package datastructures;


import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

public class HeapQueue<T> implements Heap<T> {
  private List<T> elements;

  private Comparator<T> comparator;

  public HeapQueue(Comparator<T> comparator) {
    elements = new ArrayList<>();
    this.comparator = comparator;
  }

  public HeapQueue(List<T> elements, Comparator<T> comparator) {
    this.elements = new ArrayList<>(elements);
    this.comparator = comparator;
    for (var i = getLastNonLeaveNode(elements.size()); i >= 0; i--) {
      bubbleDown(i);
    }
  }

  @Override
  public T peek() {
    if (elements.isEmpty()) {
      throw new IndexOutOfBoundsException();
    }
    return elements.get(0);
  }

  @Override
  public T pop() {
    if (elements.isEmpty()) {
      throw new IllegalStateException();
    }

    T removedItem = peek();
    swap(0, elements.size() - 1);
    elements.remove(elements.size() - 1);

    bubbleDown(i);
    return removedItem;
  }

  @Override
  public void insert(T element) {
    elements.add(element);
    bubbleUp(elements.size() - 1);
  }

  private void bubbleUp(int currentIndex) {
    int parentIndex = getParentIndex(currentIndex);

    if (currentIndex == 0 || comparator.compare(elements.get(currentIndex), elements.get(parentIndex)) < 0) {
      return;
    }

    swap(currentIndex, parentIndex);
    bubbleUp(parentIndex);
  }


  private void bubbleDown(int currentIndex) {
    int leftChildIndex = getLeftChildIndex(currentIndex);
    int rightChildIndex = getRightChildIndex(currentIndex);
    int largestIndex = currentIndex;

    if (leftChildIndex < elements.size() && comparator.compare(elements.get(leftChildIndex), elements.get(largestIndex)) > 0) {
      largestIndex = leftChildIndex;
    }

    if (rightChildIndex < elements.size() && comparator.compare(elements.get(rightChildIndex), elements.get(largestIndex)) > 0) {
      largestIndex = rightChildIndex;
    }

    if (largestIndex != currentIndex) {
      swap(currentIndex, largestIndex);
      bubbleDown(largestIndex);
    }
  }

  private void swap(int firstIndex, int secondIndex) {
    T temp = elements.get(secondIndex);
    elements.set(secondIndex, elements.get(firstIndex));
    elements.set(firstIndex, temp);
  }

  private int getLeftChildIndex(int index) {
    return 2 * index + 1;
  }

  private int getRightChildIndex(int index) {
    return 2 * index + 2;
  }

  private int getLastNonLeaveNode(int length) {
    return (length - 1) / 2;
  }

  private int getParentIndex(int currentIndex) {
    return (currentIndex - 1) / 2;
  }
}

package datastructures;


public interface Heap<T> {
  T peek();
  T pop();
  void insert(T element);
}

Thread Safe version

@ThreadSafe
public class SynchronisedHeapQueue<T> extends HeapQueue<T>{
  public SynchronisedHeapQueue(Comparator<T> comparator) {
    super(comparator);
  }

  public SynchronisedHeapQueue(List<T> elements, Comparator<T> comparator) {
    super(elements, comparator);
  }

  @Override
  public synchronized T peek() {
    return super.peek();
  }

  @Override
  public synchronized T pop() {
    return super.pop();
  }

  @Override
  public synchronized void insert(T element) {
    super.insert(element);
  }
}