优先级队列(堆)

优先级队列(堆)队列是一种先进先出 FIFO 的数据结构 但有些情况下 操作的数据可能带有优先级 一般出队列时 可能需要优先级高的元素先出队列 该中场景下 使用队列显然不合适 比如 在手机上玩游戏的时候 如果有

大家好,欢迎来到IT知识分享网。

目录

1.定义

2.性质

3.分类

4.示例代码

5.PriorityQueue(小根堆)-线程不安全

1. 使用注意事项

2. 构造器介绍

3. 使用示例

4. 小根堆改为大根堆

5.如果数据类型为Integer这样的包装类(其源码不能更改),怎么改大根堆?

6.总结

7.扩容问题

​8.常用方法


1.定义

队列是一种先进先出(FIFO)的数据结构,但有些情况下,操作的数据可能带有优先级,一般出队列时,可能需要优先级高的元素先出队列,该中场景下,使用队列显然不合适,比如:在手机上玩游戏的时候,如果有来电,那么系统应该优先处理打进来的电话。

在这种情况下,我们的数据结构应该提供两个最基本的操作,一个是返回最高优先级对象,一个是添加新的对象。这种数据结构就是优先级队列(Priority Queue)

简单而言,底层其实就是一颗二叉树,只不过这棵二叉树比较特殊,为完全二叉树,在这个基础上实现的一种结构,这种结构叫做堆。

2.性质

从上面的定义中,我们可以得到堆的性质:

1.堆中某个节点的值总是不大于或不小于其父节点的值;

2.堆总是一棵完全二叉树。

并且从堆的概念可知,堆是一棵完全二叉树,因此可以层序的规则采用顺序的方式来高效存储。

3.分类

小根堆:根节点比左右孩子都小                                       大根堆:根节点比左右孩子都大

     优先级队列(堆)                                             优先级队列(堆)

4.示例代码

在本次示例代码中,我们将使用向下调整的方法,从最后一个子树的根节点开始局部调整为大根堆,直到整个数组都满足大根堆的性质。

1. 在实现这个数据结构的过程中需要解决的问题两点:

1.1 如何找到最后一课子树的根节点  —-(array.length-1-1)/2

优先级队列(堆)

1.2 每颗子树调整的时候,结束位置在哪里  —-  location < array.length

2.示例代码

2.1 大根堆

