当前位置:   article > 正文

【Java】PriorityQueue优先级队列(堆)+比较器详解

【Java】PriorityQueue优先级队列(堆)+比较器详解

目录

铺垫

建堆

基本公式

调整为大(小)根堆

堆的增加

堆的删除 

 正文:

比较

重写基类的equals()比较方法:

内部比较器——Comparable.compareTo(E o):

外部比较器——Comparator.compare(T o1, T o2):

源码粗略分析

常用构造方法

实现Comparable接口 

实现Comparator接口

改为大根堆

对于基本类型:

对于引用类型:

比较器快速写法


铺垫

优先级队列的底层是一个数组,但这个数组又比较特殊——二叉树通过层序遍历存放到该数组的数据。这样存放的方式就是这种数据结构。

大根堆:如果在堆中,每个根结点的值大于两个孩子结点的值。(对左右孩子无要求)

小根堆:如果在堆中,每个根结点的值小于两个孩子结点的值。(对左右孩子无要求)


建堆

基本公式

调整为大(小)根堆

在一个一维数组中:

1.对于每一个根结点都要调整为大小根堆。

2.调整时,先要找到较大的孩子

3.然后二者交换,继续向下调整该根结点下面的结点。

4.调整时可能会存在越界的情况,孩子结点可能会越界。

  1. public void createHeap() {
  2. //建大根堆
  3. //对每一个根节点使用向下调整算法
  4. //最后一个孩子 c = length - 1
  5. //c = 2 * p +1
  6. //p = (c - 1)/2
  7. for (int parent = (array.length - 1 -1) / 2; parent >= 0; parent--) {
  8. shiftDown(parent, usedSize);
  9. }
  10. }
  11. //向下调整
  12. private void shiftDown(int parent, int length) {
  13. int child = 2 * parent + 1;
  14. while (child < length) {
  15. //先判断孩子是否越界
  16. if (child < length - 1 && array[child] < array[child + 1]) {
  17. child++;
  18. }
  19. if (array[parent] < array[child]) {
  20. swap(array, parent, child);
  21. parent = child;
  22. child = 2 * parent + 1;
  23. } else {
  24. break;
  25. }
  26. }
  27. }
  28. private void swap(int[] array, int x, int y) {
  29. int tmp = array[x];
  30. array[x] = array[y];
  31. array[y] = tmp;
  32. }

堆的增加

在堆里添加完数据后,应该还要保证堆为大(小)根堆。

1.判断堆是否满了,满了扩容。

2.把添加的数据放到数组的最后面。

3.找到最后一个结点的父亲结点,向上调整。

4.(建大堆)如果父亲结点比孩子结点小,就调整,然后,父亲结点指向孩子结点,重新计算父亲结点,直到最后一个孩子调整完。

5.useSized++

 

堆的删除 

优先级队列,是对于某些数据优先操作的结构,所以很自然的我们要优先对堆顶数据进行删除。

1.判断堆是否为空,为空抛出异常。

2.堆顶数据与最后一个结点进行交换。

3.对堆顶使用一次向下调整即可恢复成大(小)根堆。

4.useSized--

  1. public void pop() {
  2. if (isEmpty()) {
  3. return;
  4. }
  5. swap(array, 0, usedSize - 1);
  6. usedSize--;
  7. shiftDown(0, usedSize);
  8. }

 正文:

有了以上概念,我们就能更容易理解PriorityQueue的源码部分的实现。

比较

在生成大小根堆的时候,数据是通过比较大小来排序的。所以PriorityQueue底层必定有比较数据大小的东西。

重写基类的equals()比较方法

所有的类都是继承Object类的,Object类中就有equals()方法。

重写equals()方法用到这里显然不合适,因为该方法只能比较二者是否想等,无法比较出大小关系

内部比较器——Comparable<E>.compareTo(E o):

Comparable是JDK提供的泛型接口类,源码如下:

  1. //Compareble是java.lang中的接口类,可以直接使用
  2. public interface Comparable<E> {
  3. // 返回值:
  4. // < 0: 表示 this 指向的对象小于形参 o 指向的对象
  5. // == 0: 表示 this 指向的对象等于形参 o 指向的对象
  6. // > 0: 表示 this 指向的对象大于形参 o 指向的对象
  7. int compareTo(E o);
  8. }
  9. //对象支持自己与自己比较
  10. //实现了该接口的类就可以自动进行排序
  11. //集合通过Collection.sort()排序,数组通过Arrays.sort()进行排序

