Implementation of binary heap tree

1.1 basic concepts

A binary heap is a complete binary tree (different from a full binary tree). The value of a node in the heap is always not greater than the value of its parent node. Usually, this heap is called the maximum heap (the corresponding minimum heap can be defined). An element in the lower layer is not necessarily smaller than an element in the upper layer.

1. Large top reactor (maximum reactor)

The value of any parent node of the maximum heap is greater than or equal to the value of its left and right child nodes.

2. Small top pile (minimum pile)

The value of any parent node of the minimum heap is less than or equal to the value of its left and right child nodes.

The root node of a binary heap is called the top of the heap

The characteristics of the maximum heap and the minimum heap determine that the top of the maximum heap is the largest element in the whole heap, and the top of the minimum heap is the smallest element in the whole heap.

1.2 storage principle

A complete binary tree is more suitable for storing arrays. Using an array to store a complete binary tree is very memory saving. Because we do not need to store the pointers of the left and right child nodes, we can find the left and right child nodes and parent nodes of a node simply through the subscript of the array.

We can see from the figure that the left child node of the node with subscript i in the array is the node with subscript left child(i) = i * 2 + 1, the right child node is the node with subscript right child(i) = i * 2 + 2, and the parent node is the node with subscript parent(i) = (i-1) /2.

1.3 train of thought analysis

1. Add element

The elements in the heap are arranged in the style of array. Adding elements is equivalent to the rightmost element of the sequence traversal, that is, the rightmost element of the lowest layer. From the perspective of array representation, the value of element [52] is added at the position with index [10].

This obviously does not meet the nature of the heap. All the added elements are compared with the parent node. It is found that element [52] is larger than [16], so the positions of the two elements need to be exchanged.

The binary tree with [52] as the root satisfies the nature of heap, but this is obviously not enough. The node [52] is larger than its parent node [41], so there is also element exchange between the two.

At this time, [52] is smaller than the element node [62], which meets the nature of the heap. Therefore, the nature of the heap is not destroyed at this position.

2. Delete element

Take out the elements from the heap, only the largest element, the top element, and the top element.

Top the last element [16] in the heap to the top of the heap. After this operation, the element with index 0 is [16], and the last element is also 16. Then delete the last element. One element is successfully reduced from the number of elements, and it is the element at the top of the heap.

However, now that the elements at the top of the heap have broken the nature of the heap, the sinking operation should be performed to compare the sinking elements with its left and right children. Select the largest element in the child. If it is smaller than the largest element, exchange it.

Then continue to compare and exchange.

At this time, the sinking node is larger than 15, which meets the basic properties of the heap. At this time, the sinking operation has ended.

1.4 code implementation

1. Sequence table: ArrayList

package cn.heap.demo01;

import java.util.Iterator;

public class ArrayList<E> implements Iterable<E>{
    // Elements of the sequence table
    private E[] data;
    // Number of sequential table elements
    private int size;

    // Define constants
    private static final int ELEMENT_NOT_FOUND = -1;
    private static final int DEFAULT_CAPACITY = 6;

    // Number of elements
    public int size() {
        return size;
    }

    //Clear all elements
    public void clear() {
        for (int i = 0; i < size; i++) {
            data[i] = null;
        }
        size = 0;
    }


    // Constructor, passing in the capacity of the array to construct SqList
    public ArrayList(int capacity){
        data = (E[]) new Object[capacity];
        capacity = (capacity < DEFAULT_CAPACITY) ? DEFAULT_CAPACITY : capacity;
    }

    // Parameterless constructor. The capacity of the default array is capacity=10
    public ArrayList(){
        this(DEFAULT_CAPACITY);
    }

    // Gets the capacity of the array
    public int getCapacity(){
        return data.length;
    }

    // Gets the number of elements in the array
    public int getSize(){
        return size;
    }

    // Returns whether the array is empty
    public boolean isEmpty(){
        return size == 0;
    }

    // Add a new element after all elements
    public void addLast(E e){
        add(size, e);
    }

    // Add a new element before all elements
    public void addFirst(E e){
        add(0, e);
    }

    // Insert a new element at the index e
    public void add(int index, E e){
        // Capacity expansion operation
        if(size == data.length){
           resize(2 * data.length);
        }

        for(int i = size - 1; i >= index ; i --){
            data[i + 1] = data[i];
        }

        data[index] = e;
        size ++;
    }

    // Gets the element at the index position
    public E get(int index){
        rangeCheck(index);
        return data[index];
    }

