Java堆结构PriorityQueue完全解析

Java堆结构PriorityQueue完全解析在堆排序这篇文章中千辛万苦的实现了堆的结构和排序,其实在Java1.5版本后就提供了一个具备了小根堆性质的数据结构也就是优先队列PriorityQueue。下面详细了解一下PriorityQueue到底是如何实现小顶堆的,然后利用PriorityQueue实现大顶堆。PriorityQueue的数据结构PriorityQueue的逻辑结构是一棵完全二叉树,存储结构其实是一个数组。逻辑结构层次遍历的

大家好,又见面了,我是你们的朋友全栈君。

堆排序这篇文章中千辛万苦的实现了堆的结构和排序,其实在Java 1.5版本后就提供了一个具备了小根堆性质的数据结构也就是优先队列PriorityQueue。下面详细了解一下PriorityQueue到底是如何实现小顶堆的,然后利用PriorityQueue实现大顶堆。

PriorityQueue的数据结构

这里写图片描述

PriorityQueue的逻辑结构是一棵完全二叉树,存储结构其实是一个数组。逻辑结构层次遍历的结果刚好是一个数组。

PriorityQueue的操作

①add(E e) 和 offer(E e) 方法

add(E e) 和 offer(E e) 方法都是向PriorityQueue中加入一个元素,其中add()其实调用了offer()方法如下:

public boolean add(E e) {
        return offer(e);
    }

下面主要看看offer()方法的作用:
这里写图片描述
如上图调用 offer(4)方法后,往堆中压入4然后从下往上调整堆为小顶堆。offer()的代码实现:

public boolean offer(E e) {
        if (e == null)
            throw new NullPointerException();
      //如果压入的元素为null 抛出异常 
        int i = size;
        if (i >= queue.length)
            grow(i + 1);
            //如果数组的大小不够扩充
        size = i + 1;
        if (i == 0)
            queue[0] = e;
            //如果只有一个元素之间放在堆顶
        else
            siftUp(i, e);
            //否则调用siftUp函数从下往上调整堆。
        return true;
    }

对上面代码做几点说明:
①优先队列中不能存放空元素。
②压入元素后如果数组的大小不够会进行扩充,上面的queue其实就是一个默认初始值为11的数组(也可以赋初始值)。
③offer元素的主要调整逻辑在 siftUp ( i, e )函数中。下面看看 siftUp(i, e) 函数到底是怎样实现的。

private void siftUpComparable(int k, E x) {
        Comparable<? super E> key = (Comparable<? super E>) x;
        while (k > 0) {
            int parent = (k - 1) >>> 1;
            Object e = queue[parent];
            if (key.compareTo((E) e) >= 0)
                break;
            queue[k] = e;
            k = parent;
        }
        queue[k] = key;
    }

上面的代码还是比较简明的,就是当前元素与父节点不断比较如果比父节点小就交换然后继续向上比较,否则停止比较的过程。

② poll() 和 remove() 方法
poll 方法每次从 PriorityQueue 的头部删除一个节点,也就是从小顶堆的堆顶删除一个节点,而remove()不仅可以删除头节点而且还可以用 remove(Object o) 来删除堆中的与给定对象相同的最先出现的对象。先看看poll()方法。下面是poll()之后堆的操作

删除元素后要对堆进行调整:
这里写图片描述

堆中每次删除只能删除头节点。也就是数组中的第一个节点。
这里写图片描述
将最后一个节点替代头节点然后进行调整。
这里写图片描述
如果左右节点中的最小节点比当前节点小就与左右节点的最小节点交换。直到当前节点无子节点,或者当前节点比左右节点小时停止交换。

poll()方法的源码

public E poll() {
        if (size == 0)
            return null;
      //如果堆大小为0则返回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;
    }

看看 siftDown(0, x) 方法的源码:

private void siftDownComparable(int k, E x) {
        Comparable<? super E> key = (Comparable<? super E>)x;
        int half = size >>> 1;        // loop while a non-leaf
        while (k < half) {
            int child = (k << 1) + 1; // assume left child is least
            Object c = queue[child];
            int right = child + 1;
            if (right < size &&
                ((Comparable<? super E>) c).compareTo((E) queue[right]) > 0)
                c = queue[child = right];
            if (key.compareTo((E) c) <= 0)
                break;
            queue[k] = c;
            k = child;
        }
        queue[k] = key;
    }

siftDown()方法就是从堆的第一个元素往下比较,如果比左右孩子节点的最小值小则与最小值交换,交换后继续向下比较,否则停止比较。
remove(4)的过程图:
这里写图片描述
先用堆的最后一个元素 5 代替4然后从5开始向下调整堆。这个过程和poll()函数一样,只不过poll()函数每次都是从堆顶开始。
remove(Object o)的代码:

 public boolean remove(Object o) {
        int i = indexOf(o);
        //先在堆中找到o的位置
        if (i == -1)
            return false;
        //如果不存在则返回false。 
        else {
            removeAt(i);
            //否则删除数组中第i个位置的值,调整堆。
            return true;
        }
    }

