[数据结构与算法]-二叉堆(binary heap)介绍及其实现(Java)

版权声明:本文为博主原创文章,转载时请注明出处,谢谢!喝酒不骑马 邮箱[email protected] https://blog.csdn.net/Colton_Null/article/details/80963217

本文欢迎转载,转载前请联系作者。若未经允许转载,转载时请注明出处,谢谢! http://blog.csdn.net/colton_null 作者:喝酒不骑马 Colton_Null from CSDN


一.什么是二叉堆?

二叉堆(binary heap)是一种通常用于实现优先队列的数据结构。要想知道二叉堆是什么东西,得从两方面介绍它:结构性和堆序性。

1.结构性

二叉堆是一颗除底层外被完全填满的二叉树,对于底层上的元素满足从左到右填入的特性。如下图所示。
这里写图片描述
所以,鉴于二叉堆的这个结构特性,我们可以很容易得出——一颗高为h的二叉堆有 2 h 2 h + 1 1 个节点。

而且,基于二叉堆的这个特性,我们可以用一个数组在表示这种数据结构,不需要使用链。如下图所示。
这里写图片描述

数组上任意位置i上的元素,它的左节点在2i上,右节点在2i + 1上,它的父节点在└i/2┘。不用链表的好处在于,这么做对于计算机来说,访问某一元素的效率很高,不需要遍历即可访问。

2.堆序性

这里我们用最小堆(min-heap)来说明。

在堆中,每个节点n,都小于或等于它的两个子节点。如下图所示。
这里写图片描述
当然,我们可以类比处最大堆(max-heap)即每个节点都大于或等于它的子节点。

根据二叉堆的这个性质,最小元素或者最大元素即为根节点。我们直接就可以访问到这个最小的元素,即array[1](数组结构参考结构性)。

二.保持二叉堆的特性

在使用二叉堆的时候,我们对二叉堆插入和获取最小值的操作,会破坏二叉堆的特性。那么我们需要在每次操作之后,对二叉堆进行维护。

1.上滤

我们在执行插入(insert)操作的时候,可以采用上滤(percolate up)策略对二叉树进行维护。

首先在插入元素t时,在底层创建一个空穴,以保证完全二叉树的性质。如果元素t可以直接放在该空穴中不影响堆的堆序性,则插入完成;如果元素t不能直接放置在空穴中(即对于min-heap来说,t小于它的根节点或对于max-heap来说,t大于它的根节点),则把空穴的根节点放置在空穴中,原根节点被视为空穴。直到t被放入在合适的空穴中。

过程如下图所示,插入元素18。
这里写图片描述

2.下滤

在我们获取最小元素(即获取根节点并删除它时),我们需要用下滤(percolate down)策略来维护堆序性。

当删除最小元素时,根节点变为空穴,那么为了保证二叉堆的结构性,所以要把最后一个元素x移动到该堆的某个地方。如果x可以直接放入到空穴中,则下滤结束;否则,空穴子节点中最小或最大的值(这要看是min-heap还是max-heap,min-heap为最小值,max-heap为最大值)放入到空穴中,然后空穴下移一位,直到x可以被放置在空穴中。

过程如下图所示。

这里写图片描述

3.最小二叉堆的Java实现

BinaryHeap.java

public class BinaryHeap<T extends Comparable<? super T>> {
    public BinaryHeap() {
        this(DEFAULT_CAPACITY);
    }

    public BinaryHeap(int capacity) {
        currentSize = 0;
        arr = (T[]) new Comparable[capacity + 1];
    }

    public BinaryHeap(T[] items) {
        currentSize = items.length;
        arr = (T[]) new Comparable[(currentSize + 2) * 11 / 10];

        int i = 1;
        for (T item : items) {
            arr[i++] = item;
        }
        buildHeap();
    }

