• JAVA优先级队列详解


    基本使用

    默认:最小堆,每次可获得最小元素

    优先队列按照其作用不同,可以分为以下两种:

    • 最大优先队列: 可以获取并删除队列中最大的值
    • 最小优先队列: 可以获取并删除队列中最小的值
    将元素放入队列:add,offer
    将队首元素从队列删除:remove,poll
    查看队列内的对首元素:element,peek
    
    • 1
    • 2
    • 3

    和标准队列不同的是,当删除队首元素的时候,删除的是priority queue中最小的元素。但是,priority queue并不是对所有的元素排序,其内部是用heap(堆)实现的。堆是一个自组织的二叉树,在这个二叉树里,addremove操作使得最小的元素“吸引”到二叉树的根部,而不用在排列整个队列上耗费时间。

    PriorityQueue heap = new PriorityQueue<>(
                    (n1,n2) -> n1-n2  //这一句加不加结果是一样的
            );
            heap.add(4);
            heap.add(1);
            heap.add(2);
            heap.add(3);
            while(!heap.isEmpty())
            {
                System.out.println(heap.poll());
            }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    输出顺序是1,2,3,4。
    可以看到默认的比较规则就是n1-n2,如果我们改成n2-n1

    PriorityQueue heap = new PriorityQueue<>(
    	(n1,n2) -> n2-n1 //这一句加不加结果是一样的
    );
    
    • 1
    • 2
    • 3

    输出顺序就是4,3,2,1。

    可以想见优先队列内部是按照比较器返回的结果进行处理的,一般认为a,b比较结果大于0,就是a大于b,等于0就是a等于b;小于0就是a小于b。
    现在我们知道当比较n1,n2的时候,如果结果大于0(也就是默认的n1-n2>0),那么要把n1下沉(默认的小根堆),当我们改成n2-n1的时候,结果大于0,证明n2大于n1,但还是n1(值较小的元素)下沉,于是这个堆就成了大根堆!

    单元素优先级队列

    就如上面的基本用法,下面换一个比较字符的例子:

    将字母分为三个等级输出

    输入一个字符串,高级为"bdfhkl",中级为"aceimnorstuvwxz",低级为"gjqpy",请将字符串按字母等级分割为3个字符串,每个字符串内按字典序输出。如果字符串为空输出null。

    import java.util.*;
    
    public class demo5 {
        public static void main(String[] args) {
            Scanner scanner = new Scanner(System.in);
            String s = scanner.next();
            PriorityQueue<Character> q1 = new PriorityQueue<>();
            PriorityQueue<Character> q2 = new PriorityQueue<>();
            PriorityQueue<Character> q3 = new PriorityQueue<>();
            for (int i = 0; i < s.length(); i++) {
                switch (fun(s.charAt(i))) {
                    case 1 :
                        q1.offer(s.charAt(i));
                        break;
                    case 2 :
                        q2.offer(s.charAt(i));
                        break;
                    case 3 :
                        q3.offer(s.charAt(i));
                        break;
                    default:
                        break;
                }
            }
            if (q1.size() != 0) {
                while (q1.size() != 0) {
                    System.out.print(q1.poll());
                }
                System.out.println();
            } else {
                System.out.println("null");
            }
            if (q2.size() != 0) {
                while (q2.size() != 0) {
                    System.out.print(q2.poll());
                }
                System.out.println();
            } else {
                System.out.println("null");
            }
            if (q3.size() != 0) {
                while (q3.size() != 0) {
                    System.out.print(q3.poll());
                }
                System.out.println();
            } else {
                System.out.println("null");
            }
        }
        static int fun(char c) {
            switch (c) {
                case 'b' :
                case 'h':
                case 'f':
                case 'k':
                case 'l':
                case 'd':
                    return 1;
                case 'g':
                case 'j':
                case 'p':
                case 'q':
                case 'y':
                    return 3;
                default:
                    return 2;
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70

    再举一个链表节点比较的例子:

    基本写法:

    就像TreeSet一样,一个priority queue要么存储的是实现了Comparable接口的元素,要么在构造函数中传入一个Comparator对象。

    	public class ListNode {
    		int val;
            ListNode next;
            ListNode() {}
            ListNode(int val) { this.val = val; }
            ListNode(int val, ListNode next) { this.val = val; this.next = next; }
        }
        
    	//自定义比较类,升序排列
        static Comparator<ListNode> cLNode = new Comparator<ListNode>() {
            public int compare(ListNode o1, ListNode o2) {
                    return o1.val-o2.val;
            }
        };
        
        public static void main(String[] args) {
        	//自定义类的优先队列需要重写比较类作为传入参数
            Queue<ListNode> que = new PriorityQueue<>(cLNode);   
            //简单写法
            // Queue que = new PriorityQueue<>((v1, v2) -> v1.val - v2.val);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    合并K个有序链表

    把链表节点放入一个最小堆,就可以每次获得 k 个节点中的最小节点。

    优先队列 pq 中的元素个数最多是 k,所以一次 poll 或者 add 方法的时间复杂度是 O(logk);所有的链表节点都会被加入和弹出 pq所以算法整体的时间复杂度是 O(Nlogk),其中 k 是链表的条数,N 是这些链表的节点总数

    ListNode mergeKLists(ListNode[] lists) {
        if (lists.length == 0) return null;
        // 虚拟头结点
        ListNode dummy = new ListNode(-1);
        ListNode p = dummy;
        // 优先级队列,最小堆
        PriorityQueue<ListNode> pq = new PriorityQueue<>(
            lists.length, (a, b)->(a.val - b.val));
        // 将 k 个链表的头结点加入最小堆
        for (ListNode head : lists) {
            if (head != null)
                pq.add(head);
        }
    
        while (!pq.isEmpty()) {
            // 获取最小节点,接到结果链表中
            ListNode node = pq.poll();
            p.next = node;
            if (node.next != null) {
                pq.add(node.next);
            }
            // p 指针不断前进
            p = p.next;
        }
        return dummy.next;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    class Solution {
        class Status implements Comparable<Status> {
            int val;
            ListNode ptr;
    
            Status(int val, ListNode ptr) {
                this.val = val;
                this.ptr = ptr;
            }
    
            public int compareTo(Status status2) {
                return this.val - status2.val;
            }
        }
    
        PriorityQueue<Status> queue = new PriorityQueue<Status>();
    
        public ListNode mergeKLists(ListNode[] lists) {
            for (ListNode node: lists) {
                if (node != null) {
                    queue.offer(new Status(node.val, node));
                }
            }
            ListNode head = new ListNode(0);
            ListNode tail = head;
            while (!queue.isEmpty()) {
                Status f = queue.poll();
                tail.next = f.ptr;
                tail = tail.next;
                if (f.ptr.next != null) {
                    queue.offer(new Status(f.ptr.next.val, f.ptr.next));
                }
            }
            return head.next;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    /**
     * Definition for singly-linked list.
     * public class ListNode {
     *     int val;
     *     ListNode next;
     *     ListNode(int x) {
     *         val = x;
     *         next = null;
     *     }
     * }
     */
    import java.util.*;
    public class Solution {
        public ListNode mergeKLists(ArrayList<ListNode> lists) {
            if(lists.size() == 0){
                return null;
            }
            ListNode dummy = new ListNode(-1);
            ListNode p = dummy;
            PriorityQueue<ListNode> pq = new PriorityQueue<ListNode>(lists.size(), (a, b) -> (a.val - b.val));
            for(ListNode x : lists){
                if(x != null){
                    pq.offer(x);
                }
            }
            while(!pq.isEmpty()){
                ListNode node = pq.poll();
                p.next = node;
                if(node.next != null){
                    pq.offer(node.next);
                }
                p = p.next;
            }
            return dummy.next;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    双元素优先队列

    PriorityQueue<int[]> heap = new PriorityQueue<>(
                    (pair1,pair2) -> pair1[0]-pair2[0]
            );
            heap.add(new int[]{1,9});
            heap.add(new int[]{2,8});
            heap.add(new int[]{3,7});
            while(!heap.isEmpty())
            {
                int[] cur = heap.poll();
                System.out.println(cur[0] + "," + cur[1]);
            }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    这里我们必须是要设置比较规则的,因为默认里面没有比较数组的规则。这里我们设置第一个元组的第一个数减去第二个元组的第一个数。
    按照前面设想的逻辑,此时如果结果大于0,证明pair2的第一个数比pair1的第一个数大,而pair1下沉,说明此时是一个以元组的第一个数为比较依据的大根堆。

    现在我们可以自由地依照我们的想法来设置排序规则了,比如我现在想要以元组的和为比较依据,并且是小根堆。根据猜想,我们需要让大的值下沉,也就是比较结果大于0的时候,应该是第一个元组是大值。

            PriorityQueue<int[]> heap = new PriorityQueue<>(
                    (pair1,pair2) -> pair1[0]+pair1[1]-pair2[0]-pair2[1]
            );
            heap.add(new int[]{1,9});
            heap.add(new int[]{2,6});
            heap.add(new int[]{3,4});
            while(!heap.isEmpty())
            {
                int[] cur = heap.poll();
                System.out.println(cur[0] + "," + cur[1]);
            }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    这里结合TreeSet使用一下,类似的道理:

    import java.io.IOException;
    
    import java.util.*;
    
    public class demo02 {
        public static void main(String[] args) throws IOException {
            Scanner scanner = new Scanner(System.in);
            int n = scanner.nextInt();
            scanner.nextLine();
            int[] height = new int[n];//身高
            String[] name = new String[n];//姓名
            for(int i = 0 ; i < n; i++){
                height[i] = scanner.nextInt();
            }
            scanner.nextLine();
            for(int i = 0 ; i < n; i++){
                name[i] = String.valueOf(scanner.next());
            }
            TreeMap<Integer, PriorityQueue<String >> treeMap = new TreeMap<>();
            for(int i = 0; i < n; i++){
                if(treeMap.containsKey(height[i])){
                    treeMap.get(height[i]).add(name[i]);
                }else{
                    PriorityQueue<String > queue = new PriorityQueue<>();
                    treeMap.put(height[i], queue);
                    queue.add(name[i]);
                }
            }
            List<String> list = new ArrayList<>();
            PriorityQueue<String > queue = new PriorityQueue<>();
            while(!treeMap.isEmpty()){
                queue = treeMap.pollFirstEntry().getValue();
                while(!queue.isEmpty()){
                    list.add(queue.poll());
                }
            }
            for(int i = 0; i < n; i++){
                System.out.print(list.get(i) + " ");
            }
            System.out.println();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    常用的场景——调度

    单线程CPU

    这个面试时候遇到过,现场手撕

    1834. 单线程 CPU - 力扣(LeetCode)

    在这里插入图片描述

    这题的难度不算大,就是有些复杂,难点在于你要同时控制三个变量(开始时间、处理时间、索引)的有序性,而且这三个变量还有优先级

    • 首先应该考虑开始时间,因为只要到了开始时间,任务才进入可执行状态;
    • 其次应该考虑任务的处理时间,在所有可以执行的任务中优先选择处理时间最短的;
    • 如果存在处理时间相同的任务,那么优先选择索引最小的。

    所以这道题的思路是:

    先根据任务「开始时间」排序,维护一个时间线变量 now 来判断哪些任务到了可执行状态,然后借助一个优先级队列 pq 对「处理时间」和「索引」进行动态排序

    利用优先级队列动态排序是有必要的,因为每完成一个任务,时间线 now 就要更新,进而产生新的可执行任务。

    import java.util.*;
    
    public class demo6 {
        public static void main(String[] args) {
            int[][] tasks = {{1, 2}, {2, 4}, {3, 2}, {4, 1}};
            int[] res = getOrder(tasks);
            System.out.print(Arrays.toString(res));
            // 输出[0, 2, 3, 1]
        }
        static int[]  getOrder(int[][] tasks) {
            int n = tasks.length;
            // 把原始索引也添加上,方便后面排序用
            ArrayList<int[]> triples = new ArrayList<>();
            for (int i = 0; i < n; i++) {
                triples.add(new int[]{tasks[i][0], tasks[i][1], i});
            }
            // 数组先按照任务的开始时间排序
            triples.sort(Comparator.comparingInt(a -> a[0]));
            //上面一行等价于
            //    triples.sort((a, b) -> {
            //        return a[0] - b[0];
            //    });
            // 按照任务的处理时间排序,如果处理时间相同,按照原始索引排序
            PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> {
                if (a[1] != b[1]) {
                    // 比较处理时间
                    return a[1] - b[1];
                }
                // 比较原始索引
                return a[2] - b[2];
            });
            ArrayList<Integer> res = new ArrayList<>();
            // 记录完成任务的时间线
            int now = 0;
            int i = 0;
            while (res.size() < n) {
                if (!pq.isEmpty()) {
                    // 完成队列中的一个任务
                    int[] triple = pq.poll();
                    res.add(triple[2]);
                    // 每完成一个任务,就要推进时间线
                    now += triple[1];
                } else if (i < n && triples.get(i)[0] > now) {
                    // 队列为空可能因为还没到开始时间,
                    // 直接把时间线推进到最近任务的开始时间
                    now = triples.get(i)[0];
                }
                // 由于时间线的推进,会产生可以开始执行的任务
                for (; i < n && triples.get(i)[0] <= now; i++) {
                    pq.offer(triples.get(i));
                }
            }
            // Java 语言特性,将 List 转化成 int[] 格式
            int[] arr = new int[n];
            for (int j = 0; j < n; j++) {
                arr[j] = res.get(j);
            }
            return arr;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60

    Arrays.sort()

    在Arrays.sort中也可以自定义排序规则,试验后发现是一样的譬如:

    int[][] arr = {{1,9},{2,8},{3,7}};
    Arrays.sort(arr,(a,b)->b[0]-a[0]);
    
    • 1
    • 2

    当结果大于0的时候,是a向后,也就是默认的是升序,这里的代码的含义是以元组的第一个值为依据,降序排列。
    需要注意的是如果是一维数组,是无法自定义这样的规则的,如果想换成降序,最简单的办法就是翻转:

    Arrays.sort(arr,Collections.reverseOrder());
    
    • 1

    注意这里的数组不能是int[],需要是Integer[]


    今日推歌

    ----《还是爱着》 王铮亮

    我们慢慢将对方活成 相互的生命
    这样跟你 牵绊地 成一体
    还是爱着你 却少了说爱你
    平淡岁月里 总抹掉些言语
    而热烈退去 活在平常里
    越难舍难离
    还是爱着你 最亲最近的你
    好几程风雨 越吵闹越相依
    手心连着你 脉搏跳动的呼吸

  • 相关阅读:
    亚商投资顾问 早餐FM/1028华为海外推广5.5G
    第二章 进程与线程 八、处理机调度(时机切换、过程调度方式)
    基于Matlab实现全局优化算法
    PTA 7-77 查找指定字符
    新版Chromedriver在哪下载(Chromedriver 116.0.5845.188的寻找之旅)
    【剑指offer】序列化二叉树
    [C++随想录] 优先级队列
    IDEA(安装和使用)
    Qt设置horizontal line 和vertical line的颜色
    基于Qt的目录统计QDirStat
  • 原文地址:https://blog.csdn.net/m0_51013067/article/details/126521551