Java 优先级队列

Java 优先级队列Java优先级队列

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE稳定放心使用

Java 优先级队列

PriorityQueue简介

PriorityQueue,即优先级队列。优先级队列可以保证每次取出来的元素都是队列中的最小最大的元素(Java优先级队列默认每次取出来的为最小元素)。

大小关系: 元素的比较可以通过元素本身进行自然排序,也可以通过构造方法传入比较器进行比较。

继承关系

在这里插入图片描述
通过继承关系图可以知道PriorityQueueQueen接口的一个实现类,而Queen接口是Collection接口的一个实现类,因此其拥有Collection接口的基本操作,此外,队列还提供了其他的插入移除查询的操作。每个方法存在两种形式:一种是抛出异常(操作失败时),另外一种是返回一个特殊值(null或者false,取决于操作)。
在这里插入图片描述
PriorityQueuepeekelement操作的时间复杂度都为常数addofferremove以及poll的时间复杂度是log(n)

PriorityQueue示例

import java.util.PriorityQueue;

public class PriorityQueueTest01 { 
   
    public static void main(String[] args) { 
   
        PriorityQueue<Integer> queue = new PriorityQueue<>();
        queue.add(11);
        queue.add(33);
        queue.add(22);
        queue.add(55);
        queue.add(44);

        System.out.println(queue.remove());
        System.out.println(queue.remove());
        System.out.println(queue.remove());
        System.out.println(queue.remove());
        System.out.println(queue.remove());
    }
}

运行结果:
在这里插入图片描述
代码中我们依次添加1133225544五个数据,然后进行删除,通过结果我们发现,每次删除的都为队列中最小元素,即体现了优先级队列

结论: 优先级队列默认每次获取队列中最小的元素,也可以通过comparator比较器来自定义每次获取为最小还是最大。

注意: 优先级队列中不可以存储null

Comparable比较器

Comparable接口

public interface Comparable<T> { 
   
    public int compareTo(T o);
}

该接口只存在一个public int compareTo(T o);方法,该方法表示所在的对象和o对象进行比较,返回值分三种:
1: 表示当前对象大于o对象
0: 表示当前对象等于o对象
-1: 表示当前对象小于o对象

在优先级队列中或者具有比较特征的集合中存储的对象需要实现Comparable接口

需求: 在优先级队列中存储对象学生,每个学生有idnameage三个属性,并且使优先级队列每次按照学生的id从小到大取出。

代码示例:
Student类: 当前类实现了Comparable接口,即当前类提供了默认的比较方法。

public class Student implements Comparable{ 
   
    private int id;
    private String name;
    private int age;
    
    public Student(int id, String name, int age) { 
   
        this.id = id;
        this.name = name;
        this.age = age;
    }

    public int getId() { 
   
        return id;
    }

    @Override
    public String toString() { 
   
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    @Override
    public int compareTo(Object o) { 
   
        Student o1 = (Student)o;
        return this.id - o1.id;
    }
}

PriorityQueueTest类:

public class PriorityQueueTest { 
   
    public static void main(String[] args) { 
   
        PriorityQueue<Student> queue = new PriorityQueue<>();
        queue.add(new Student(2,"王昭君",18));
        queue.add(new Student(1,"吕布",19));
        queue.add(new Student(5,"貂蝉",17));
        queue.add(new Student(3,"赵云",20));
        queue.add(new Student(4,"荆轲",18));

        System.out.println(queue.remove());
        System.out.println(queue.remove());
        System.out.println(queue.remove());
        System.out.println(queue.remove());
        System.out.println(queue.remove());
    }
}

运行结果:
在这里插入图片描述

Comparator比较器

新需求: 如果使优先级队列按照学生id从大到小取出呢?我们很快就会想到修改Student类的compareTo方法,使return o1.id – this.id;,这样当然可以实现我们的新需求。但是有很多时候类的compareTo方法是不能修改的,比如JDK给我们提供的源代码,在不修改compareTo方法的前提下实现需求,只能用Comparator比较器了。

Comparator接口

public interface Comparator<T> { 
   
