Java 文档 - Heap & Priority Queues

161 阅读2分钟

Heap

1.1 Introduction 

A heap is a semi-ordered tree-based data strucure where either

- Each parent's key is greater than its children (a max heap- largest element on the top)

- Or each parent's key is less than its children (a min heap - smallest element on the top)

Often these trees will have a max number of children (per parent) of 2, in which case they are known as binary heaps.

1.2 Heap Operation: Insert

We always insert a new element into the highest, and then left-most, position that is available. (This will allow us to efficiently use an array to represent the elements)

Newly inserted, this will violates the max-heap property (children must be smaller than the parent), so we have to "swim" up / heapifyUp the element until the property is satisfied again - we do a series of swaps between the element and its parent until it is no longer larger than its parent

What if the inserted elements were smaller? Then less swaps (or even no swaps, possibly) may be required.

In case of inserting 7 into the tree, only one swap is required to restore the max-heap property.

1.3 Heap Operation: Removal

To remove any element, we wil take that element out of the tree and replace it with the lowest, then right-most, element int the tree. Often we will be removing the top element in a priority queue.

The top now violates the max-heap property (each parent is larger than its children), so we will need to "sink" down / heapify down until the property is satisfied.

To do this, we will swap this element with the larger of its two children until we satisfy the max-heap property again. (If we don't use the larger child, we might not end up with a max-heap)

If the swapped element is larger, we might require fewer swaps

1.4 Heap Data Storage

We will use an array as our backing data structure. Because of the way we chose to add and remove elements from the heap, mapping them to indices in an array is a snap. The first index tells us where the root is, and the last where to insert a new element after ( or which element would swap into a hole). Note that we will never have internal holes in our array.

Heap Implementation

2.1 Use array for MinIntHeap

import java.util.Arrays;

public class MinIntHeap {
    //for a heap, we can use one int array to achieve heap functions
    private int capacity = 0;
    private int size = 0;

    int[] items = new int[capacity];
    private int getLeftChildIndex(int parentIndex) {return 2 * parentIndex + 1; }
    private int getRightChildIndex(int parentIndex) {return 2 * parentIndex + 2; }
    private int getParentIndex(int childIndex) { return (childIndex - 1) / 2; }

    private boolean hasLeftChild(int index) { return getLeftChildIndex(index) < size; }
    private boolean hasRightChild(int index) { return getRightChildIndex(index) < size; }
    private boolean hasParent(int index) { return getParentIndex(index) >= 0; }

    private int leftChild(int index) { return items[getLeftChildIndex(index)]; }
    private int rightChild(int index) { return items[getRightChildIndex(index)]; }
    private int parent(int index) { return items[getParentIndex(index)]; }

    private void swap(int indexOne, int indexTwo) {
        int temp = items[indexOne];
        items[indexOne] = items[indexTwo];
        items[indexTwo] = temp;
    }

    private void ensureExtraCapacity() {
        if(size == capacity) {
            items = Arrays.copyOf(items, capacity * 2);
            capacity *= 2;
        }
    }

    public int peek() {
        if(size == 0) throw new IllegalStateException();
        return items[0];
    }

    public int poll() {
        if(size == 0) throw new IllegalStateException();
        int item = items[0];
        items[0] = items[size - 1];
        size--;
        heapifyDown();
        return item;
    }

    public void add(int item) {
        ensureExtraCapacity();
        items[size] = item;
        size++;
        heapifyUp();
    }

    public void heapifyUp() {
        int index = size - 1;
        while(hasParent(index) && parent(index) > items[index]) {
            swap(getParentIndex(index), index);
            index = getParentIndex(index);
        }
    }

    public void heapifyDown() {
        int index = 0;
        while(hasLeftChild(index)) {
            int smallerChildIndex = getLeftChildIndex(index);
            if(hasRightChild(index) && rightChild(index) < leftChild(index)) {
                smallerChildIndex = getRightChildIndex(index);
            }

            if(items[index] < items[smallerChildIndex]) {
                break;
            } else {
                swap(index, smallerChildIndex);
            }
            index = smallerChildIndex;
        }
    }
}

Priority Queues

3.1 Make a priority queue from a heap

- Use a heap data structure underneath

- Insert new elements into the heap

- Remove the top element to get the highest priority element

- Change priorities by removing the element and then re-inserting it

3.2 Implementation Caveats

- Array may need resizing as heap grows; doubling is a good rule of thumb

- Sometimes heaps with more than two children per parent may be more efficient for some applications