• 数据结构——堆排序


    目录

     堆的建立

     向上调整建堆 

    向下调整建堆 

    向上调整和向下调整时间复杂度的比较 

    大小堆如何选择 

    Top-K问题 

    之前我们实现的堆是创建一个数组,将元素组的内容拷贝过来进行排序的,这样的空间复杂度是O(N),我们可对堆进行优化,使得不拷贝元素,直接在原数组上进行操作使得原数组变为有序的数组

     堆的建立

     向上调整建堆 

    1. //建堆——向上调整建堆 第一个元素看作一个堆,后面的元素看作依次插入
    2. for (int i = 1; i < n; ++i)
    3. {
    4. AdjustUp(a, i);
    5. }

    向下调整建堆 

    之前向下调整的建堆方法是,头尾交换然后将头向下调整,向下调整的前提是左右子树都是大堆或小堆

    当数组是这个样子。就无法使用向下调整 

    我们可以从倒数第一个非叶子节点(倒数第一个叶子节点的父节点)开始向下开始调,直到调整到根,叶子节点没必要调(兄弟关系),

     先让i指向60,然后60和70交换,i--,i指向100再进行调整,之后再i--,i等于100进行调整

    1. //建堆——向下调整建堆
    2. for (int i = (n - 1 - 1) / 2; i >= 0; --i) //n-1是最后一个节点,(n-1-1)/2是最后一个节点的父节点
    3. //物理上操作数组,逻辑上在操作堆
    4. {
    5. AdjusuDown(a, n, i);
    6. }

    向上调整和向下调整时间复杂度的比较 

    向下调整时间复杂度:O(N)

     向下调整总步数=该层个数*需向下调整次数

     当满二叉树节点较多时,最后一层节点的个数约为总结点个数的一半,向下调整的好处:节点所在层数越小(以第1层为比较基准),调整次数越多,节点所在层数越大,此时节点越多,调整次数越小

     向上调整时间复杂度:O(N*logN)

     

     利用错位相减可计算出调整次数

    我们只算最后一层的调整次数

     

     向上调整:越往下节点越多,调整次数越多,算法时间复杂度过大

    大小堆如何选择 

     对于升序和降序我们该如何选择建堆方式?

    答案:1.升序——大堆     2.降序——小堆

    大思路:选择排序,依次选数,从后往前排

    升序建小堆会出问题

     

     建堆的时间复杂度是O(N),效率低

     选数

    1. //选数
    2. int i = 0;
    3. while (i < n)
    4. {
    5. Swap(&a[0], &a[n - i]);
    6. AdjusuDown(a, n - i, 0);
    7. ++i;
    8. }
    1. void AdjusuDown(HPDataTypedef* a,int n, HPDataTypedef parent)
    2. {
    3. HPDataTypedef minChild = parent * 2 + 1;
    4. while (minChild
    5. {
    6. if (minChild+11] >a[minChild])
    7. {
    8. minChild++;
    9. }
    10. if (a[minChild] > a[parent])
    11. {
    12. Swap(&a[parent], &a[minChild]);
    13. parent = minChild;
    14. minChild = parent * 2 + 1;
    15. }
    16. else
    17. break;
    18. }
    19. }
    20. void Heapsort(int* a, int n)
    21. {
    22. //建堆——向下调整建堆
    23. for (int i = (n - 1 - 1) / 2; i >= 0; --i) //n-1是最后一个节点,(n-1-1)/2是最后一个节点的父节点
    24. //物理上操作数组,逻辑上在操作堆
    25. {
    26. AdjusuDown(a, n, i);
    27. }
    28. //选数
    29. int i = 1;
    30. while (i < n)
    31. {
    32. Swap(&a[0], &a[n - i]);
    33. AdjusuDown(a, n - i, 0);
    34. ++i;
    35. }
    36. }
    37. int main()
    38. {
    39. int a[] = { 65,100,75,32,50,60 };
    40. Heapsort(a, 6);
    41. return 0;
    42. }

    Top-K问题 

     TOP-K问题:即求数据结合中前K个最大的元素或者最小的元素,一般情况下数据量都比较大。
    比如:专业前10名、世界500强、富豪榜、游戏中前100的活跃玩家等。
    对于Top-K问题,能想到的最简单直接的方式就是排序,但是:如果数据量非常大,排序就不太可取了(可能
    数据都不能一下子全部加载到内存中)。最佳的方式就是用堆来解决,基本思路如下:
    1. 用数据集合中前K个元素来建堆
    前k个最大的元素,则建小堆
    前k个最小的元素,则建大堆
    2. 用剩余的N-K个元素依次与堆顶元素来比较,不满足则替换堆顶元素

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

    1.排序——O(N*logN)

    2.堆选数

      a.建大堆和建小堆都可以,建大堆相当于选K次(Pop k次)时间复杂度:O(N+logN*k)

     更好的方式,建小堆

    1.用前K个数,建K个数的小堆

    2.依次遍历后续的N-k个数,如果比堆里的数据大,就替换堆里的数据,然后向下替换

    ,堆里的数据就是最大的前K个

    时间复杂度:K+logK*(N-K),综合之后O(N),空间复杂度O(K)

    以文件方式进行操作(这里数组大小为10,K=10)

    1. void PrintTopK(const char* filename, int k)
    2. {
    3. assert(filename);
    4. FILE* fout = fopen(filename, "r");
    5. if (fout == NULL)
    6. {
    7. perror("fopen fail");
    8. return;
    9. }
    10. int* minHeap = (int*)malloc(sizeof(int) * k);
    11. if (minHeap == NULL)
    12. {
    13. perror("malloc fail");
    14. return;
    15. }
    16. // 如何读取前K个数据
    17. for (int i = 0; i < k; ++i)
    18. {
    19. fscanf(fout, "%d", &minHeap[i]);//scanf系列会忽略中间的空格 换行
    20. }
    21. // 建k个数小堆
    22. for (int j = (k - 2) / 2; j >= 0; --j)
    23. {
    24. AdjusuDown(minHeap, k, j);
    25. }
    26. // 继续读取后N-K
    27. int val = 0;
    28. while (fscanf(fout, "%d", &val) != EOF)
    29. {
    30. if (val > minHeap[0])
    31. {
    32. minHeap[0] = val;
    33. AdjusuDown(minHeap, k, 0);
    34. }
    35. }
    36. for (int i = 0; i < k; ++i)
    37. {
    38. printf("%d ", minHeap[i]);
    39. }
    40. free(minHeap);
    41. fclose(fout);
    42. }
    43. int main()
    44. {
    45. const char* filename = "Data.txt";
    46. int N = 10000;
    47. int K = 10;
    48. //CreateDataFile(filename, N);
    49. PrintTopK(filename, K);
    50. return 0;
    51. }
    1. void CreateDataFile(const char* filename, int N)
    2. {
    3. FILE* fin = fopen(filename, "w");
    4. if (fin == NULL)
    5. {
    6. perror("fopen fail");
    7. return;
    8. }
    9. srand(time(0));
    10. for (int i = 0; i < N; ++i)
    11. {
    12. fprintf(fin, "%d\n", rand() % 1000000);
    13. }
    14. fclose(fin);
    15. }
    16. void PrintTopK(const char* filename, int k)
    17. {
    18. assert(filename);
    19. FILE* fout = fopen(filename, "r");
    20. if (fout == NULL)
    21. {
    22. perror("fopen fail");
    23. return;
    24. }
    25. int* minHeap = (int*)malloc(sizeof(int) * k);
    26. if (minHeap == NULL)
    27. {
    28. perror("malloc fail");
    29. return;
    30. }
    31. // 如何读取前K个数据
    32. for (int i = 0; i < k; ++i)
    33. {
    34. fscanf(fout, "%d", &minHeap[i]);//scanf系列会忽略中间的空格 换行
    35. }
    36. // 建k个数小堆
    37. for (int j = (k - 2) / 2; j >= 0; --j)//(k-1-1)/2
    38. {
    39. AdjusuDown(minHeap, k, j);
    40. }
    41. // 继续读取后N-K
    42. int val = 0;
    43. while (fscanf(fout, "%d", &val) != EOF)
    44. {
    45. if (val > minHeap[0])
    46. {
    47. minHeap[0] = val;
    48. AdjusuDown(minHeap, k, 0);
    49. }
    50. }
    51. for (int i = 0; i < k; ++i)
    52. {
    53. printf("%d ", minHeap[i]);
    54. }
    55. free(minHeap);
    56. fclose(fout);
    57. }
    58. int main()
    59. {
    60. const char* filename = "Data.txt";
    61. int N = 10000;
    62. int K = 10;
    63. CreateDataFile(filename, N);//
    64. PrintTopK(filename, K);
    65. return 0;
    66. }

  • 相关阅读:
    Nginx优化与防盗链
    Spring事务之@Transactional注解详解
    可视化—gojs 超多超实用经验分享(四)
    33张Java高级进阶技术思维导图,白嫖大佬梳理的技术要点!只需看重点,学习效率提升300%(建议收藏)
    token过期 如何使用refresh_token实现无感刷新页面?
    C++如何让自己变得富有?
    asp.net core之Host
    《Java基础知识》Java 内省(Introspector)详解2
    C++ 提高编程
    2021年50道Java线程面试题
  • 原文地址:https://blog.csdn.net/weixin_49449676/article/details/126239775