该方法可以比较出数据的大小,所以我们可以在定义类的时候实现该接口,重写方法就可以比较。

外部比较器——Comparator<T>.compare(T o1, T o2):

  1. //Comparator是java.util 包中的泛型接口类,使用时必须导入对应的包
  2. public interface Comparator<T> {
  3. // 返回值:
  4. // < 0: 表示 o1 指向的对象小于 o2 指向的对象
  5. // == 0: 表示 o1 指向的对象等于 o2 指向的对象
  6. // > 0: 表示 o1 指向的对象等于 o2 指向的对象
  7. int compare(T o1, T o2);
  8. }
  9. //使用Comparator的情况:
  10. //1.没有实现Comparable接口
  11. //2.实现了Comparable接口,但是其中的compareTo方法不符合自己的预期


源码粗略分析

  1. public static void main(String[] args) {
  2. PriorityQueue<Integer> priorityQueue1= new PriorityQueue<>();
  3. priorityQueue1.offer(4);
  4. priorityQueue1.offer(6);
  5. priorityQueue1.offer(3);
  6. System.out.println(priorityQueue1);
  7. }

 

若是其他引用类型,就不可以这样offer()了,在offer()第二个数据的时候就会抛异常。

  1. class Student {
  2. public String name;
  3. public int age;
  4. public Student (int age) {
  5. this.age = age;
  6. }
  7. }
  8. public class Test {
  9. public static void main(String[] args) {
  10. PriorityQueue<Integer> priorityQueue1= new PriorityQueue<>();
  11. priorityQueue1.offer(4);
  12. priorityQueue1.offer(6);
  13. priorityQueue1.offer(3);
  14. System.out.println(priorityQueue1);
  15. PriorityQueue<Student> priorityQueue2 = new PriorityQueue<>();
  16. priorityQueue2.offer(new Student(20));
  17. priorityQueue2.offer(new Student(22));
  18. }
  19. }


常用构造方法

第一种:什么都不传,那么默认空间大小为11,无比较器

第二种:只传空间大小,此时空间大小为所传的值

第三种:只传比较器,下面详解比较器作用


实现Comparable接口 

  1. class Student implements Comparable<Student> {
  2. public String name;
  3. public int age;
  4. public Student (int age) {
  5. this.age = age;
  6. }
  7. @Override
  8. public int compareTo(Student o) {
  9. return this.age - o.age;
  10. }
  11. @Override
  12. public String toString() {
  13. return "Student{" +
  14. "name='" + name + '\'' +
  15. ", age=" + age +
  16. '}';
  17. }
  18. }
  19. public class Test {
  20. public static void main(String[] args) {
  21. PriorityQueue<Integer> priorityQueue1= new PriorityQueue<>();
  22. priorityQueue1.offer(4);
  23. priorityQueue1.offer(6);
  24. priorityQueue1.offer(3);
  25. System.out.println(priorityQueue1);
  26. PriorityQueue<Student> priorityQueue2 = new PriorityQueue<>();
  27. priorityQueue2.offer(new Student(20));
  28. priorityQueue2.offer(new Student(22));
  29. priorityQueue2.offer(new Student(23));
  30. System.out.println(priorityQueue2);
  31. }
  32. }

 当实现了Comparable接口后,重写CompareTo方法后,此时就可以比较引用类型了。

此时它应该执行到一下源码部分。

实现Comparator接口

  1. class Student {
  2. public String name;
  3. public int age;
  4. public Student (int age) {
  5. this.age = age;
  6. }
  7. @Override
  8. public String toString() {
  9. return "Student{" +
  10. "name='" + name + '\'' +
  11. ", age=" + age +
  12. '}';
  13. }
  14. }
  15. class StuCompare implements Comparator<Student> {
  16. @Override
  17. public int compare(Student o1, Student o2) {
  18. return o2.age - o1.age;
  19. }
  20. }
  21. public class Test {
  22. public static void main(String[] args) {
  23. PriorityQueue<Integer> priorityQueue1= new PriorityQueue<>();
  24. priorityQueue1.offer(4);
  25. priorityQueue1.offer(6);
  26. priorityQueue1.offer(3);
  27. System.out.println(priorityQueue1);
  28. PriorityQueue<Student> priorityQueue2 = new PriorityQueue<>(new StuCompare());
  29. priorityQueue2.offer(new Student(20));
  30. priorityQueue2.offer(new Student(22));
  31. priorityQueue2.offer(new Student(23));
  32. System.out.println(priorityQueue2);
  33. }
  34. }

