内容简介:Topk 问题,即求得 N 个乱序元素中第 k 大(小)的元素,是一个很经典的问题。在此文章中,我总结了自己对此问题的思路。可以直接使用各种排序算法,将数组从大到小排序后再取第 $k$ 个元素时间复杂度即为各排序算法的时间复杂度
Topk 问题,即求得 N 个乱序元素中第 k 大(小)的元素,是一个很经典的问题。在此文章中,我总结了自己对此问题的思路。
思路一
描述
可以直接使用各种 排序 算法,将数组从大到小排序后再取第 $k$ 个元素
时间复杂度
时间复杂度即为各 排序算法 的时间复杂度
缺陷
可以知道这种方法的时间复杂度最小为 $O(NlogN)$ ,但当 $N$ 很大时,这种方法的复杂度还是较高的。接下来我们对其进行优化。
思路二
描述
可以使用类似选择排序的方式,找出数组中最大的元素,与第一个元素交换,再找出第二大元素,与第二个元素交换,直到找到第 $k$ 大元素便停止。
这种方法当 $N$ 很大,$k$ 较小,即 $k < logN$ 时比思路一要好。
时间复杂度
$O(kN)$
代码
public static Comparable select(Comparable[] a, int k) {
int N = a.length;
for (int i = 0; i < k; i++) {
int max = i;
for (int j = i + 1; j < N; j++) {
if (less(a[max], a[j]))
max = j;
}
exch(a, max, i);
}
return a[k - 1];
}
源代码
思路三
描述
采用基于小根堆的 优先队列 ,前 $k$ 个元素输入时,建立大小为 $k$ 的小根堆,之后每输入一个元素,便调整堆结构,并将根结点的元素(即最小元素)移除。直到所有元素输入完成后,留在堆里的 $k$ 个元素便是最大的 $k$ 个元素,此时根结点的元素便是第 $k$ 大的元素。
时间复杂度
$O(Nlogk)$
代码
优先队列
public class MinPQ<Key extends Comparable<Key>> {
private Key[] pq; //基于堆的完全二叉树
private int N = 0; //存储于pq[1..N],pq[0]不使用
public MinPQ(int maxN) {
pq = (Key[]) new Comparable[maxN + 1];
}
public boolean isEmpty() {
return N == 0;
}
public int size() {
return N;
}
public void insert(Key v) {
pq[++N] = v; //先将新元素添加在堆末尾
swim(N); //再上浮到合适位置
}
public Key delMin() {
Key min = pq[1]; //从根节点得到最大元素
exch(1, N--); //将其和最后一个结点交换
pq[N + 1] = null; //防止对象游离
sink(1); //恢复堆的有序性
return min;
}
public void show() {
for (int i = 1; i <= N; i++) {
System.out.printf(pq[i] + " ");
}
System.out.println();
}
private boolean less(int i, int j) {
return pq[i].compareTo(pq[j]) < 0;
}
private void exch(int i, int j) {
Key t = pq[i];
pq[i] = pq[j];
pq[j] = t;
}
private void swim(int k) {
while (k > 1 && less(k, k / 2)) {
exch(k / 2, k);
k = k / 2;
}
}
private void sink(int k) {
while (2 * k <= N) {
int j = 2 * k;
if (j < N && less(j + 1, j))
j++;
if (less(k, j))
break;
exch(k, j);
k = j;
}
}
}
选择
public static String select(String[] a, int k) {
MinPQ<String> pq = new MinPQ<>(k + 1); //多一个用于重排和删除
for (int i = 0; i < a.length; i++) {
pq.insert(a[i]);
if (pq.size() > k)
pq.delMin();
}
return pq.delMin();
}
源代码
https://github.com/XutongLi/Algorithm-Learn/tree/master/src/S2_Sorting/Others_TopK/TopK_MInPQ
思路四
描述
结合快速排序中的划分方法(partition())和二分法。一次划分后,$a[j]$ 左侧的元素都大于 $a[j]$ ,右侧的都小于 $a[j]$ ,比较 $k-1$ 与 $j$ ,如果相等,则 $a[j]$ 便是第 $k$大的元素,$a[0..j]$ 为前 $k$ 大的元素;若 $j>k-1$ ,则对 $a[j]$ 左半部分进行划分;若 $j<k-1$ ,则对 $a[j]$ 右半部分进行划分,直到找到第 $k$ 大的元素。
时间复杂度
$O(N)$
解释:假设每次都正好将数组二分,那么比较的总次数为 $(N+N/2+N/4+…)$ ,直到找到第 $k$ 大的元素,这个和显然小于 $2N$ 。
代码
public static Comparable select(Comparable[] a, int k) {
k --;
shuffle(a);
int lo = 0, hi = a.length - 1;
while (hi > lo) {
int j = partition(a, lo, hi);
if (j == k)
return a[j];
else if (j > k)
hi = j - 1;
else
lo = j + 1;
}
return a[k];
}
public static int partition(Comparable[] a, int lo, int hi) {
int i = lo, j = hi + 1;
Comparable v = a[lo]; //pivot
while (true) {
while (less(v, a[++i])){
if (i == hi)
break;
}
while (less(a[--j], v)) {
if (j == lo)
break;
}
if (i >= j)
break;
exch(a, i, j);
}
exch(a, lo, j);
return j;
}
以上所述就是小编给大家介绍的《TopK问题》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- acme.sh 续期问题(路径问题)
- 缓存的一些问题和一些加密算法【缓存问题】
- 如何把设计问题转化为数学问题(方法论)
- 推荐系统中的冷启动问题和探索利用问题
- GraphQL 教程(六)—— N+1问题和缓存等问题
- Golang 并发问题(四)之单核上的并发问题
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Learning PHP, MySQL, JavaScript, and CSS
Robin Nixon / O'Reilly Media / 2012-9-3 / USD 39.99
If you're familiar with HTML, you can quickly learn how to build interactive, data-driven websites with the powerful combination of PHP, MySQL, and JavaScript - the top technologies for creating moder......一起来看看 《Learning PHP, MySQL, JavaScript, and CSS》 这本书的介绍吧!