自学内容网 自学内容网

Java-数据结构-优先级队列(堆)-(二) (゚▽゚*)

文本目录:

❄️一、PriorityQueue的常用接口:

          ➷ 1、PriorityQueue的特性:

          ➷ 2、使用PriorityQueue的注意:

           ➷ 3、PriorityQueue的构造:

    ☞  1、无参数的构造方法:

     ☞  2、有参数的构造方法:

     ☞  3、带一个参数comparator的构造方法:

     ☞  4、用集合的构造方法:

           ➷ 4、PriorityQueue的插入和删除等方法:

    ☞  1、插入方法(offer(E e)):

    ☞  2、删除方法( poll() ) 和 查看优先级最高的( peek() ):

     ☞  3、获得有效元素的个数( size() ):

     ☞  4、判空( isEmpty() ):

 ❄️二、堆的应用:

          ➷ 1、PriorityQueue的实现:

          ➷ 2、堆排序:

          ➷ 3、Top-k问题:

  ❄️总结:


❄️一、PriorityQueue的常用接口:

          ➷ 1、PriorityQueue的特性:

        在 Java 集合中给我们提供了两种优先级队列:PriorityQueuePriorityBlockingQueue,它们肯定是有区别的,我们的 PriorityQueue  线程不安全PriorityBlockingQueue 线程安全的 ,我们这次呢主要介绍 PriorityQueue 

         我们还是请出我们的老朋友:

我们可以看到我们的  PriorityQueue  是实现 Queue 这个接口的。


          2、使用PriorityQueue的注意:

在我们使用  PriorityQueue  的时候我们要注意:


1、在我们使用  PriorityQueue  的时候我们需要导入包:

所以我们一定要导包。


 2、 PriorityQueue  中存放的数据必须是可以比较大小的,不能插入无法比较大小的对象,否则          会抛出 ClassCastException 异常。

     因为我们的优先级队列需要比较对象之后才进行存入,这时候我们比较时候要强转成 Comparable 这个,我们来看看这个的底层代码:

所以我们必须要存入可以比较的对象。 


3、我们不能插入 null 对象,否则会抛出 NullPointerException 这个异常。我们来看:

存入 null 就会抛出异常。 


4、没有容量限制,可以插入任意多个元素,其内部可以自动扩容。


5、插入和删除元素的时候的时间复杂度为O(logN)


6、 PriorityQueue 底层使用了堆的数据结构。


7、 PriorityQueue 默认情况下是 小堆 ------ 就是每次获得堆里最小的元素


           3、PriorityQueue的构造:

     我们的 优先级队列 的构造方法常见的有几种构造方法,我们来一一介绍来看,但是呢在我们介绍构造方法之前呢,我们来看看 优先级队列 的底层代码的一些代码:


    ☞  1无参数的构造方法:

我们来看看这个无参构造的底层代码:

public PriorityQueue(int initialCapacity) {
        this(initialCapacity, null);
    }

 这里呢我们调用的两个参数的构造方法,我们的 容量是默认的 11 个容量。


     ☞  2有参数的构造方法:

   我们同样先来看看底层的代码:

public PriorityQueue(int initialCapacity) {
        this(initialCapacity, null);
    }

这个呢就是创建一个 容量 为 initialCapacity 这个容量的 优先级队列

这里调用的同样是两个参数的构造方法。

这里要注意我们传入的参数不能 < 1。


     ☞  3带一个参数comparator的构造方法:

public PriorityQueue(Comparator<? super E> comparator) {
        this(DEFAULT_INITIAL_CAPACITY, comparator);
    }

这里同样是调用带两个参数的构造方法,我们传入的是一个比较器。容量 还是 11 这个默认参数


接下来我们来看看对于这几个构造方法所调用的 两个参数的构造方法是什么样的:

public PriorityQueue(int initialCapacity,
                         Comparator<? super E> comparator) {
        if (initialCapacity < 1)
            throw new IllegalArgumentException();
        this.queue = new Object[initialCapacity];
        this.comparator = comparator;
    }

这个就是我们的底层代码。 

这里我们会直接把传入的参数直接给到 queue 这个的容量。之后把第二个参数给到我们的比较器 


     ☞  4用集合的构造方法:

我们还是先来看看底层代码:

public PriorityQueue(Collection<? extends E> c) {
        if (c instanceof SortedSet<?>) {
            SortedSet<? extends E> ss = (SortedSet<? extends E>) c;
            this.comparator = (Comparator<? super E>) ss.comparator();
            initElementsFromCollection(ss);
        }
        else if (c instanceof PriorityQueue<?>) {
            PriorityQueue<? extends E> pq = (PriorityQueue<? extends E>) c;
            this.comparator = (Comparator<? super E>) pq.comparator();
            initFromPriorityQueue(pq);
        }
        else {
            this.comparator = null;
            initFromCollection(c);
        }
    }

这里就是我们传入的集合会直接给到我们的数组中,来构造。 


           4、PriorityQueue的插入和删除等方法:

    这里呢我们只介绍一下关于插入方法的操作的流程,其余的呢,我们看一下演示结构,因为和我们上次博客实现的堆的方法原理是一样的。


    ☞  1、插入方法(offer(E e)):

     我们在上一个博客中已经介绍了堆的插入是如何实现的了,这里呢是类似的,我们在这里呢,我们直接来看看这个方法在Java中的执行流程图:  

    这里的 compareTo 这个方法默认的是 this.val - o.val 在上面就是 15 - 11,这样得到的就是小根堆如果我们想要实现大根堆,就把其变成 o.val - this.val 就可以了,我们来看看如何实现的: 


    ☞  2、删除方法( poll() ) 和 查看优先级最高的( peek() ):