通过自定义的比较器,并且通过传入比较器的构造方法后,我们也可以比较。

此时它应该执行到下面的源码部分。


改为大根堆

因为源码默认建小根堆,如果我们想建大根堆,有了以上源码分析,我们不难发现,关键就在比较器上,我们只需要改变比较规则,就可以建立大根堆。

对于基本类型:

比如Integer,我们无法到源码中改变比较规则,所以只能传比较器,这样会优先使用有比较器的方法。

  1. class IntCompare implements Comparator<Integer> {
  2. @Override
  3. public int compare (Integer o1, Integer o2) {
  4. return o2.compareTo(o1);
  5. }
  6. }
  7. public class Test {
  8. public static void main(String[] args) {
  9. PriorityQueue<Integer> priorityQueue1= new PriorityQueue<>(new IntCompare());
  10. priorityQueue1.offer(4);
  11. priorityQueue1.offer(6);
  12. priorityQueue1.offer(3);
  13. System.out.println(priorityQueue1);
  14. }
  15. }

 

对于引用类型:

若该引用类型实现了Comparable接口,我们直接修改compareTo

若没有,则修改比较器中的compare

  1. class Student implements Comparable<Student> {
  2. public String name;
  3. public int age;
  4. public Student (int age) {
  5. this.age = age;
  6. }
  7. @Override
  8. public int compareTo(Student o) {
  9. return o.age - this.age;
  10. }
  11. @Override
  12. public String toString() {
  13. return "Student{" +
  14. "name='" + name + '\'' +
  15. ", age=" + age +
  16. '}';
  17. }
  18. }
  19. public class Test {
  20. public static void main(String[] args) {
  21. PriorityQueue<Student> priorityQueue2 = new PriorityQueue<>();
  22. priorityQueue2.offer(new Student(20));
  23. priorityQueue2.offer(new Student(22));
  24. priorityQueue2.offer(new Student(23));
  25. System.out.println(priorityQueue2);
  26. }
  27. }

 ​​​​​

 

  1. class Student {
  2. public String name;
  3. public int age;
  4. public Student (int age) {
  5. this.age = age;
  6. }
  7. @Override
  8. public String toString() {
  9. return "Student{" +
  10. "name='" + name + '\'' +
  11. ", age=" + age +
  12. '}';
  13. }
  14. }
  15. class StuCompare implements Comparator<Student> {
  16. @Override
  17. public int compare(Student o1, Student o2) {
  18. return o2.age - o1.age;
  19. }
  20. }
  21. public class Test {
  22. public static void main(String[] args) {
  23. PriorityQueue<Student> priorityQueue2 = new PriorityQueue<>(new StuCompare());
  24. priorityQueue2.offer(new Student(20));
  25. priorityQueue2.offer(new Student(22));
  26. priorityQueue2.offer(new Student(23));
  27. System.out.println(priorityQueue2);
  28. }
  29. }


比较器快速写法

  1. class Student {
  2. public String name;
  3. public int age;
  4. public Student (int age) {
  5. this.age = age;
  6. }
  7. @Override
  8. public String toString() {
  9. return "Student{" +
  10. "name='" + name + '\'' +
  11. ", age=" + age +
  12. '}';
  13. }
  14. }
  15. public class Test {
  16. public static void main(String[] args) {
  17. //这里直接new了一个比较器传过去了
  18. PriorityQueue<Student> priorityQueue2 = new PriorityQueue<>(new Comparator<Student>() {
  19. @Override
  20. public int compare(Student o1, Student o2) {
  21. return o1.age - o2.age;
  22. }
  23. });
  24. priorityQueue2.offer(new Student(20));
  25. priorityQueue2.offer(new Student(22));
  26. priorityQueue2.offer(new Student(23));
  27. System.out.println(priorityQueue2);
  28. }
  29. public static void main1(String[] args) {
  30. MyPriorityQueue heap = new MyPriorityQueue();
  31. int[] arr = {27, 15, 19, 18, 28, 34, 65, 49, 25, 37};
  32. heap.initArray(arr);
  33. heap.createHeap();
  34. heap.push(100);
  35. //heap.pop();
  36. PriorityQueue<Integer> priorityQueue1= new PriorityQueue<>();
  37. priorityQueue1.offer(4);
  38. priorityQueue1.offer(6);
  39. priorityQueue1.offer(3);
  40. System.out.println(priorityQueue1);
  41. }
  42. }

有什么错误评论区指出,希望可以帮到你。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家小花儿/article/detail/355108?site
推荐阅读
相关标签
  

闽ICP备14008679号