数据结构之堆的应用—TopK问题

x33g5p2x  于2021-10-07 转载在 其他  
字(2.6k)|赞(0)|评价(0)|浏览(392)

TopK问题

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

这里我们讲解三种方法:

方法一:排序

思路:
将所有数据进行排序,取前K个元素即可

  1. void swap(int *p1,int *p2)
  2. {
  3. int temp=*p1;
  4. *p1=*p2;
  5. *p2=temp;
  6. }
  7. void AdjustDown(int *a,int n,int parent)
  8. {
  9. int child=parent*2+1;
  10. while(child<n)
  11. {
  12. if(child+1<n && a[child+1]<a[child])//右孩子存在且右孩子小于或大于左孩子(建小堆找小的孩子,大堆找大的孩子)
  13. {
  14. child=child+1;
  15. }
  16. if(a[parent]>a[child])
  17. {
  18. swap(&a[parent],&a[child]);
  19. parent=child;
  20. child=parent*2+1;
  21. }
  22. else
  23. {
  24. break;
  25. }
  26. }
  27. }
  28. void HeapSort(int *a,int n)
  29. {
  30. //建堆
  31. for(int i=(n-1-1)/2;i>=0;i--)
  32. {
  33. AdjustDown(a,n,i);
  34. }
  35. //排序
  36. int end = n-1;
  37. while(end>0)
  38. {
  39. swap(&a[0],&a[end]);
  40. end--;
  41. AdjustDown(a,end,0);
  42. }
  43. }
  44. void PrintTopK(int *a,int n,int k)
  45. {
  46. HeapSort(a,n);
  47. for(int i=0;i<k;i++)
  48. {
  49. printf("%d ",a[i]);
  50. }
  51. printf("\n");
  52. }

*时间复杂度O(N/logN)

方法二:将N个数建堆,取出前K个

思路:
建好堆,(找最大的前K个建大堆,找最小的前K个建小堆)然后将堆顶的数与最后一个数交换位置,向下调整一次找次大/小的数(注意向下调整时不要包含交换后的最后一个位置),重复这样的操作,直到找完K个数

  1. void swap(int *p1,int *p2)
  2. {
  3. int temp=*p1;
  4. *p1=*p2;
  5. *p2=temp;
  6. }
  7. void AdjustDown(int *a,int n,int parent)
  8. {
  9. int child=parent*2+1;
  10. while(child<n)
  11. {
  12. if(child+1<n && a[child+1]<a[child])//右孩子存在且右孩子小于或大于左孩子(建小堆找小的孩子,大堆找大的孩子)
  13. {
  14. child=child+1;
  15. }
  16. if(a[parent]>a[child])
  17. {
  18. swap(&a[parent],&a[child]);
  19. parent=child;
  20. child=parent*2+1;
  21. }
  22. else
  23. {
  24. break;
  25. }
  26. }
  27. }
  28. void PrintTopK(int *a,int n,int k)
  29. {
  30. //建堆
  31. for(int i=(n-1-1)/2;i>=0;i--)
  32. {
  33. AdjustDown(a,n,i);
  34. }
  35. int end=n-1;
  36. for(int i=0;i<k;i++)
  37. {
  38. printf("%d ",a[0]);
  39. swap(&a[0],&a[end]);//交换最值和最后一个位置的数
  40. end--;//不包含最后一个位置
  41. AdjustDown(a,end,0);//调整
  42. }
  43. }

*时间复杂度O(N+K/logN)

方法三:建一个K个数的堆(最优)

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

对于Top-K问题,能想到的最简单直接的方式就是排序,但是:如果数据量非常大,排序就不太可取了(可能数据都不能一下子全部加载到内存中)。最佳的方式就是用堆来解决,基本思路如下:

用数据集合中前K个元素来建堆
前k个最大的元素,则建小堆
前k个最小的元素,则建大堆
*
用剩余的N-K个元素依次与堆顶元素来比较,不满足则替换堆顶元素

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

思路:

首先我们建一个K个数的堆,需要前K个最大的数时,建小堆。需要前K个最小的数时,建大堆。然后将n-k个数依次与堆顶比较,如果比堆顶小(需要前K小个数)/大(需要前K大个数),则进K个数的堆,然后调整找次大/小的数

注意:

需要前K个最大的数时,建小堆

需要前K个最小的数时,建大堆

  1. #include<stdio.h>
  2. void swap(int* p1, int* p2)
  3. {
  4. int temp = *p1;
  5. *p1 = *p2;
  6. *p2 = temp;
  7. }
  8. void AdjustDown(int* a, int n, int parent)
  9. {
  10. int child = parent * 2 + 1;
  11. while (child < n)
  12. {
  13. if (child + 1 < n && a[child + 1] < a[child])//右孩子存在且右孩子小于或大于左孩子(建小堆找小的孩子,大堆找大的孩子)
  14. {
  15. child = child + 1;
  16. }
  17. if (a[parent] > a[child])
  18. {
  19. swap(&a[parent], &a[child]);
  20. parent = child;
  21. child = parent * 2 + 1;
  22. }
  23. else
  24. {
  25. break;
  26. }
  27. }
  28. }
  29. void PrintTopK(int* a, int n, int k)
  30. {
  31. //建k个数的堆
  32. for (int i = (k - 1 - 1) / 2; i >= 0; i--)//O(k)
  33. {
  34. AdjustDown(a, k, i);//取最大的需要建小堆,取最小的需要建大堆
  35. }
  36. for (int i = k; i < n; i++)//O(n*logk)
  37. {
  38. if (a[0] < a[i])//比堆顶大的就入堆
  39. {
  40. swap(&a[0], &a[i]);
  41. AdjustDown(a, k, 0);
  42. }
  43. }
  44. for (int i = 0; i < k; i++)
  45. {
  46. printf("%d ", a[i]);
  47. }
  48. }

*时间复杂度O(K+N/logK),当我们要取前K个最大的数时,建一个K个数的小堆,剩下的数比堆顶大,就替换堆顶的数据进堆当最大的前K个数,一些数没进堆,那么说明其它数在堆里面,这些数肯定比topK要小,当最大的K数中某一个来了以后,一定比堆顶的数据大,一定能进堆

相关文章