我们来看看删除优先级最高的栈顶数据:


     ☞  3、获得有效元素的个数( size() )


     ☞  4、判空( isEmpty() )


 ❄️二、堆的应用:

          ➷ 1、PriorityQueue的实现:

     我们的 PriorityQueue 的底层使用的就是堆来实现的。


          ➷ 2、堆排序:

     堆排序就是使用堆的思想来实现排序,我们的排序有两种排序,升序 和 降序,我们分为来那个步骤来进行堆排序:

    1、建堆:

升序:建大堆。

降序:建小堆。

    2、利用堆删除的思想来进行排序:

      我们来使用堆排序 创建升序 来进行介绍如何建成堆排序。

      就是把一个 大根堆 的 第一个数据和最后一个数据进行交换,之后我们把交换完的第一个数据向下调整,再次变成大根堆但是要注意,我们交换完之后的位置不用再调整,我们这时候要使用 一个 临时变量进行记录 我们 向下调整 的判断是否结束的位置。我们来看看流程图:

这个理解之后呢,我们来看看这个对于 使用大根堆来实现升序 的代码要如何实现: 

 private void shiftDown(int parent,int usedSize) {
        int child = parent * 2 + 1;//parent根节点的左子树的节点

        while (child < usedSize) {

            //判断有没有右子树,如果有就把 child 设置为最大的值的下标
            if (child + 1 < usedSize && elem[child + 1] > elem[child]) {
                //右子树比左子树大把 child + 1,就是右子树
                child += 1;
            }

            if (elem[parent] < elem[child]) {
                //进行交换
                swap(elem,child,parent);
                //调整完,把 parent 和 child 进行调整位置
                parent = child;
                child = parent * 2 + 1;
            }else {
                //这是 parent下标的值大于child,跳出
                break;
            }
        }
    }

    private void swap(int[] elem,int i,int j) {
        int tmp = elem[i];
        elem[i] = elem[j];
        elem[j] = tmp;
    }
//这是我们的上次博客的代码。我们自实现的建立大根堆

public void HeapSort() {
        //我们把第一个数据和最后一个数据进行交换
        //之后我们把 0 下标的值进行向下排序,这样之后我们会把大值放后面,小值放堆的前面
        int end = usedSize - 1;
        while (end > 0){
          swap(elem,0,end);
          shiftDown(0,end);
          end--;
        }
    }

     这就是我们的使用 大根堆进行排升序。对于 降序我们要使用建小堆来实现,我们和 用大堆实现升序就是思想是差不多的。


          ➷ 3、Top-k问题:

这个也是我们出现过的面试题。

                      ▶  Top-k的传送门:

                                       Top-k问题的最小k个数

     这个问题呢,就是求数据集合中前 k 个最大的元素或者是最小的元素,一般情况下呢都是数据比较多的情况下。

     我们对于这个问题呢,我们有三种不同的做法,假设我们有N个数据,我们来看:

1、直接排序,直接找到前 k 个。

2、建立一个我们有 N 个数据的堆,之后出前k个数据。

      就是如果求前 k 个最小的数据就是建小根堆。求前 k 个最大的数据就是建大根堆。

3、 比如我们求前k个最小的元素,我们执行:建立前k个大根堆,之后我们把N-K个元素和堆顶元         素进行比较,如果比堆顶的元素小,就把堆顶的数据删除,之后再把这个小的元素入堆

       对于求前k个最大的元素就是反之。(这个方法就是我们Top-k问题的最好的解决办法)

我们来看流程图:

    这样呢就是我们的最好的解决 Top-k 问题的思路了,之后理解这个之后呢,我们来看看我们的Top-k的代码如何编写:

class IntComp implements Comparator<Integer> {
    //这是把其变成大根堆
    @Override
    public int compare(Integer o1, Integer o2) {
        return o2.compareTo(o1);
    }
}

class Solution {
    public int[] smallestK(int[] arr, int k) {
        int[] ret = new int[k];
        if (arr == null || k == 0) {
            return ret;
        }
        //默认为小堆,我们传比较器
        PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(k,new IntComp());

        //把前k个元素放入到堆中
        for (int i = 0; i < k; i++) {
            priorityQueue.offer(arr[i]);
        }

        //之后从第 N-k 个数据开始比较知道比较结束
        //这里是求前k个最小的,所以把第 N-k 个数据和 堆顶进行比较,如果 N-k 小的话就是 删堆顶,入第N-k的元素堆
        for (int i = k; i < arr.length; i++) {
            int peekval = priorityQueue.peek();
            if (arr[i] < peekval) {
                priorityQueue.poll();
                priorityQueue.offer(arr[i]);
            }
        }

        //把堆里的元素放到数组中
        for (int i = 0; i < k; i++) {
            ret[i] = priorityQueue.poll();
        }

        return ret;
    }
}

    ※   注意:这里我们的 PriorityQueue 创建为什么要传参呢?因为我们默认的建堆方式是小根堆,我们是求前k个最小的元素,所以我们要建大根堆,所以我们要传比较器,使之变成大根堆。


  ❄️总结:

      OK,我们这次的博客就到这里就结束了,这里呢还是比较难理解的,所以我们要多进行练习一下,那么我们下次的博客呢,就开始接受我们的排序的问题了,这个排序还是很重要的,我们尽情期待吧!!!拜拜咯~~~


原文地址:https://blog.csdn.net/2303_80388948/article/details/142362757

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!