    /**
     * 二叉堆的插入方法。使用上滤法。
     *
     * @param t 被插入的元素
     */
    public void insert(T t) {
        if (currentSize == arr.length - 1) {
            // 如果当前元素个数为数组长度-1,则扩容
            enlargeArray(arr.length * 2 + 1);
        }
        int hole = ++currentSize;
        // arr[0] = t初始化,最后如果循环到顶点,t.compartTo(arr[hole / 2])即arr[0]为0,循环结束
        for (arr[0] = t; t.compareTo(arr[hole / 2]) < 0; hole /= 2) {
            // 根节点的值赋值到子节点
            arr[hole] = arr[hole / 2];
        }
        // 根节点(或树叶节点)赋值为t
        arr[hole] = t;
    }

    /**
     * 寻找堆内最小值。索引1处的元素最小。
     *
     * @return
     */
    public T findMin() {
        if (isEmpty()) {
            // 这里如果堆为空,可以抛出异常。
            // throw new UnderflowException( );
        }
        // 第1位的元素最小
        return arr[1];
    }

    public T deleteMin() {
        if (isEmpty()) {
            // 这里如果堆为空,可以抛出异常。
            // throw new UnderflowException( );
        }
        T minItem = findMin();
        // 将最后一个节点赋值到根节点
        arr[1] = arr[currentSize--];
        // 从根节点执行下滤
        percolateDown(1);
        return minItem;
    }

    /**
     * 判断堆是否为空
     *
     * @return 为空返回true;否则返回false
     */
    public boolean isEmpty() {
        return currentSize == 0;
    }

    /**
     * 堆置空
     */
    public void makeEmpty() {
        currentSize = 0;
    }

    /**
     * 打印堆
     */
    public void print(){
        System.out.print("堆为:");
        for (int i = 1;arr[i] != null;i++){
            System.out.print(arr[i] + " ");
        }
        System.out.println();
    }

    private static final int DEFAULT_CAPACITY = 10;

    private int currentSize;
    private T[] arr;

    /**
     * 下滤
     *
     * @param hole 下滤其实的节点的索引
     */
    private void percolateDown(int hole) {
        int child;
        T tmp = arr[hole];

        for (; hole * 2 <= currentSize; hole = child) {
            child = hole * 2;
            if (child != currentSize && arr[child + 1].compareTo(arr[child]) < 0) {
                // 做子节点不为左后一个节点(说明有右节点)且右节点比做节点小,索引改为右节点节点
                child++;
            }
            if (arr[child].compareTo(tmp) < 0) {
                // 如果遍历到的这个节点比最后一个元素小
                arr[hole] = arr[child];
            } else {
                break;
            }
        }
        // 将最后一个元素补到前面的空位
        arr[hole] = tmp;
    }

    private void buildHeap() {
        for (int i = currentSize / 2; i > 0; i--) {
            percolateDown(i);
        }
    }

    /**
     * 扩容
     * @param newSize 新数组的大小
     */
    private void enlargeArray(int newSize) {
        T[] old = arr;
        arr = (T[]) new Comparable[newSize];
        for (int i = 0; i < old.length; i++) {
            arr[i] = old[i];
        }
    }
}

BinaryHeapTest.java 测试类

public class BinaryHeapTest {
    public static void main(String[] args) {
        BinaryHeap binaryHeap = new BinaryHeap();
        int[] nums = new int[]{23, 98, 34, 63, 3, 0, 87, 45};
        for (Integer num : nums) {
            binaryHeap.insert(num);
        }
        binaryHeap.print();

        System.out.println("堆是否为空:" + binaryHeap.isEmpty());


        System.out.println("获取最小值:" + binaryHeap.deleteMin());

        binaryHeap.print();

    }
}

输出:

堆为:0 23 3 45 63 34 87 98 
堆是否为空:false
获取最小值:0
堆为:3 23 34 45 63 98 87 98 

最小二叉堆实现可用。

以上就是有关二叉堆(binary heap)介绍及其Java实现。


有关[数据结构与算法]的学习内容已经上传到github,喜欢的朋友可以支持一下。
https://github.com/MaYuzhe/data-structures-and-algorithm-study-notes-java


站在前人的肩膀上前行,感谢以下博客及文献的支持。
《数据结构与算法分析(第3 版) 工业出版社》

猜你喜欢

转载自blog.csdn.net/Colton_Null/article/details/80963217