    // View the index of the element
    public int indexOf(E e){
        if (e == null) {
            for (int i = 0; i < size; i++) {
                if (data[i] == null) return i;
            }
        } else {
            for (int i = 0; i < size; i++) {
                if (e.equals(data[i])) return i;
            }
        }
        return ELEMENT_NOT_FOUND;
    }

    // Modify the element of index position to e
    public void set(int index, E e){
        rangeCheck(index);
        data[index] = e;
    }

    // Find whether there is an element e in the array
    public boolean contains(E e){
       return indexOf(e) != ELEMENT_NOT_FOUND;
    }

    // Find the index of element e in the array. If element e does not exist, return - 1
    public int find(E e){
        for(int i = 0 ; i < size ; i ++){
            if(data[i].equals(e))
                return i;
        }
        return -1;
    }

    // Delete the element at index position from the array and return the deleted element
    public E remove(int index){
        rangeCheck(index);
        E ret = data[index];
        for(int i = index + 1 ; i < size ; i ++){
            data[i - 1] = data[i];
        }
        // Empty
        data[--size] = null;

        // Volume reduction operation
        if(size == data.length / 4){
            resize(data.length / 2);
        }
        return ret;
    }

    // Deletes the first element from the array and returns the deleted element
    public E removeFirst(){
        return remove(0);
    }

    // Deletes the last element from the array and returns the deleted element
    public E removeLast(){
        return remove(size - 1);
    }

    // Remove element e from array
    public void removeElement(E e){
        int index = find(e);
        if(index != -1) {
            remove(index);
        }
    }

    // Array index out of bounds processing
    private void outOfBounds(int index){
        throw new IndexOutOfBoundsException("index:" + index + ", Size:" + size);
    }

    // Index value check range method
    private void rangeCheck(int index){
        if(index < 0 || index >=size){
            // Call out of bounds processing method
            outOfBounds(index);
        }
    }

    // Add method index check scope
    private void rangeCheckAdd(int index){
        if(index < 0 || index >size){
            // Call out of bounds processing method
            outOfBounds(index);
        }
    }

    // capacity expansion method
    private void resize(int newCapacity){
        E[] newData = (E[])new Object[newCapacity];
        for(int i = 0 ; i < size ; i ++){
            newData[i] = data[i];
        }
        data = newData;
    }

    @Override
    public String toString(){
        StringBuilder res = new StringBuilder();
        res.append(String.format("Sequence table(ArrayList)length:%d, container:%d\n", size, data.length));
        res.append('[');
        for(int i = 0 ; i < size ; i ++){
            res.append(data[i]);
            if(i != size - 1)
                res.append(", ");
        }
        res.append(']');
        return res.toString();
    }

    // Exchange method
    public void swap(int i, int j){
        E temp = data[i];
        data[i] = data[j];
        data[j] = temp;
    }

    // traversal method 
    @Override
    public Iterator<E> iterator() {
        return new SIterator();
    }

    private class SIterator implements Iterator{
        // Define a pointer variable
        private int cur;
        public SIterator(){
            this.cur=0;
        }
        @Override
        public boolean hasNext() {
            return cur< size;
        }
        @Override
        public E next() {
            return data[cur++];
        }
    }
}

2. Maximum heap: MaxHeap

package cn.heap.demo01;

import java.util.Iterator;

/***
 * Maximum heap implementation
 * @param <E>
 */
public class MaxHeap<E extends Comparable<E>> implements Iterable<E>{
    // Use ArrayList as the storage container for the largest heap
    private ArrayList<E> data;
    // Heap space
    public MaxHeap(){
        data = new ArrayList<>();
    }

    // Gets the index of the parent node
    private int parent(int k){
        if(k <= 0){
            throw new IllegalArgumentException("No parent node!");
        }
        return (k -1 ) / 2;
    }

    // Gets the index of the left child node
    private int leftChild(int k){
        return 2 * k + 1;
    }

    // Gets the index of the right child node
    private int rightChild(int k){
        return 2 * k + 2;
    }

    // Returns the maximum number of valid heap elements
    public int size(){
        return data.size();
    }

    // Determine whether the binary heap is empty
    public boolean isEmpty(){
        return data.isEmpty();
    }

    // Empty binary stack
    public void clear(){
        data.clear();
    }

    // Add an element to the maximum heap e
    public void add(E e){
        data.addLast(e);
        // Index corresponding to floating element
        siftUp(data.size() -1);
    }

