赞
踩
默认:最小堆,每次可获得最小元素
优先队列按照其作用不同,可以分为以下两种:
将元素放入队列:add,offer
将队首元素从队列删除:remove,poll
查看队列内的对首元素:element,peek
和标准队列不同的是,当删除队首元素的时候,删除的是
priority queue
中最小的元素。但是,priority queue
并不是对所有的元素排序,其内部是用heap
(堆)实现的。堆是一个自组织的二叉树,在这个二叉树里,add
和remove
操作使得最小的元素“吸引”到二叉树的根部,而不用在排列整个队列上耗费时间。
PriorityQueue<Integer> 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。
可以看到默认的比较规则就是n1-n2
,如果我们改成n2-n1
PriorityQueue<Integer> heap = new PriorityQueue<>(
(n1,n2) -> n2-n1 //这一句加不加结果是一样的
);
输出顺序就是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;
}
}
}
再举一个链表节点比较的例子:
基本写法:
就像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<ListNode> que = new PriorityQueue<>((v1, v2) -> v1.val - v2.val);
}
把链表节点放入一个最小堆,就可以每次获得 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;
}
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;
}
}
/**
* 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;
}
}
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]);
}
这里我们必须是要设置比较规则的,因为默认里面没有比较数组的规则。这里我们设置第一个元组的第一个数减去第二个元组的第一个数。
按照前面设想的逻辑,此时如果结果大于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]);
}
这里结合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();
}
}
这个面试时候遇到过,现场手撕
这题的难度不算大,就是有些复杂,难点在于你要同时控制三个变量(开始时间、处理时间、索引)的有序性,而且这三个变量还有优先级:
- 首先应该考虑开始时间,因为只要到了开始时间,任务才进入可执行状态;
- 其次应该考虑任务的处理时间,在所有可以执行的任务中优先选择处理时间最短的;
- 如果存在处理时间相同的任务,那么优先选择索引最小的。
所以这道题的思路是:
先根据任务「开始时间」排序,维护一个时间线变量
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;
}
}
在Arrays.sort中也可以自定义排序规则,试验后发现是一样的譬如:
int[][] arr = {{1,9},{2,8},{3,7}};
Arrays.sort(arr,(a,b)->b[0]-a[0]);
当结果大于0的时候,是a向后,也就是默认的是升序,这里的代码的含义是以元组的第一个值为依据,降序排列。
需要注意的是如果是一维数组,是无法自定义这样的规则的,如果想换成降序,最简单的办法就是翻转:
Arrays.sort(arr,Collections.reverseOrder());
注意这里的数组不能是int[],需要是Integer[]
----《还是爱着》 王铮亮
我们慢慢将对方活成 相互的生命
这样跟你 牵绊地 成一体
还是爱着你 却少了说爱你
平淡岁月里 总抹掉些言语
而热烈退去 活在平常里
越难舍难离
还是爱着你 最亲最近的你
好几程风雨 越吵闹越相依
手心连着你 脉搏跳动的呼吸
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。