数据结构之堆和优先队列

定义

  1. 堆通常是一个可以被看做一棵树的数组对象,所有它没有使用父指针或者子指针。
  2. 二叉堆是一棵完全二叉树。
  3. 堆分为两种:最大堆最小堆,两者的差别在于节点的排序方式。
  4. 最大堆:父节点的值比每一个子节点的值都要大。子树也是最大堆。
    最小堆:父节点的值比每一个子节点的值都要小。子树也是最小堆

堆与二叉搜索树

节点的顺序。在二叉搜索树中,左子节点必须比父节点小,右子节点必须必比父节点大。但是在堆中并非如此。在最大堆中两个子节点都必须比父节点小,而在最小堆中,它们都必须比父节点大。
内存占用。普通树占用的内存空间比它们存储的数据要多。你必须为节点对象以及左/右子节点指针分配额为是我内存。堆仅仅使用一个数据来村塾数组,且不使用指针。
平衡。二叉搜索树必须是“平衡”的情况下,其大部分操作的复杂度才能达到O(log n)。你可以按任意顺序位置插入/删除数据,或者使用 AVL 树或者红黑树,但是在堆中实际上不需要整棵树都是有序的。我们只需要满足对属性即可,所以在堆中平衡不是问题。因为堆中数据的组织方式可以保证O(log n) 的性能。
搜索。在二叉树中搜索会很快,但是在堆中搜索会很慢。在堆中搜索不是第一优先级,因为使用堆的目的是将最大(或者最小)的节点放在最前面,从而快速的进行相关插入、删除操作。

堆的操作

这里以最大堆为例

插入

插入其实就是把插入结点放在堆最后面,然后与父亲比较,如果父亲值小于它,那么它就和父亲结点交换位置,重复该过程,直到插入节点遇到一个值比它大的父亲或者它成为树根结点。

以最大堆为栗子,在堆中插入值为20的结点(红色结点代表新进入)

在这里插入图片描述
20明显大于它的父亲结点值,所以和7交换位置,交换后新的父亲值还是比它小,继续交换
在这里插入图片描述在这里插入图片描述在这里插入图片描述
一步一步与它前面比它小的父亲结点交换位置,最后20成为根结点
最小堆同理,只不过要求父亲结点要比它小,如果父亲结点大于它,就得交换

删除

删除通常是指删除最大堆中的最大值或者最小堆中的最小值,也就是树根。
以删除最大堆树根为例子,删除其实就是整个堆中少了一个结点,不妨把位于最后面的一个结点当成新的树根,然后与它左右孩子比较,与值最大并且值大于它的孩子进行交换,直到它的孩子都是小于它的,或者变成树叶。

还是用最大堆为例,删除树根20,把最后的结点8提到树根
在这里插入图片描述在这里插入图片描述
然后就按说明步骤,找到一个位置,它的孩子都小于它或者他变成树叶
在这里插入图片描述在这里插入图片描述在这里插入图片描述

堆的特性

如果从数组的第一个节点开始存放数据的话,当前节点的父节点、左孩子、右孩子的索引就会有如下的关系:

父节点的索引:(index-1)/2 (index为当前节点的索引)

左孩子的索引:index*2+1

右孩子的索引:index*2+2
右孩子的索引 = 左孩子的索引 + 1

在这里插入图片描述
如上图堆,采用数据存储为:

[ 10, 7, 2, 5, 1 ]

堆的实现

  1. 定义
public class MyHeap {
    Integer[] data;  //存放数据
    int size;   //堆的大小