    int compare(T o1, T o2);
}

该接口中提供了int compare(T o1, T o2)方法,该方法需要参数是两个待比较的对象
返回结果是int类型
1: 表示o1对象大于o2对象
0: 表示o1对象等于o2对象
-1: 表名o1对象小于o2对象

修改PriorityQueueTest类:

import java.util.PriorityQueue;

public class PriorityQueueTest { 
   
    public static void main(String[] args) { 
   
        PriorityQueue<Student> queue = new PriorityQueue<>(new Comparator<Student>() { 
   
            @Override
            public int compare(Student o1, Student o2) { 
   
                return o2.getId() - o1.getId();
            }
        });
        
        queue.add(new Student(2,"王昭君",18));
        queue.add(new Student(1,"吕布",19));
        queue.add(new Student(5,"貂蝉",17));
        queue.add(new Student(3,"赵云",20));
        queue.add(new Student(4,"荆轲",18));

        System.out.println(queue.remove());
        System.out.println(queue.remove());
        System.out.println(queue.remove());
        System.out.println(queue.remove());
        System.out.println(queue.remove());
    }
}

运行结果:
在这里插入图片描述

底层原理

优先级队列是如何保证每次取出的是队列中最小(最大)的元素呢?查看源码,底层的存储结构为一个数组

transient Object[] queue;

表面上是一个数组结构,实际上优先级队列采用的是堆的形式来进行存储的,通过调整小根堆大根堆来保证每次取出的元素为队列中最小最大

小根堆(任意一个非叶子节点的权值,都不大于其左右子节点的权值)
大根堆(任意一个非叶子节点的权值,都大于其左右子节点的权值)

可以通过数组来实现优先级队列底层实现,图示:
在这里插入图片描述

对于堆的实现是基于数组来实现的,实际开辟存储空间是数组,对数据的访问按照二叉树来进行访问遍历。父节点子节点编号存在联系,父节点和子节点存在如下关系:
leftNo = parentNo * 2 + 1;
rightNo= parantNo * 2 + 2;
parentNo = (nodeNo – 1) / 2;

通过以上的三个公式,可以轻易的计算出某个节点的父节点以及子节点的下标,这就是为什么可以使用数组来存储堆的原因。

以小根堆为例,数据如何进行调整:

插入数据
图示:
在这里插入图片描述
插入数据首先在有效数据的最后一个位置,即插入在某个叶子节点上,以该节点为待调整节点,和其父节点比较,如果当前节点大于父节点,符合小根堆,不用进行调整,否则需要进行调整,调整至0号根节点或者是其中某一个位置时当前节点大于父节点才终止。

源码如下:

//从下往上调整过程 k表示待插入元素位置 x表示待插入数据
private void siftUpUsingComparator(int k, E x) { 
   
        while (k > 0) { 
   
            //通过当前待调整节点k找到父节点
            int parent = (k - 1) >>> 1;
            Object e = queue[parent];
            //如果此时父节点数据小于待插入节点数据,满足小根堆,break退出
            if (comparator.compare(x, (E) e) >= 0)
                break;
            //不满足小根堆,将父节点值插入待插入节点中
            queue[k] = e;
            //待比较位置就指向了父节点
            k = parent;
        }
        queue[k] = x;
    }

删除数据
图示:
在这里插入图片描述
因为是小根堆,其堆顶元素最小,所以删除的为堆顶的元素。删除堆顶元素过程,首先记录0号下标的位置,并用最后一个元素替换0号下标的元素,当前的小根堆可能被破坏,需要对堆进行调整,从k指定的位置开始,将逐层向下与当前的左右孩子中较小的进行交换,直到x小于或者等于左右孩子中的任何一个为止。

源码如下:

//删除节点 从上往下进行调整 待比较的位置,待调整的元素x
private void siftDownUsingComparator(int k, E x) { 
   
        //将size/2 ,从上往下调整只需要比较到size/2即可,
        //因为size/2时已经到了叶子节点,无需再调整。
        int half = size >>> 1;
        while (k < half) { 
   
            //找到当前节点的左孩子
            int child = (k << 1) + 1;
            Object c = queue[child];
            int right = child + 1;//右孩子节点下标
            //找到左右孩子最小的节点,将位置记录到child,值记录在c上
            if (right < size && comparator.compare((E) c, (E) queue[right]) > 0)
                c = queue[child = right];
            //当左右孩子最小的都大于父节点x值,满足小根堆,结束调整过程
            if (comparator.compare(x, (E) c) <= 0)
                break;
            //最小的孩子节点小于父节点,不满足小根堆,需要进行调整
            queue[k] = c;
            k = child;
        }
        queue[k] = x;
    }
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/190151.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • TASKLIST命令操作[通俗易懂]

