当前位置:   article > 正文

堆排序之“TOP-K”问题_top k

top k

目录

一、什么是TOP-K问题

二、解决思路 

一般的正常思路:

最优的解决思路:

三、文件流中实践TOP-K方法 

创建包含足够多整数的文件:

向下调整算法

找出最大的K个数

完整版代码:


前面我已经学习过使用“堆排序”对数组排降序了,接下来再来看一个堆排序的应用场景。

一、什么是TOP-K问题

TOP-K问题:即求数据结合中前K个最大的元素或者最小的元素,一般情况下数据量都比较大,即:N个数找最大的前K个。

二、解决思路 

一般的正常思路:

把这N个数建成大堆,Pop弹出K次堆顶,即可找出最大的前K个数,但是有些场景这种思路解决不了,例如N非常大时,假设N是10亿,10亿个数建堆所需的空间我们来计算一下:

一个整型变量需要四个字节空间,10亿个整型数据需要40亿个字节,1G可以放10亿字节,所以我们需要 4G 空间为10亿个整型数据建堆。 

4G感觉不多的话,如果一百亿数据呢?一千亿呢?

内存无法承载这么大的空间时,数据会存储到磁盘上,磁盘的效率比内存慢很多,所以这种方法如果数据过多,就无法再内存上快速找到TOP-K。

最优的解决思路:

  1. 前K个数建小堆。
  2. 后面N-K个数,依次比较,如果比堆顶的数据大,就替换它进堆,进堆后向下调整。
  3. 最后这个小堆的值就是最大的前K个。

三、文件流中实践TOP-K方法 

我们来在文件中实践一下这个方法:

创建包含足够多整数的文件:

  1. void CreateNData()
  2. {
  3. int n = 100000;
  4. srand(time(0));
  5. const char* file = "data.txt";
  6. FILE* fin = fopen(file, "w");
  7. if (fin == NULL)
  8. {
  9. perror("fopen error");
  10. return;
  11. }
  12. for (size_t i = 0; i < n; i++) {
  13. int x = rand() % 10000000;
  14. fprintf(fin, "%d\n", x);
  15. }
  16. fclose(fin);
  17. }
  • 定义一个变量n,表示要生成的随机整数的数量为十万个。
  • 使用srand函数设置随机数种子,time(0)返回当前时间的秒数,确保每次运行程序生成的随机数序列都不同。
  • 定义一个文件名file,表示要生成的文件名。
  • 使用fopen函数打开文件,以写入模式打开,如果没有文件,则创建一个,如果文件打开失败,输出一个错误信息并返回。
  • 使用for循环生成n个随机整数,并使用fprintf函数将它们写入文件中。
  • 使用fclose函数关闭文件。

如果建小堆的方法—向下调整忘记了,我们来复习一下·

向下调整算法

  1. void AdjustDown(HPDataType* a, int size, int parent)
  2. {
  3. int child = parent * 2 + 1;
  4. while (child < size) {
  5. if (child + 1 < size && a[child + 1] < a[child]) {
  6. child++;
  7. }
  8. if (a[child] < a[parent]) {
  9. Swap(&a[child], &a[parent]);
  10. parent = child;
  11. child = parent * 2 - 1;
  12. }
  13. else {
  14. break;
  15. }
  16. }
  17. }
  • 通过传入参数获取到当前的左子节点的位置。
  • 当child位置小于数组元素个数时进行判断。
  • 进入循环,首先判断检查右子节点是否存在并且比左子节点的值小,如果是,将 child 更新为右子节点的索引,以确保选择更小的子节点进行比较。
  • 比较选定的子节点的值与父节点的值,如果子节点的值小于父节点的值,就交换它们。
  • 更新parent为新的子节点位置,更新child为新的左子节点位置,然后继续比较和交换,直到不再需要交换为止。
  • 如果当前子节点不小于当前父节点则停止循环。

 找出最大的K个数

  1. void PrintTopK(int k)
  2. {
  3. const char* file = "data.txt";
  4. FILE* fout = fopen(file, "r");
  5. if (fout == NULL)
  6. {
  7. perror("fopen error");
  8. return;
  9. }
  10. int* kminheap = (int*)malloc(sizeof(int) * k);
  11. if (kminheap == NULL)
  12. {
  13. perror("malloc error");
  14. return;
  15. }
  16. for (int i = 0; i < k; i++)
  17. {
  18. fscanf(fout, "%d", &kminheap[i]);
  19. }
  20. for (int i = (k - 1 - 1) / 2; i >= 0; i--)
  21. {
  22. AdjustDown(kminheap, k, i);
  23. }
  24. int val = 0;
  25. while (!feof(fout))
  26. {
  27. fscanf(fout, "%d", &val);
  28. if (val > kminheap[0])
  29. {
  30. kminheap[0] = val;
  31. AdjustDown(kminheap, k, 0);
  32. }
  33. }
  34. for (int i = 0; i < k; i++)
  35. {
  36. printf("%d ", kminheap[i]);
  37. }
  38. printf("\n");
  39. }