    public MyHeap() {
        data = new Integer[10];  //默认数组大小为10
    }

}
  1. 获取父节点
	 /**
     * 返回堆中索引为index的节点的父节点的索引
     * @param index
     * @return
     */
    private int parent(int index) {
        if (index == 0)
            System.out.println("index-0 doesn't have parent");
        return (index - 1) / 2;
    }
  1. 返回左孩子
	 /**
     * 返回堆中索引为index的节点的左孩子的索引
     * @param index
     * @return
     */
    private int leftChild(int index) {
        return index * 2 + 1;
    }
  1. 返回右孩子
	/**
     * 返回堆中索引为index的节点的右孩子的索引
     * @param index
     * @return
     */
    private int rightChild(int index) {
        return index * 2 + 2;
    }
  1. 交换数据
	 /**
     * 交换索引为i、j的值
     * @param i
     * @param j
     */
    private void swap(int i, int j) {
        Integer t = data[i];
        data[i] = data[j];
        data[j] = t;
    }
  1. 向上调整
 	/**
     * 如果一个节点比它的父节点大(最大堆),那么需要将它同父节点交换位置。
     * @param k
     */
    private void shiftUp(int k){
        while (k > 0){  //k不为树根
            if(data[parent(k)].compareTo(data[k]) < 0){ //如果比父节点大
                swap(k,parent(k));
            }else{
                break;
            }
            k = parent(k);
        }
    }
  1. 向下调整
	/**
     * 如果一个节点比它的子节点小(最大堆),那么需要将它向下移动。
     * @param k
     */
    private void shiftDown(int k){
        while (leftChild(k) < size){    //存在子节点
            int j = leftChild(k);
            if(j + 1 < size && data[j].compareTo(data[j+1]) < 0){   //如果右孩子存在,且大于左孩子
                j = rightChild(k);
            }
            if(data[k].compareTo(data[j]) < 0){ //如果比子节点小
                swap(k,j);
            }else {
                break;
            }
            k = j;
        }
    }
  1. 将数组转化为堆
 /**
     * 将任意数组转化为堆
     * @param array
     */
    public void heapify(Integer[] array){
        if(array == null && array.length == 0)
            return;
        data = array;
        size = array.length;
        //先将数组直接看成是一个完全二叉树,然后找到该树的最后一个节点的父节点。然后从这个节点开始到根节点结束,执行shiftDown操作。时间复杂度为O(n)
        for (int i = parent(size - 1); i >= 0; i--) {
            shiftDown(i);
        }

    }
  1. 数组扩容
 	/**
     * 数组扩容
     * @param size
     */
    private void ensureCapacity(int size) {
        int len = data.length;
        if(size>len)
        {
            //数组拷贝扩容
            data = Arrays.copyOf(data, size);
        }
    }
  1. 添加元素
	/**
     * 添加元素
     * @param element
     */
    public void add(Integer element){
        ensureCapacity(size+1);
        data[size++] = element;
        shiftUp(size-1);
    }
  1. 删除堆顶元素
	/**
     * 删除堆顶元素
     */
    public Integer remove(){
        if(size == 0)
            return null;
        Integer e = data[0];
        swap(0,size-1); //将数组第一个元素与最后一个元素交换
        data[size-1] = null;    //删除数组最后一个元素
        size--;
        shiftDown(0);
        return e;
    }
  1. 删除指定元素
	/**
     * 删除指定位置的元素
     * @param index
     * @return
     */
    public Integer remove(int index){
        if(size == 0 || index > size - 1 || index < 0)
            return null;
        Integer e = data[index];
        if(index == size - 1){
            data[size-1] = null;
            size--;
        }else{
            swap(index,size-1);
            data[size-1] = null;
            size--;
            if(data[index].compareTo(data[parent(index)]) > 0)
                shiftUp(index);
            else
                shiftDown(index);
        }
        return e;
    }

优先队列

  1. 普通的队列是一种先进先出的数据结构,元素在队列尾追加,而从队列头删除。在优先队列中,元素被赋予优先级。当访问元素时,具有最高优先级的元素最先删除。优先队列具有最高级先出 (first in, largest out)的行为特征。
  2. 优先队列通常采用堆数据结构来实现。
  3. Java中的优先队列PriorityQueue就是采用数组实现的二叉堆(最小堆)来实现。
    在这里插入图片描述

JDK中优先队列的实现

  1. 入队(二叉堆添加元素)
	/**
     * Inserts the specified element into this priority queue.
     *
     * @return {@code true} (as specified by {@link Queue#offer})
     * @throws ClassCastException if the specified element cannot be
     *         compared with elements currently in this priority queue
     *         according to the priority queue's ordering
     * @throws NullPointerException if the specified element is null
     */
    public boolean offer(E e) {
        if (e == null)
            throw new NullPointerException();
        modCount++;
        int i = size;
        if (i >= queue.length)
            grow(i + 1);	//数组扩容
        size = i + 1;
        if (i == 0)
            queue[0] = e;
        else
            siftUp(i, e);	//向上调整
        return true;
    }
  1. 出队(删除堆顶元素)
    public E poll() {
        if (size == 0)
            return null;
        int s = --size;
        modCount++;
        E result = (E) queue[0];	//获取堆顶元素
        E x = (E) queue[s];
        queue[s] = null;	//删除数组最后一个元素
        if (s != 0)
            siftDown(0, x);	//向下调整
        return result;
    }

最大堆和最小堆实现的区别在于shiftUp、shiftDown中判断条件相反
由于Object数组元素无法比较大小,可将泛型元素强制转换为Comparable<? super E>类型

参考链接:
https://blog.csdn.net/szu_crayon/article/details/81812946
https://www.jianshu.com/p/6b526aa481b1
https://blog.csdn.net/qq_37044026/article/details/86714130

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码农先锋

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值