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)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • SQL server2019安装教程[通俗易懂]

    下载必备由于安装文件太大,所以没有办法上传,各位就请自行下载安装步骤点击sql2019安装包选择基本浏览安装到指定的位置,点击安装下载下载完成之后就是这个界面,然后点击自定义选择下一步等待扫描完成后选择选择下一步之后再选择第一个“执行SQLSERVER2019的全新安装”,然后点击下一步勾选第一个接受条款,继续下一步在“实例功能”里面勾选你需要…

    2022年4月7日
    59
  • c#实现图片gif去水印「建议收藏」

    做项目时候会遇到在网络上爬的源文件,png图片或者动画gif背景都带有水印,“百度出品”“不得转载”等等,这样出来的文件放在项目里面当做自己的资源来用肯定是不可以的,现在就来用lockbits替换背景的颜色,实现水印消除的目的。话不多述,上图:处理前:这是处理之前的图,其实底部的“baidu汉语“看着并不是很明显(仔细看),仍然需要把字体的背部水印去掉,这里开始用lockbits来去水印了。处理

    2022年4月9日
    135
  • 快速刷微信小程序访问量和浏览量

    快速刷微信小程序访问量和浏览量1、先开发小程序,小程序需要有亮点,毕竟新颖(这样别人才更好去点击查看)。2、流量主开通的条件是独立访客(UV)不低于1000,1000人说多不多,说少也不少,因为小程序是没有链接的,是不可以进行一个流量刷取的,独立访客是需要1000个实实在在的用户,并不是访问量。3、开发好小程序之后,自己要为自己的小程序做好宣传,前提小程序需要做的完美,小程序一定要做分享功能,将小程序分享到个人、微信群、朋友圈,这样估计很容易就达到几百了。4、后续可以去各种论坛发帖,切记不要恶意刷用户量,会导致小程序被封。5

    2022年9月18日
    0
  • networkmanager服务是否启动_nmcli开热点

    networkmanager服务是否启动_nmcli开热点一、简介NetworkManager服务是管理和监控网络设置的守护进程,CentOS7更加注重使用NetworkManager服务来实现网络的配置和管理,CentOS7以前是通过network服务管理网络,以后的版本所有网络管理和设置统一由NetworkManager服务来维护。它是一个动态的,事件驱动的网络管理服务。NetworkManager在系统中的管理工具为nmcli二、nmcli简单使用2.1、查看#查看所有硬件设备信息nmclideviceshow#查看制定设备信息

    2022年10月4日
    0
  • Xshell安装docker「建议收藏」

    Xshell安装docker「建议收藏」docker基本组成镜像(image):docker镜像好比一个模板,可以通过这个模板创建容器服务,例如:tomcat镜像===>run===>tomcat01容器(提供服务器)通过这个镜像可以创建多个容器(最终服务或项目在容器中运行)容器(container):docker利用容器技术,独立运行一个或一组应用,通过镜像来创建。启动、停止、删除基本命令目前就可以把这个容器理解为就是一个简易的linux系统仓库(repository):存放镜像的地方,类似maven中央仓库仓库

    2022年9月9日
    0
  • placeholder的样式设置

    placeholder的样式设置在input框中有时想将输入的字和placeholder设为不同的颜色或其它效果,这时就可以用以下代码来对placeholder进行样式设置了。::-webkit-input-placeholder

    2022年7月4日
    25

发表回复

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

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