    // Float up the element corresponding to the corner mark K
    private void siftUp(int k){
        // If the parent node is smaller than itself, exchange elements
        while (k > 0 && data.get(k).compareTo(data.get(parent(k))) < 0){
            data.swap(k, parent(k));
            k = parent(k);
        }
    }

    // Maximum heap found
    public E findMax(){
        if (data.isEmpty()){
            throw new IllegalArgumentException("The maximum heap is empty!!!!");
        }
        return data.get(0);
    }

    // Minimum heap
    public E findMin(){
        if(data.isEmpty()){
            throw new IllegalArgumentException("The maximum heap is empty!!!");
        }
        E min = data.get(0);
        for (int i=1; i< data.size(); i++){
            if(data.get(i).compareTo(min) < 0){
                min = data.get(i);
            }
        }
        // Return minimum heap
        return min;
    }

    // Delete maximum
    public E extractMax(){
        // Get the maximum
        E max = findMax();
        // Swap the index 0 with the last element
        data.swap(0, data.size() - 1);
        // Delete the last element
        data.remove(data.size() - 1);

        // Call function
        siftDown(0);
        return max;
    }

    // float downward
    private void siftDown(int k) {
        while (leftChild(k) < data.size()){
            // Get the maximum value of left and right children
            int j = leftChild(k);
            if(j + 1 < data.size() && data.get(j+1).compareTo(data.get(j)) > 0){
                // data[j] is the maximum of leftChild and rightChild
                j = rightChild(k);
            }
            if(data.get(k).compareTo(data.get(j)) < 0){
                data.swap(k,j);
                k = j;
            }else {
                break;
            }
        }
    }

    // After taking out the largest element, put in a new element
    public E replace(E e){
        E ret = findMax();
        data.set(0, e);
        siftDown(0);
        return ret;
    }

    @Override
    public Iterator<E> iterator() {
        return data.iterator();
    }

    @Override
    public String toString(){
        return data.toString();
    }
}

3. Test class: TestMaxHeap

package cn.heap.demo01;

import java.util.Random;

public class TestMaxHeap {
    public static void main(String[] args) {
        MaxHeap<Integer> heap = new MaxHeap<>();
        Random random = new Random();
        // Traverse add
        for (int i=0; i<10; i++){
            heap.add(random.nextInt(20));
        }

        System.out.println(heap);

        for (int i=0; i<4; i++){
            System.out.println(heap.extractMax());
        }
    }
}

4. Execution results

1.5 priority queue

1. Basic introduction

Ordinary queue: first in first out, last in and last out.

Priority queue: the order of leaving the queue has nothing to do with the order of entering the queue. It is related to priority. It is still a queue in essence.

Application: in the task manager: dynamically select the task with the highest priority for execution. In the game: Tower Defense gives priority to attack (distance, threat, order).

2. Case realization

Interface: Queue

package cn.heap.demo02;

public interface Queue<E> extends Iterable<E>{
    int size();
    boolean isEmpty();
    // Queue operation
    void enqueue(E element);
    // Out of line operation
    E dequeue();
    // View the elements of the current team leader
    E getFront();
    // Empty queue
    void clear();
}

Priority queue: PriorityQueue

package cn.heap.demo02;

import java.util.Iterator;

public class PriorityQueue<E extends Comparable<E>> implements Queue<E>{
    // Heap object
    private MaxHeap<E> heap;
    // constructor 
    public PriorityQueue(){
        heap = new MaxHeap<>();
    }

    @Override
    public int size() {
        return heap.size();
    }

    @Override
    public boolean isEmpty() {
        return heap.isEmpty();
    }

    @Override
    public void enqueue(E element) {
        // Queue operation
        heap.add(element);
    }

    @Override
    public E dequeue() {
        return heap.extractMax();
    }

    @Override
    public E getFront() {
        return heap.findMax();
    }

    @Override
    public void clear() {
        heap.clear();
    }

    @Override
    public Iterator<E> iterator() {
        return heap.iterator();
    }

    @Override
    public String toString() {
        return heap.toString();
    }
}

Test class: TestPriorityQueue

package cn.heap.demo02;

import java.util.Random;

public class TestPriorityQueue {
    public static void main(String[] args) {
        PriorityQueue<Integer> queue = new PriorityQueue<>();
        Random random = new Random();
        // Traverse add
        for (int i=0; i<10; i++){
            queue.enqueue(random.nextInt(20));
        }

        System.out.println(queue);

        for (int i=0; i<4; i++){
            System.out.println(queue.dequeue());
        }
    }
}

results of enforcement

Keywords: data structure

Added by lc on Mon, 03 Jan 2022 12:27:30 +0200