TopK算法问题
本文总结下TopK相关算法,分为两个部分,一为讨论TopK问题的类型,二为相关的解题思路和C++代码实现。
TopK 问题一般为海量数据问题,可以分为两大类:
- 找最大/最小的 K 个数
这种问题一般分为两种类型,一为内存不够无法一次性加载,二为内存充足可以完全处理数据。
- 找重复次数最大的 K 个数
这种问题一般是涉及极其庞大的数据量,内存不可能一次性加载完成。必须采用 分治法。
TopK 问题
找最大最小的k个数
- 利用大/小顶堆 + hashmap
例如找最大的K个数,利用一个大小为k的最小堆,每次数据和堆顶比较,若大于堆顶,则将堆顶移出,并把数据放入堆,排序,否则则忽略。HashMap在这里的作用是避免重复数据。
这种方法空间复杂度 k,不管数据量有多大,内存中占用不会变。总体的时间复杂度为:n * log(k)。 C++实现如下:
vector<int> maxTopKHeap(vector<int> &array, int k){
unordered_map<int, bool> indexes;
vector<int> heap;
for(auto item: array){
auto find = indexes.find(item);
if (find != indexes.end())
continue;
if (heap.size() < k){
indexes[item] = true;
heap.push_back(item);
push_heap(heap.begin(), heap.end(), greater<int>());
}else{
int top = heap[0];
if (top < item){
indexes.erase(top);
indexes[item] = true;
pop_heap(heap.begin(), heap.end(), greater<int>());
heap.pop_back();
heap.push_back(item);
push_heap(heap.begin(), heap.end(), greater<int>());
}
}
}
return heap;
}
- 使用快速选择算法(Quick Select)
使用这个算法需要内存够用,能把数据全部加载进来,且没有重复数据。
主要脱胎于快排中的 partition 函数。假设 partition划分的方法是比基准数据 大的在左边,小的在右边,那么:若一次划分返回的下标为 i,若 i==k,则说明找到了这K个数,若i>k,说明需要在左边去找,反之需要在右边去找,按照这个思路重复调用得到结果。
C++ 实现如下:
int partition(vector<int> &array, int start, int end){
while (start < end){
while(start < end && array[start] >= array[end]) --end;
swap(array[start], array[end]);
while(start < end && array[end] <= array[start]) ++start;
swap(array[start], array[end]);
}
return start;
}
vector<int> maxTopKQuickSelect(vector<int> &array, int k){
int index;
int start = 0, end = array.size() - 1;
while(true){
index = partition(array, start, end);
if (index == k)
break;
else if(index > k)
end = index - 1;
else
start = index + 1;
}
vector<int> result(array.begin(), array.begin() + index);
return result;
}
找重复次数最大的 K 个数
这个问题只能用分治法,因为需要统计数据出现的次数。
先将 n个数据分成 m 份,每一份 都找出重复次数最大的 K 个数,然后两两合并(2K),在合并集合中 同样找重复次数最大的K个数,重复进行,最后的剩下的 K个数就是要的答案。