数据结构青铜到王者第十三话---优先级队列(堆)(2)

发布于:2025-09-01 ⋅ 阅读:(31) ⋅ 点赞:(0)

续接上一话

目录

一、常见接口的介绍(续)

1、 PriorityQueue常用接口介绍

1.1优先级队列的构造

1.2插入/删除/获取优先级最高的元素

1.3oj练习

二、堆的应用

1、PriorityQueue的实现

2、堆排序

3、Top-k问题


一、常见接口的介绍(续)

1、 PriorityQueue常用接口介绍

1.1优先级队列的构造

        此处只是列出了PriorityQueue中常见的几种构造方式

 static void TestPriorityQueue(){
        // 创建一个空的优先级队列,底层默认容量是11
        PriorityQueue<Integer> q1 = new PriorityQueue<>();
 
        // 创建一个空的优先级队列,底层的容量为initialCapacity
        PriorityQueue<Integer> q2 = new PriorityQueue<>(100);
 
        ArrayList<Integer> list = new ArrayList<>();
        list.add(4);
        list.add(3);
        list.add(2);
        list.add(1);
 
        // 用ArrayList对象来构造一个优先级队列的对象
        // q3中已经包含了三个元素
        PriorityQueue<Integer> q3 = new PriorityQueue<>(list);
        System.out.println(q3.size());
        System.out.println(q3.peek());
 }

#注:默认情况下,PriorityQueue队列是小堆,如果需要大堆需要用户提供比较器

// 用户自己定义的比较器:直接实现Comparator接口,然后重写该接口中的compare方法即可
class IntCmp implements Comparator<Integer>{
    @Override
    public int compare(Integer o1, Integer o2) {
        return o2-o1;
    }
 }
 
public class TestPriorityQueue {
    public static void main(String[] args) {
        PriorityQueue<Integer> p = new PriorityQueue<>(new IntCmp());
        p.offer(4);
        p.offer(3);
        p.offer(2);
        p.offer(1);
        p.offer(5);
        System.out.println(p.peek());
    }
 }

        此时创建出来的就是一个大堆。

1.2插入/删除/获取优先级最高的元素

 
        System.out.println(p.peek());
    }
 }
 static void TestPriorityQueue2(){
    int[] arr = {4,1,9,2,8,0,7,3,6,5};
 
    // 一般在创建优先级队列对象时,如果知道元素个数,建议就直接将底层容量给好
    // 否则在插入时需要不多的扩容
    // 扩容机制:开辟更大的空间,拷贝元素,这样效率会比较低
    PriorityQueue<Integer> q = new PriorityQueue<>(arr.length);
    for (int e: arr) {
        q.offer(e);
    }
 
    System.out.println(q.size());   // 打印优先级队列中有效元素个数
    System.out.println(q.peek());   // 获取优先级最高的元素
 
    // 从优先级队列中删除两个元素之和,再次获取优先级最高的元素
    q.poll();
    q.poll();
    System.out.println(q.size());   // 打印优先级队列中有效元素个数
    System.out.println(q.peek());   // 获取优先级最高的元素
 
    q.offer(0);
    System.out.println(q.peek());   // 获取优先级最高的元素
 
    // 将优先级队列中的有效元素删除掉,检测其是否为空
    q.clear();
    if(q.isEmpty()){
        System.out.println("优先级队列已经为空!!!");
    }
    else{
        System.out.println("优先级队列不为空");
    }
 }

#注:以下是JDK 1.8中,PriorityQueue的扩容方式:

private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
 
private void grow(int minCapacity) {
    int oldCapacity = queue.length;
    // Double size if small; else grow by 50%
    int newCapacity = oldCapacity + ((oldCapacity < 64) ?
                                     (oldCapacity + 2) :
                                     (oldCapacity >> 1));
    // overflow-conscious code
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    queue = Arrays.copyOf(queue, newCapacity);
 }
 
private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) // overflow
        throw new OutOfMemoryError();
    return (minCapacity > MAX_ARRAY_SIZE) ?
        Integer.MAX_VALUE :
        MAX_ARRAY_SIZE;
 }

优先级队列的扩容说明:

        (1)如果容量小于64时,是按照oldCapacity的2倍方式扩容的

        (2)如果容量大于等于64,是按照oldCapacity的1.5倍方式扩容的

        (3)如果容量超过MAX_ARRAY_SIZE,按照MAX_ARRAY_SIZE来进行扩容

1.3oj练习

        top-k问题:最大或者最小的前k个数据。比如:世界前500强公司

面试题 17.14. 最小K个数 - 力扣(LeetCode)

        我们用一个大根堆实时维护数组的前 k 小值。        

        首先将前 k 个数插入大根堆中,随后从第 k+1 个数开始遍历,如果当前遍历到的数比大根堆的堆顶的数要小,就把堆顶的数弹出,再插入当前遍历到的数。最后将大根堆里的数存入数组返回即可。

class Solution {
    public int[] smallestK(int[] arr, int k) {
        int[] vec = new int[k];
        // 参数检测
        if (arr == null||k <= 0) { 
            return vec;
        }
        PriorityQueue<Integer> queue = new PriorityQueue<Integer>(new Comparator<Integer>() {
            public int compare(Integer num1, Integer num2) {
                return num2 - num1;
            }
        });
        //将数组中的元素依次放到堆中
        for (int i = 0; i < k; ++i) {
            queue.offer(arr[i]);
        }
        for (int i = k; i < arr.length; ++i) {
            if (queue.peek() > arr[i]) {
                queue.poll();
                queue.offer(arr[i]);
            }
        }
        // 将优先级队列的前k个元素放到数组中
        for (int i = 0; i < k; ++i) {
            vec[i] = queue.poll();
        }
        return vec;
    }
}

        该解法只是PriorityQueue的简单使用,并不是topK最好的做法,那topk该如何实现?下面介绍:

二、堆的应用

1、PriorityQueue的实现

        用堆作为底层结构封装优先级队列

2、堆排序

堆排序即利用堆的思想来进行排序,总共分为两个步骤:

(1)建堆:

                升序:建大堆

                降序:建小堆

(2)利用堆删除思想来进行排序

        建堆和堆删除中都用到了向下调整,因此掌握了向下调整,就可以完成堆排序。

3、Top-k问题

        TOP-K问题:即求数据集合中前K个最大的元素或者最小的元素,一般情况下数据量都比较大。

比如:专业前10名、世界500强、富豪榜、游戏中前100的活跃玩家等。

        对于Top-K问题,能想到的最简单直接的方式就是排序,但是:如果数据量非常大,排序就不太可取了(可能数据都 不能一下子全部加载到内存中)。最佳的方式就是用堆来解决,基本思路如下:

        (1)用数据集合中前K个元素来建堆

                                前k个最大的元素,则建小堆

                                前k个最小的元素,则建大堆

        (2)用剩余的N-K个元素依次与堆顶元素来比较,不满足则替换堆顶元素

        将剩余N-K个元素依次与堆顶元素比完之后,堆中剩余的K个元素就是所求的前K个最小或者最大的元素。

                                        【具体代码实现,见下一话!!!】


网站公告

今日签到

点亮在社区的每一天
去签到