什么是TOP-K问题:
解决方法:
1.堆解决:
以下是一个使用std::priority_queue
实现的示例代码,这个例子中我们使用最小堆来找到数组中最大的K个元素:
#include <iostream>
#include <vector>
#include <queue>
#include <algorithm>
void findTopK(const std::vector<int>& nums, int k) {
std::priority_queue<int, std::vector<int>, std::greater<int>> minHeap;
for (int num : nums) {
if (minHeap.size() < k) {
minHeap.push(num);
} else if (num > minHeap.top()) {
minHeap.pop();
minHeap.push(num);
}
}
std::vector<int> topKElements;
while (!minHeap.empty()) {
topKElements.push_back(minHeap.top());
minHeap.pop();
}
// 打印结果
for (int num : topKElements) {
std::cout << num << " ";
}
std::cout << std::endl;
}
int main() {
std::vector<int> nums = {3, 2, 1, 5, 6, 4};
int k = 2;
findTopK(nums, k);
return 0;
}
这段代码首先定义了一个函数findTopK
,它接受一个整数数组nums
和一个整数k
作为参数。函数内部使用了一个最小堆minHeap
来存储当前找到的K个最大元素。遍历数组中的每个元素,如果堆的大小小于K,就将元素推入堆中;如果堆的大小等于K,并且当前元素大于堆顶元素,则弹出堆顶元素,并将当前元素推入堆中。这样,堆中始终维护着K个最大的元素。
最后,代码将堆中的元素转移到一个向量topKElements
中,并逆序打印这些元素,因为最小堆的顶部是最小的元素,而我们需要最大的元素。
请注意,这个实现假定数组中的元素是唯一的,如果数组中有重复的元素,需要根据具体需求调整代码逻辑。此外,这个实现的时间复杂度是O(n log K),其中n是数组中元素的数量。
当数据中有重复值时,使用堆来解决TOP-K问题需要稍作修改,以确保能够正确处理重复的元素。我们可以使用一个额外的数据结构(例如哈希表)来记录每个元素出现的次数,这样即使元素值相同,我们也能知道它们是否是不同的实例。
以下是一个C++示例,演示如何在考虑重复元素的情况下,使用最小堆来找到数组中最大的K个元素:
#include <iostream>
#include <vector>
#include <queue>
#include <map>
#include <functional>
// 自定义比较函数,用于优先队列
struct Compare {
bool operator()(const std::pair<int, int>& a, const std::pair<int, int>& b) {
// 优先级队列默认是大顶堆,我们使用负值来实现小顶堆
return a.first < b.first || (a.first == b.first && a.second < b.second);
}
};
void findTopKWithDuplicates(const std::vector<int>& nums, int k) {
std::priority_queue<std::pair<int, int>, std::vector<std::pair<int, int>>, Compare> minHeap;
std::map<int, int> count; // 记录每个元素出现的次数
// 统计每个元素出现的次数
for (int num : nums) {
count[num]++;
}
// 构建最小堆
for (auto& kv : count) {
if (minHeap.size() < k) {
minHeap.push({kv.first, kv.second});
} else if (kv.first > minHeap.top().first) {
minHeap.pop();
minHeap.push({kv.first, kv.second});
} else if (kv.first == minHeap.top().first && kv.second > minHeap.top().second) {
minHeap.pop();
minHeap.push({kv.first, kv.second});
}
}
// 打印结果
std::vector<int> topKElements;
while (!minHeap.empty()) {
topKElements.push_back(minHeap.top().first);
minHeap.pop();
}
std::reverse(topKElements.begin(), topKElements.end());
for (int num : topKElements) {
std::cout << num << " ";
}
std::cout << std::endl;
}
int main() {
std::vector<int> nums = {3, 1, 1, 5, 9, 2, 2, 5};
int k = 4;
findTopKWithDuplicates(nums, k);
return 0;
}
在这个示例中,使用了一个std::pair<int, int>
作为堆中的元素,其中第一个int
是元素的值,第二个int
是该元素出现的次数。然后定义了一个自定义比较函数Compare
来确保堆的元素按照值和次数排序。这样,即使有重复的值,我们也能根据次数来区分它们。
注意,这种方法的时间复杂度仍然是O(n log k),但是它能够正确处理重复元素的情况。
2.其他方法:
除了使用最小堆的方法外,还有其他几种方法可以在C++中实现TOP-K问题的解决方案:
- 遍历排序方法:遍历数组,进行比较查找,非常耗时。
k 轮遍历,分别在每轮中提取第 1、2、…、k 大的元素,时间复杂度为 O(nk) 。
此方法只适用于 k≪n 的情况,因为当 k 与 n 比较接近时,其时间复杂度趋向于 O() ,非常耗时。
看看就好,最好别用!!!
- 排序方法: 对整个数组进行排序,然后选择最大的K个元素。这种方法简单,但时间复杂度较高。
我们可以先对数组 nums
进行排序,再返回最右边的 k 个元素,时间复杂度为 O(n*logn) 。
显然,该方法“超额”完成任务了,因为我们只需找出最大的 k 个元素即可,而不需要排序其他元素。
#include <algorithm>
#include <vector>
#include <iostream>
void topKSort(std::vector<int>& nums, int k) {
std::sort(nums.begin(), nums.end(), std::greater<int>());
nums.resize(k); // 只保留最大的K个元素
}
// 使用示例
int main() {
std::vector<int> nums = {3, 2, 1, 5, 6, 4};
int k = 2;
topKSort(nums, k);
for (int num : nums) {
std::cout << num << " ";
}
return 0;
}
- 快速选择方法: 快速选择是快速排序的变种,可以用于找到第K大的元素,然后根据这个元素将数组分为两部分。
#include <vector>
#include <iostream>
#include <cstdlib>
int partition(std::vector<int>& nums, int left, int right) {
int pivot = nums[right];
int i = left;
for (int j = left; j < right; j++) {
if (nums[j] > pivot) {
std::swap(nums[i], nums[j]);
i++;
}
}
std::swap(nums[i], nums[right]);
return i;
}
void quickSelect(std::vector<int>& nums, int left, int right, int k) {
if (left < right) {
int pivotIndex = partition(nums, left, right);
if (k == pivotIndex) {
return;
} else if (k < pivotIndex) {
quickSelect(nums, left, pivotIndex - 1, k);
} else {
quickSelect(nums, pivotIndex + 1, right, k);
}
}
}
void topKQuickSelect(std::vector<int>& nums, int k) {
quickSelect(nums, 0, nums.size() - 1, nums.size() - k);
}
// 使用示例
int main() {
std::vector<int> nums = {3, 2, 1, 5, 6, 4};
int k = 2;
topKQuickSelect(nums, k);
for (int i = nums.size() - k; i < nums.size(); i++) {
std::cout << nums[i] << " ";
}
return 0;
}
- 线性时间算法: 使用BFPRT算法(中位数的中位数算法)来找到第K大的元素,但实现较为复杂,通常不推荐在实际编程中使用。
- 计数排序: 如果元素的范围不大,可以使用计数排序来快速找到TOP-K。
#include <vector>
#include <iostream>
void topKCountingSort(std::vector<int>& nums, int k) {
int maxVal = *std::max_element(nums.begin(), nums.end());
std::vector<int> count(maxVal + 1, 0);
for (int num : nums) {
count[num]++;
}
std::vector<int> topK;
for (int i = maxVal; i >= 0 && k > 0; i--) {
if (count[i] > 0) {
topK.insert(topK.end(), count[i], i);
k -= count[i];
}
}
nums = topK;
}
// 使用示例
int main() {
std::vector<int> nums = {3, 2, 1, 5, 6, 4};
int k = 2;
topKCountingSort(nums, k);
for (int num : nums) {
std::cout << num << " ";
}
return 0;
}
- 近似算法: 对于大数据集,可以使用近似算法如LSH(局部敏感哈希),但这通常涉及到更复杂的数据结构和算法。
每种方法都有其适用场景和优缺点。在实际应用中,需要根据数据的特性和性能要求来选择最合适的方法。