优先队列算法( Priority queue)
前言:
引入:优先队列问题常用于降低时间复杂度,达到快速搜索的目的
源码阅读Priority queue类:
底层分析:依据优先级构造堆
下面我们来谈一谈实现的原理
优先队列是利用堆来实现的
堆可以看做的一颗完全二叉树的顺序存储结构,(大顶堆:每个结点的值都大于等于左右孩子的值)
优先队列的两个基本操作:(都在维护堆序性)
出队:堆顶出队,最后一个记录,代替堆顶的位置,重新调整为堆
入队:将新元素放入树中的末尾,再调整为堆
一个大顶堆构建完成后,且堆顶出队后,其他结点都满足最大堆的定义,只需要将堆顶执行下沉操作,
即都可再次调整为堆
出队后的操作:下沉(在已经构建好堆的基础上再次维护堆):
堆顶与左右孩子比较,若大则已经满足无需构建,若比孩子小,则与较大的孩子交换,交换到新位置后继续向下比较
(这里在堆排序里详细探讨过)
比较次数最多为树的高度
如果不理解上述过程建议看我的博客:堆排序
入队后的操作:上浮 (比较次数:最多为树的高度h)
入队时,将新元素放入最后一个记录之后,例如29入队,放在12的后面
复杂度分析:
优先队列的时间复杂度为:logn(而线性的复杂度为n,有时为二维复杂度将程指数阶,使用优先队列将大大提升效率)
这是是由于上浮和下沉中最大遍历范围为树的高度,完全二叉树的树高为h=[logn]+1
Lambda表达式构建Priority queue
这里我们只需要
PriorityQueue queue =new PriorityQueue(大小,比较器类型);
- Lambda表达式法书写比较器接口:
Lambda表达式允许通过表达式来代替功能接口功能(注意表达式中参数以及参数方法的一致性,使用中尽量添加泛型约束)
Lambda作为排序参口传递进方法中,也就是说Lambda可以替代Comparator接口作为排序参数
Lambda表达式的基本语法:
(
p
a
r
a
m
e
t
e
r
s
)
−
>
e
x
p
r
e
s
s
i
o
n
或
(
p
a
r
a
m
e
t
e
r
s
)
−
>
s
t
a
t
e
m
e
n
t
s
;
(parameters) -> expression 或 (parameters) ->{ statements; }
(parameters)−>expression或(parameters)−>statements;
// 1. 不需要参数,返回值为 5
() -> 5
// 2. 接收一个参数(数字类型),返回其2倍的值
x -> 2 * x
// 3. 接受2个参数(数字),并返回他们的差值
(x, y) -> x – y
// 4. 接收2个int型整数,返回他们的和
(int x, int y) -> x + y
// 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)
(String s) -> System.out.print(s)
那么我们如何实现以小顶堆进行排序的优先级队列呢
PriorityQueue heap=new PriorityQueue<Integer>((n1,n2)->n1-n2);
(这里的泛型最好加上)
解释:参数为两个数,返回两个值的差 差为1 则交换,差为负数和0不交换,维护了升序排列
例题实现:
- 找出第 K 大的异或坐标值
给你一个二维矩阵 matrix 和一个整数 k ,矩阵大小为 m x n 由非负整数组成。
矩阵中坐标 (a, b) 的 值 可由对所有满足 0 <= i <= a < m 且 0 <= j <= b < n 的元素 matrix[i][j](下标从 0 开始计数)执行异或运算得到。
请你找出 matrix 的所有坐标中第 k 大的值(k 的值从 1 开始计数)。
示例 1:
输入:matrix = [[5,2],[1,6]], k = 1
输出:7
解释:坐标 (0,1) 的值是 5 XOR 2 = 7 ,为最大的值。
示例 2:
输入:matrix = [[5,2],[1,6]], k = 2
输出:5
解释:坐标 (0,0) 的值是 5 = 5 ,为第 2 大的值。
示例 3:
输入:matrix = [[5,2],[1,6]], k = 3
输出:4
解释:坐标 (1,0) 的值是 5 XOR 1 = 4 ,为第 3 大的值。
示例 4:
输入:matrix = [[5,2],[1,6]], k = 4
输出:0
解释:坐标 (1,1) 的值是 5 XOR 2 XOR 1 XOR 6 = 0 ,为第 4 大的值。
思路:首先思路还是前缀和,但是这次变成了二维前缀
通过模拟后我们得到前缀公式(这里的前缀和做出了修改,与行和列有关)
s u m [ i ] [ j ] = s u m [ i − 1 ] [ j ] 异 或 s u m [ i ] [ j − 1 ] 异 或 s u m [ i − 1 ] [ j − 1 ] 异 或 m a t [ i − 1 ] [ j − 1 ] ; sum[i][j] = sum[i - 1][j] 异或sum[i][j - 1] 异或 sum[i - 1][j - 1] 异或 mat[i - 1][j - 1]; sum[i][j]=sum[i−1][j]异或sum[i][j−1]异或sum[i−1][j−1]异或mat[i−1][j−1];
思路:得到前缀和后,这时我们构建一个由小顶堆构成的大小为k的优先队列,并在加入的过程中不断维护优先队列的大小
这样,当我们加入完所有元素后,只有k-1个元素比我们堆顶的元素大,那么堆顶元素即为第k大的元素
代码实现:
class Solution {
public int kthLargestValue(int[][] mat, int k) {
int m = mat.length, n = mat[0].length;
int[][] sum = new int[m + 1][n + 1];
PriorityQueue<Integer> q = new PriorityQueue<>(k, (a, b)->a - b);
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
sum[i][j] = sum[i - 1][j] ^ sum[i][j - 1] ^ sum[i - 1][j - 1] ^ mat[i - 1][j - 1];
if (q.size() < k) {//容量小于k,则加入
q.add(sum[i][j]);
} else {
if (sum[i][j] > q.peek()) {//比堆顶元素大,加入进堆中并维护
q.poll();
q.add(sum[i][j]);
}
}
}
}
return q.peek();
}
}