代码较长进行分段讲解: 

  1. const char* file = "data.txt";
  2. FILE* fout = fopen(file, "r");
  3. if (fout == NULL)
  4. {
  5. perror("fopen error");
  6. return;
  7. }
  8. int* kminheap = (int*)malloc(sizeof(int) * k);
  9. if (kminheap == NULL)
  10. {
  11. perror("malloc error");
  12. return;
  13. }
  14. for (int i = 0; i < k; i++)
  15. {
  16. fscanf(fout, "%d", &kminheap[i]);
  17. }
  • 定义一个文件名file,表示要读取的文件名。
  • 使用fopen函数打开文件,以读取模式打开,如果文件打开失败,输出一个错误信息并返回。
  • 使用malloc函数动态分配一个大小为k的整数数组 kminheap,用于存储最大的 k 个数,如果内存分配失败,输出一个错误信息并返回。
  • 使用for循环从文件中读取前k个整数,并将它们存储到kminheap数组中。
  1. for (int i = (k - 1 - 1) / 2; i >= 0; i--)
  2. {
  3. AdjustDown(kminheap, k, i);
  4. }
  5. int val = 0;
  6. while (!feof(fout))
  7. {
  8. fscanf(fout, "%d", &val);
  9. if (val > kminheap[0])
  10. {
  11. kminheap[0] = val;
  12. AdjustDown(kminheap, k, 0);
  13. }
  14. }
  15. for (int i = 0; i < k; i++)
  16. {
  17. printf("%d ", kminheap[i]);
  18. }
  19. printf("\n");
  • 使用向下调整函数将kminheap数组构建成一个小堆(从小到大)。
  •  feof(fout)是一个C标准库函数,用于判断文件指针fout所指向的文件是否已经到达文件末尾。该函数的返回值为非零值表示已经到达文件末尾,返回值为0表示文件还没有到达末尾。
  • 使用while循环从文件中读取剩余的N-K整数,如果某个整数比堆顶元素大,就将它替换堆顶元素,并使用AdjustDown函数将堆重新调整为小堆,
  • 因为我们需要前K个最大的数,而建的小堆也是K个元素,所以这种操作可以得到由最大前K个元素构成的小堆。
  • 使用for循环输出堆中的所有元素。
  • 使用fclose函数关闭文件。

 我们来测试一下,首先我在主函数调用CreateNData函数对文件data.txt写入文件。

  1. int main()
  2. {
  3. CreateNData();
  4. return 0;
  5. }

然后在文件中对五个数添加几位使其成为最大的五个数。

这是屏蔽掉 CreateNData 函数,防止重新生成数字覆盖我修改的数字,调用PrintTopK函数输出5个最大的数。

  1. int main()
  2. {
  3. //CreateNData();
  4. PrintTopK(5);
  5. return 0;
  6. }

输出结果为: 

完整版代码:

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <time.h>
  4. void Swap(HPDataType* p1, HPDataType* p2)
  5. {
  6. HPDataType tmp = *p1;
  7. *p1 = *p2;
  8. *p2 = tmp;
  9. }
  10. void AdjustDown(HPDataType* a, int size, int parent)
  11. {
  12. int child = parent * 2 + 1;
  13. while (child < size) {
  14. if (child + 1 < size && a[child + 1] < a[child]) {
  15. child++;
  16. }
  17. if (a[child] < a[parent]) {
  18. Swap(&a[child], &a[parent]);
  19. parent = child;
  20. child = parent * 2 - 1;
  21. }
  22. else {
  23. break;
  24. }
  25. }
  26. }
  27. void CreateNData()
  28. {
  29. int n = 100000;
  30. srand(time(0));
  31. const char* file = "data.txt";
  32. FILE* fin = fopen(file, "w");
  33. if (fin == NULL)
  34. {
  35. perror("fopen error");
  36. return;
  37. }
  38. for (size_t i = 0; i < n; i++) {
  39. int x = rand() % 10000000;
  40. fprintf(fin, "%d\n", x);
  41. }
  42. fclose(fin);
  43. }
  44. void PrintTopK(int k)
  45. {
  46. const char* file = "data.txt";
  47. FILE* fout = fopen(file, "r");
  48. if (fout == NULL)
  49. {
  50. perror("fopen error");
  51. return;
  52. }
  53. int* kminheap = (int*)malloc(sizeof(int) * k);
  54. if (kminheap == NULL)
  55. {
  56. perror("malloc error");
  57. return;
  58. }
  59. for (int i = 0; i < k; i++)
  60. {
  61. fscanf(fout, "%d", &kminheap[i]);
  62. }
  63. // 建小堆
  64. for (int i = (k - 1 - 1) / 2; i >= 0; i--)
  65. {
  66. AdjustDown(kminheap, k, i);
  67. }
  68. int val = 0;
  69. while (!feof(fout))
  70. {
  71. fscanf(fout, "%d", &val);
  72. if (val > kminheap[0])
  73. {
  74. kminheap[0] = val;
  75. AdjustDown(kminheap, k, 0);
  76. }
  77. }
  78. for (int i = 0; i < k; i++)
  79. {
  80. printf("%d ", kminheap[i]);
  81. }
  82. printf("\n");
  83. fclose(fout);
  84. }
  85. int main()
  86. {
  87. CreateNData();
  88. PrintTopK(5);
  89. return 0;
  90. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/盐析白兔/article/detail/804077
推荐阅读
相关标签
  

闽ICP备14008679号