    TASKLIST命令操作[通俗易懂]        Tasklist与Taskkill是xp下很强大的命令工具。之所以强大,并不完全因为我们所熟悉的Taskkill/f/im或是Taskkill/f/pid的用法,而是因为它们的筛选器。我们先来看一下Tasklist与Taskkill的基本语法及使用:  显示运行在本地或远程计算机上的所有任务的应用程序和服务列表,带有过程ID

    2022年5月8日
    269
  • 随机数生成算法

    随机数生成算法转自:https://www.cnblogs.com/ECJTUACM-873284962/p/6926203.html1、蒙特卡洛法  蒙特卡罗方法又称统计模拟法、随机抽样技术,是一种随机模拟方法,以概率和统计理论方法为基础的一种计算方法,是使用随机数(或更常见的伪随机数)来解决很多计算问题的方法。将所求解的问题同一定的概率模型相联系,用电子计算机实现统计模拟或抽样,以获得问题的近似…

    2022年7月26日
    6
  • win10安装nfs服务器并实现liunx访问「建议收藏」

    1、测试环境    宿主操作系统:win1064位    虚拟机操作系统:ubuntuserver18.042、安装nfs服务器   a.下载安装haneWIN;   b.右键以管理员身份运行nfsserver(若不以管理员身份打开,设置项均为灰色不可设),切换到“Exports”标签页,点击“Editexportsfile"进行编辑,如下图所示。比如"E:\Video"为win1…

    2022年4月13日
    740
  • java在线播放_Java实现视频在线播放flv视频

    java在线播放_Java实现视频在线播放flv视频1、首先使用Idea创建一个SpringBoot项目。2、在application.properties文件下加入以下代码,进行DEBUG日志输出,配置pom.xml文件:#logging日志配置logging.level.root=WARNlogging.level.org.springframework.web=DEBUG4.0.0com.exampledemo0.0.1-SNAPSHOTj…

    2022年9月22日
    2
  • python常用库大全一览_python常用扩展库

    python常用库大全一览_python常用扩展库转载地址:原文地址链接Python常用库大全-尹成的技术博客-CSDN博客window._ty_rum&&window._ty_rum.server||function(t){functione(t){J&&(W.e[t]||(W.e[t]=[])).push(u())}func

    2025年7月26日
    2
  • 前端实现异步的几种方式_redux是什么

    前端实现异步的几种方式_redux是什么1.什么是Saga?实际上,这个术语出自康奈尔大学的一篇论文:http://www.cs.cornell.edu/andru/cs711/2002fa/reading/sagas.pdf最初这篇论文是为了解决分布式系统中的LLT(LongLivedTransaction),也就是长时运行事务的数据一致性问题的。这么说有点抽象,我们来举个具体的例子:假如你在一个在线订票系统上订了一张…

    2022年9月18日
    3

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

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