removeAt(int i)的代码

 private E removeAt(int i) {
        assert i >= 0 && i < size;
        modCount++;
        int s = --size;
        if (s == i) // removed last element
            queue[i] = null;
        else {
            E moved = (E) queue[s];
            queue[s] = null;
            siftDown(i, moved);
            if (queue[i] == moved) {
                siftUp(i, moved);
                if (queue[i] != moved)
                    return moved;
            }
        }
        return null;
    }

使用PriorityQueue实现大顶堆

PriorityQueue默认是一个小顶堆,然而可以通过传入自定义的Comparator函数来实现大顶堆。如下代码:

 private static final int DEFAULT_INITIAL_CAPACITY = 11;
PriorityQueue<Integer> maxHeap=new PriorityQueue<Integer>(DEFAULT_INITIAL_CAPACITY, new Comparator<Integer>() {
        @Override
        public int compare(Integer o1, Integer o2) {                
            return o2-o1;
        }
    });

实现了一个初始大小为11的大顶堆。这里只是简单的传入一个自定义的Comparator函数,就可以实现大顶堆了。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/131481.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

  • maven 配置阿里云仓库「建议收藏」

    maven 配置阿里云仓库「建议收藏」1.Windows下Maven安装配置(本地仓库配置)链接2.eclipse中新建maven项目链接3.idea中maven的相关配置链接maven中央仓库地址有哪些链接

  • 一篇读懂无线充电技术(附方案选型及原理分析)「建议收藏」

    一篇读懂无线充电技术(附方案选型及原理分析)「建议收藏」一文读懂无线充电技术(附方案选型及原理分析)0.背景1.无线供电特点2.无线供电原理及实现方式3.现有解决方案分析4.FAQ及相关测试5.参考资料0.背景现今几乎所有的电子设备,如手机,MP3和笔记本电脑等,进行充电的方式主要是有线电能传输,既一端连接交流电源,另一端连接便携式电子设备充电电池的。这种方式有很多不利的地方,首先频繁的插拔很容易损坏主板接口,另外不…

  • httpclient4.x访问https[通俗易懂]

    httpclient4.x访问https[通俗易懂]https有单向认证和双向认证之分,单向认证即客户端只会认证服务端,双向认证是客户端需要认证服务端,服务端也需要认证客户端。先说单向认证,浏览器访问服务端,服务端接收请求,会把证书(包含密钥和其他信息)和加密后响应返回给浏览器。如果这个证书不是向第三方权威机构申请的,浏览器会提示证书有问题(使用httpclient访问的话会报错)。如果忽略错误,则浏览器接受证书并解密响应,发送的数据也用此密钥

  • java培训学校杭州_杭州Java培训班

    java培训学校杭州_杭州Java培训班前言这段时间也一直在学习Netty相关知识,因为涉及知识点比较多,也走了不少弯路。目前网上关于Netty学习资料玲琅满目,不知如何下手,其实大家都是一样的,学习方法和技巧都是总结出来的,我们在没有找到很好的方法之前不如按部就班先从基础开始,一般从总分总的渐进方式,既观森林,又见草木。Netty是一款提供异步的、事件驱动的网络应用程序框架和工具,是基于NIO客户端、服务器端的编程框架。所以这里我们先以NIO和依赖相关的基础铺垫来进行剖析讲解,从而作为Netty学习之旅的一个开端。为什么学Java?Jav

  • spring事务的传播行为和隔离级别_spring常用的事务传播行为

    spring事务的传播行为和隔离级别_spring常用的事务传播行为  本文主要介绍下Spring事务中的传播行为。事务传播行为介绍Spring中的7个事务传播行为:事务行为说明PROPAGATION_REQUIRED支持当前事务,假设当前没有事务。就新建一个事务PROPAGATION_SUPPORTS支持当前事务,假设当前没有事务,就以非事务方式运行PROPAGATION_MANDATORY支持当前事务,假设当前没有事…

    2022年10月29日
  • gtest测试框架使用详解_quartus在线调试教程

    gtest测试框架使用详解_quartus在线调试教程编辑推荐:本文来自51CTO,本文主要简单介绍了googletest代码的环境配置以及简单实用过程,希望对您的学习有所帮助。1、下载googletest代码得到压缩包:解压并进入msvc文件夹:googletest-master\googletest\msvc2、打开gtest.sln文件因为我的VS是2017版,下载的gtest对应的是2010版,所以打开会提示选择目标SDK版本和升级平台工具集…

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号