/ * 大根堆 * * @param array */ public void createHeap(int[] array) { for (int parent = (array.length - 1 - 1) / 2; parent >= 0; parent--) { shiftDown(parent, array); } } / * @param parent 每颗子树的根节点 * @param array 每颗子树调整的结束位置 location < array.length */ public void shiftDown(int parent, int[] array) { int child = 2 * parent + 1; while (child < array.length) { //child + 1 < array.length 保证右孩子 if (child + 1 < array.length && array[child] < array[child + 1]) { child++; } //child下标一定为左右孩子 最大值的下标 if (array[parent] < array[child]) { int tmp = array[parent]; array[parent] = array[child]; array[child] = tmp; parent = child; child = 2 * parent + 1; }else{ break; } } }

2.2 小根堆

/ * 小根堆 * * @param array */ public void createHeap(int[] array) { for (int parent = (array.length - 1 - 1) / 2; parent >= 0; parent--) { shiftDown(parent, array); } } / * @param parent 每颗子树的根节点 * @param array 每颗子树调整的结束位置 location < array.length */ public void shiftDown(int parent, int[] array) { int child = 2 * parent + 1; while (child < array.length) { //child + 1 < array.length 保证右孩子 if (child + 1 < array.length && array[child] > array[child + 1]) { child++; } //child下标一定为左右孩子 最小值的下标 if (array[parent] > array[child]) { int tmp = array[parent]; array[parent] = array[child]; array[child] = tmp; parent = child; child = 2 * parent + 1; }else{ break; } } }

2.3 注意点(以大根堆为例)

优先级队列(堆)

2.4 建堆的时间复杂度-O(N)

优先级队列(堆)

5.PriorityQueue(小根堆)-线程不安全

其中,PriorityBlockingQueue-线程安全。下面是对PriorityQueue的使用介绍。

1. 使用注意事项

  • 1. 使用时必须导入PriorityQueue所在的包,即:importjava.util.PriorityQueue;
  • 2. PriorityQueue中放置的元素必须要能够比较大小,不能插入无法比较大小的对象,否则会抛出ClassCastException异常
  • 3. 不能插入null对象,否则会抛出NullPointerException
  • 4. 没有容量限制,可以插入任意多个元素,其内部可以自动扩容
  • 5. 插入和删除元素的时间复杂度为O(logN)
  • 6. PriorityQueue底层使用了堆数据结构
  • 7. PriorityQueue默认情况下是小堆—即每次获取到的元素都是最小的元素

2. 构造器介绍

构造器  

PriorityQueue(Collection extends E> c) 用一个集合来创建优先级队列

功能介绍
PriorityQueue() 创建一个空的优先级队列,默认容量是11
PriorityQueue(int initialCapacity) 创建一个初始容量为initialCapacity的优先级队列,注意:initialCapacity不能小于1,否则会抛IllegalArgumentException异常

3. 使用示例

class Student { private int age; public Student(int age) { this.age = age; } } public class Main { public static void main(String[] args) { PriorityQueue<Student> priorityQueue = new PriorityQueue<>(); priorityQueue.offer(new Student(20)); priorityQueue.offer(new Student(10)); } }

当我们在IDEA中运行这个代码的时候,代码会报错,报错为:

优先级队列(堆)

为什么会出现这个情况呢?我们需要了解在main方法中的三个代码的运行逻辑:

1.PriorityQueue priorityQueue = new PriorityQueue<>();

1.点击PriorityQueue查看源码,查看其默认的数组容量为11

优先级队列(堆)

  • 点PriorityQueue,查看构造方法

优先级队列(堆) 2.点击this,查看方法,我们需要先了解两个变量的含义

优先级队列(堆)

优先级队列(堆)

  • 比较器设置为null, 当没有传入比较器的时候,那么相当于只是对queue数组进行实例化了

优先级队列(堆)

 2. priorityQueue.offer(new Student(20));

 1.执行第二句话,第一次没有产生比较,直接放入零下标

优先级队列(堆)

 3.priorityQueue.offer(new Student(10));

1.第二次比较

优先级队列(堆)

2.我们需要进入sifUp()方法进行查看,我们发现如果传入的比较器为空,就会调用siftUpComparable(k, x);

优先级队列(堆)

3.点击siftUpComparable(k, x);方法,我们会发现会调用key的compareTo方法,但是在Student类中,并没有这个方法,所以会抛出异常

优先级队列(堆)

 4.所以我们需要给Student类,重写comparabTo方法

class Student implements Comparable<Student> { private int age; public Student(int age) { this.age = age; } @Override public int compareTo(Student o) { return this.age - o.age; } } public class Main { public static void main(String[] args) { PriorityQueue<Student> priorityQueue = new PriorityQueue<>(); priorityQueue.offer(new Student(20)); priorityQueue.offer(new Student(10)); } }

5.那么在第三行代码中,就会调用Student类的comparabTo方法,并且在这个方法中,没有进入循环,就进行交换了。

 3.priorityQueue.offer(new Student(10));

优先级队列(堆)

4. 小根堆改为大根堆

在1.使用注意事项中,我们说过:

7. PriorityQueue默认情况下是小堆—即每次获取到的元素都是最小的元素

 那我们应该怎么把其改成大根堆呢?以上面的Student类为例,我们可以改变comparabTo方法,将比较器的相减更换即可,将this.age – o.age换成return o.age- this.age即可。

class Student implements Comparable<Student> { private int age; public Student(int age) { this.age = age; } @Override public int compareTo(Student o) { return o.age- this.age; } } public class Main { public static void main(String[] args) { PriorityQueue<Student> priorityQueue = new PriorityQueue<>(); priorityQueue.offer(new Student(20)); priorityQueue.offer(new Student(10)); } }

5.如果数据类型为Integer这样的包装类(其源码不能更改),怎么改大根堆?

优先级队列(堆)

  • 传入自己添加定义的比较器:直接实现Comparator接口,然后重写该接口中的compare方法即

优先级队列(堆)

 1.自定义类IntCmp,实现Comparator<Integer>接口

class IntCmp implements Comparator<Integer>{ @Override public int compare(Integer o1, Integer o2) { return o2-o1; } }PriorityQueue<Integer> priorityQueue1 = new PriorityQueue<>(new IntCmp());

2.匿名内部类

PriorityQueue<Integer> priorityQueue2 = new PriorityQueue<>(new Comparator<Integer>() { @Override public int compare(Integer o1, Integer o2) { return o2.compareTo(o1); } });

3. lambda表达式

PriorityQueue<Integer> priorityQueue3 = new PriorityQueue<>((x,y)->{return x.compareTo(y);}); PriorityQueue<Integer> priorityQueue4 = new PriorityQueue<>((x,y)-> x.compareTo(y));

6.总结

  • 1.当没有传数组容量的时候,默认11;
  • 2.当没有传入比较器的时候,他必须是可比较的;
  • 3.优先使用的是比较器来比较;

7.扩容问题

小于64,二倍扩容,大于64,1.5扩容

优先级队列(堆) 8.常用方法

函数名                                              功能介绍
boolean offer(E e) 插入元素e,插入成功返回true,如果e对象为空,抛出NullPointerException异常,时间复杂度为(O(logN)) ,注意:空间不够时候会进行扩容
E peek() 获取优先级最高的元素,如果优先级队列为空,返回null
E poll() 移除优先级最高的元素并返回,如果优先级队列为空,返回null
int size() 获取有效元素的个数
void clear() 清空
boolean isEmpty() 检测优先级队列是否为空,空返回true

其中需要注意:

1.offer-堆的插入

  • 1.插入一个数到数组最后面,如果数组不够大,先扩容;
  • 2.向上调整

2.poll-堆的删除

  • 1.判断是否非空,交换0下标和数组最后一个的值;
  • 2.堆的有效数据个数减一;
  • 3.向下调整

以上就是优先级队列的所有内容了。

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/112354.html

(0)
上一篇 2026-01-18 22:20
下一篇 2026-01-18 22:33

相关推荐

发表回复

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

关注微信