topK问题

博客主要提及了topK问题,这是信息技术领域常见问题,在数据处理、算法设计等方面有重要应用。

topK问题

### Top k 问题的详细介绍 Top k 问题指的是求数据集合中前 k 个最大的元素或者最小的元素,一般情况下数据量都比较大。比如专业前 10 名、世界 500 强、富豪榜、游戏中前 100 的活跃玩家等,都属于 Top k 问题的实际应用场景[^1]。 ### Top k 问题的解决方案 #### 1. 排序法 最简单直接的方式就是对所有数据进行排序,然后取前 k 个元素。不过这种方法存在明显弊端,快速排序的平均复杂度为 $O(nlogn)$,但最坏时间复杂度为 $O(n^2)$,不能始终保证较好的复杂度。并且只需要前 k 大的元素,却对其余不需要的数也进行了排序,浪费了大量排序时间[^2]。 #### 2. 堆解法 最佳的方式是用堆来解决。基本思路是用数据集合中前 k 个元素来建堆,若求前 k 个最大的元素,则建小堆;若求前 k 个最小的元素,则建大堆。以下是使用 C 语言实现用堆解决 Top k 问题的代码示例: ```c #include <stdio.h> #include <stdlib.h> #include <time.h> // 向下调整函数 void AdjustDown(int* topk, int k, int i) { int parent = i; int child = 2 * parent + 1; while (child < k) { if (child + 1 < k && topk[child + 1] < topk[child]) { child++; } if (topk[child] < topk[parent]) { int temp = topk[child]; topk[child] = topk[parent]; topk[parent] = temp; parent = child; child = 2 * parent + 1; } else { break; } } } // 打印前 k 个最大元素 void PrintTopk(const char* file, int k) { int* topk = (int*)malloc(sizeof(int) * k); FILE* fout = fopen(file, "r"); if (fout == NULL) { perror("fopen error"); return; } // 读出前 k 个建堆 for (int i = 0; i < k; i++) { fscanf(fout, "%d", &topk[i]); } for (int i = (k - 1 - 1) / 2; i >= 0; i--) { AdjustDown(topk, k, i); } // 用剩余的 n - k 个元素依次与堆顶元素来比较,不满足则替换堆顶元素 int val = 0; int ret = fscanf(fout, "%d", &val); while (ret != EOF) { if (val > topk[0]) { topk[0] = val; AdjustDown(topk, k, 0); } ret = fscanf(fout, "%d", &val); } for (int i = 0; i < k; i++) { printf("%d ", topk[i]); } free(topk); fclose(fout); } // 测试函数 void TestTopk() { int n = 10000; srand(time(0)); const char* file = "data.txt"; FILE* fin = fopen(file, "w"); if (fin == NULL) { perror("fopen error"); return; } for (size_t i = 0; i < n; i++) { int x = rand() % 10000; fprintf(fin, "%d\n", x); } PrintTopk(file, 10); } int main() { TestTopk(); return 0; } ``` 该代码通过文件存储数据,先读取前 k 个元素构建小堆,再用剩余元素与堆顶比较,若比堆顶大则替换堆顶并调整堆,最终输出前 k 个最大元素[^4]。 #### 3. 快速选择法 快速选择可以看做是快速排序的子过程,只关注某一部分元素的选择过程。例如,寻找数组中 top - k 个最小的元素,假设某一次 partition 后 pivot 元素所在的数组索引为 m: - 若 $m == k$,那么它之前的 k 个元素即所求结果,直接返回即可; - 若 $m > k$,那么最小的 k 个元素必定位于 pivot 的左侧,只需要对左侧数组递归进行 partition; - 若 $m < k$,那么 pivot 左侧的 m 个元素是结果的一部分,还需对右侧数组递归进行 partition,寻找右侧数组中最小的 $k - m$ 个元素[^3]。 #### 4. 优先队列法 在 Java 中可以使用优先队列(堆)来解决 Top k 问题。以下是一个求数组中最小的 k 个数的 Java 代码示例: ```java import java.util.PriorityQueue; import java.util.Comparator; class Solution { public int[] smallestK(int[] arr, int k) { if (arr == null || k == 0) { return new int[0]; } PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(new Comparator<Integer>() { @Override public int compare(Integer o1, Integer o2) { return o2 - o1; } }); // 然后将前 k 个数据加入 for (int i = 0; i < k; i++) { priorityQueue.offer(arr[i]); } // 从下标第 k 个开始判断,小则删除堆顶,加入 for (int i = k; i < arr.length; i++) { int val = priorityQueue.peek(); if (val > arr[i]) { // 弹出大 priorityQueue.poll(); // 加入小的 priorityQueue.offer(arr[i]); } } // 加入数组 int[] temp = new int[k]; for (int i = 0; i < k; i++) { temp[i] = priorityQueue.poll(); } return temp; } } ``` 该代码先将前 k 个数据构建成大根堆,然后从第 k 个下标开始比较堆顶数据和当前下标的数据,如果比堆顶的小,则删除堆顶数据,添加当前下标数据,然后进行调整,直到遍历数组后,堆中的 k 个数据就是最小的 k 个数,最后将堆中数据放入数组返回[^5]。 ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值