• 【数据结构】堆的应用-----TopK问题


    目录

    一、前言

    二、Top-k问题 

     💦解法一:暴力排序

    💦解法二:建立N个数的堆

    💦解法三:建立K个数的堆(最优解)

    三、完整代码和视图 

    四、共勉


    一、前言

    在之前的文章中,已经详细的讲解了二叉树、堆、堆排序。那么关于堆还有一个比较有意思的题,就是TopK问题。

    如果对堆和二叉树还不够了解的可以看看我之前的文章哦!!!

    详解二叉树和堆

    二、Top-k问题 

    Top-k问题:在 N 个数中,找出前 K 个(最大/最小)的元素,一般情况下数据量 N 都远大于 k。

    Top-k问题在生活中是非常的常见,比如游戏中某个大区某个英雄熟练度最高的前10个玩家的排名,我们就要根据每个玩家对该英雄的熟练度进行排序,可能有200万个玩家,但我只想选出前10个,要对所有人去排个序吗?显然没这个必要。

    再比如:专业前10名、世界500强、富豪榜、游戏中前100的活跃玩家等。

     💦解法一:暴力排序

    对于Top-K问题,首先想到的最简单直接的方式就是排序。

    我们用堆排序,其时间复杂度为:O(N*log2N)。

    但是:如果数据量非常大,排序就不太可取了(可能数据都不能一下子全部加载到内存中)。


    💦解法二:建立N个数的堆

    建一个 N 个数的堆(C++中可用优先级队列priority_queue),不断的选数,选出前 k 个。

    时间复杂度:建N个数的堆为O(N),获取堆顶元素 (也即是最值) 并删除掉堆顶元素为O(log2N),上述操作重复 k 次,所以时间复杂度为O(N+k*log2N)。

    【思考】

    能否再优化一下呢?假设 N 是 10 亿数,内存中放不下,是放在文件中的。前面两个方法都不能用了。


    💦解法三:建立K个数的堆(最优解)

    ✨基本思想:

    用数据集合中前K个元素来建堆。

    找前 k 个最大的元素,则建小堆

    找前 k 个最小的元素,则建大堆

    用剩余的 N-K 个元素依次与堆顶元素来比较,不满足则删除堆顶元素,再插入。

    找前 k 个最大的元素,大于堆顶元素,则删除堆顶元素,再插入

    找前 k 个最小的元素,小于堆顶元素,则删除堆顶元素,再插入

    将剩余的 N-K 个元素依次与堆顶元素比完之后,堆中剩余的K个元素就是所求的前K个最小或者最大的元素。


    ✨时间复杂度:

    ▶ 建 k 个元素的堆为O(K);
    ▶ 遍历剩余的 N-K 个元素的时间代价为O(N-K),假设运气很差,每次遍历都入堆调整;
    ▶ 入堆调整:删除堆顶元素和插入元素都为O(log2K);
    ▶ 所以时间复杂度为O(k + (N-K)log2K)。当 N 远大于 K 时,为O(N*log2K),这种解法更优。

     

    ✨假如要找出最大的前 10 个数

    ▶ 建立 10 个元素的小堆,数据集合中前 10 个元素依次放入小堆,此时的堆顶元素是堆中最小的元素,也是堆里面第 10 个最小的元素,
    ▶  然后把数据集合中剩下的元素与堆顶比较,若大于堆顶则去掉堆顶,再将其插入,
    ▶  这样一来,堆里面存放的就是数据集合中的前 10 个最大元素,
    此时小堆的堆顶元素也就是堆中的第 10 个最大的元素

     

    ✨思考:为什么找出最大的前10个数,不能建大堆呢?

    如果你建的10个元素的大堆,堆顶元素恰好是数据集合中最大的那个,那第2大的数、第3大的数不就能找不到了。

    三、完整代码和视图 

    以从1w个数里找出最大的前10个数为例:

    1. #include <stdio.h>
    2. #include <stdlib.h>
    3. #include <assert.h>
    4. #include <stdbool.h>
    5. typedef int HPDatatype;
    6. void Swap(HPDatatype* x, HPDatatype* y)
    7. {
    8. HPDatatype temp = 0;
    9. temp = *x;
    10. *x = *y;
    11. *y = temp;
    12. }
    13. void AdjustDown(HPDatatype* a,int n,int parent)
    14. {
    15. // 左孩子
    16. int child = parent * 2 + 1;
    17. // 防止越界
    18. while (child < n)
    19. {
    20. //小堆
    21. if (child + 1 < n && a[child] > a[child + 1])
    22. {
    23. child++;
    24. }
    25. // 开始向下调整
    26. if (a[child] < a[parent])
    27. {
    28. Swap(&a[child], &a[parent]);
    29. parent = child;
    30. child = parent * 2 + 1;
    31. }
    32. else
    33. {
    34. break;
    35. }
    36. }
    37. }
    38. void TopK(HPDatatype* a, int n, int k)
    39. {
    40. HPDatatype* kminHeap = (HPDatatype*)malloc(sizeof(HPDatatype) * k);
    41. assert(kminHeap);
    42. // 1. 建堆----用a中前k个元素建堆
    43. for (int i = 0; i < k; i++)
    44. {
    45. kminHeap[i] = a[i];
    46. }
    47. // 建小堆
    48. for (int j = ((n - 1) - 1) / 2; j >= 0; j--)
    49. {
    50. // 从倒数第一个非叶子节点开始
    51. AdjustDown(kminHeap, k, j);
    52. }
    53. // 2. 将剩余n-k个元素依次与堆顶的元素交换,比堆顶大,交换
    54. for (int i = k; i < n; i++)
    55. {
    56. if (a[i] > kminHeap[0])
    57. {
    58. kminHeap[0] = a[i];//如果比堆顶大,就替换
    59. AdjustDown(kminHeap, k, 0);//向下调整确保为堆
    60. }
    61. }
    62. for (int j = 0; j < k; j++)
    63. {
    64. printf("%d ", kminHeap[j]);
    65. }
    66. printf("\n");
    67. free(kminHeap);
    68. }
    69. int main()
    70. {
    71. int n = 10000;
    72. int* a = (int*)malloc(sizeof(int) * n);
    73. srand(time(0));
    74. for (int i = 0; i < n; ++i)
    75. {
    76. a[i] = rand() % 1000000; //产生一个随机数,数值均小于100
    77. }
    78. a[5] = 1000000 + 1;
    79. a[1231] = 1000000 + 2;
    80. a[531] = 1000000 + 3;
    81. a[5121] = 1000000 + 4;
    82. a[115] = 1000000 + 5;
    83. a[2335] = 1000000 + 6;
    84. a[9999] = 1000000 + 7;
    85. a[76] = 1000000 + 8;
    86. a[423] = 1000000 + 9;
    87. a[3144] = 1000000 + 10;
    88. TopK(a, n, 10);
    89. return 0;
    90. }

    四、共勉

     以下就是我对数据结构---堆排序的理解,如果有不懂和发现问题的小伙伴,请在评论区说出来哦,同时我还会继续更新对数据结构-------链式二叉树请持续关注我哦!!!!

  • 相关阅读:
    STM8单片机在医疗设备中的应用和优势
    智慧路灯解决方案-最新全套文件
    Java泛型
    mysql -mmm
    用C#实现最小二乘法(用OxyPlot绘图)✨
    sql执行插入语句返回刚刚生成的自动编号
    实现mnist手写数字识别
    SpringCloud 02 Rest学习环境搭建(DeptProvider)
    湖仓一体电商项目(十四):实时任务执行流程
    【雨夜】业务中 自定义异常用 Exception 还是 RuntimeException? 为什么?
  • 原文地址:https://blog.csdn.net/weixin_45031801/article/details/133522277