1、全部直接排序
排序是最容易想到的方法,将n个数排序之后,取出最大的k个,即为所得。
伪代码:
sort(arr, 1, n);
return arr[1, k];
时间复杂度:O(n*lg(n))
分析:明明只需要TopK,却将全局都排序了,这也是这个方法复杂度非常高的原因。那能不能不全局排序,而只局部排序呢?
这就引出了第二个优化方法。
2、快速排序
局部排序
不再全局排序,只对最大的k个排序。冒泡是一个很常见的排序方法,每冒一个泡,找出最大值,冒k个泡,就得到TopK。
伪代码:
for(i=1 to k){
bubble_find_max(arr,i);
}
return arr[1, k];
时间复杂度:O(n*k)
3、最小堆法
先用前k个元素生成一个小顶堆,
这个小顶堆用于存储,当前最大的k个元素。
接着,从第k+1个元素开始扫描,和堆顶(堆中最小的元素)比较,如果被扫描的元素大于堆顶,则替换堆顶的元素,并调整堆,
以保证堆内的k个元素,总是当前最大的k个元素。
使用 priority_queue<int,vector<int>,greater<int> > q(arr,arr+m);
时间复杂度:O(n*lg(k))
4、分治法
分治法(Divide&Conquer),把一个大的问题,
转化为若干个子问题(Divide),每个子问题“都”解决,
大的问题便随之解决(Conquer)。这里的关键词是“都”。从伪代码里可以看到,快速排序递归时,
先通过partition把数组分隔为两个部分,
两个部分“都”要再次递归。
分治法有一个特例,叫减治法,减治法(Reduce&Conquer),把一个大的问题,转化为若干个子问题(Reduce),这些子问题中“只”解决一个,大的问题便随之解决(Conquer)。
这里的关键词是“只”。
总结
全局排序,O(n*lg(n))
局部排序,只排序TopK个数,O(n*k)
堆,TopK个数也不排序了,O(n*lg(k))
分治法,每个分支“都要”递归,例如:快速排序,O(n*lg(n))
减治法,“只要”递归一个分支,例如:二分查找O(lg(n)),随机选择O(n)
TopK的另一个解法:随机选择+partition