当前位置:   article > 正文

TopK问题与堆排序_topk的堆算法

topk的堆算法

目录

1、堆的实现

(1)定义堆

(2)初始化堆

(3)交换父子结点

(4)打印堆内数据

(5)堆是否为空

(6)取堆顶数据

(7)销毁堆

(8)✳向上调整

(9)✳数据插入堆(拿最大堆举例,即:插入后仍是保持大堆)

对于插入堆来说,最大堆、最小堆都是向上调整

(10)✳向下调整

(11)✳删除堆顶数据(拿最大堆举例,即:删除后仍保持是大堆)

对于删除堆顶数据来说,最大堆、最小堆都是需要向下调整

※向下、向上调整的时间复杂度log2_N

2、建堆的应用

(1)TopK问题

实现TopK算法(最小的K个数)-- 建立小堆

(2)堆排序

堆排序  -  (向下调整建x堆+x堆)

排升序  -  (向下调整建大堆+大堆)

排降序  -  (向下调整建小堆+小堆)

建堆的时间复杂度O(N)证明


1、堆的实现

因为堆是一棵完全二叉树,所以使用数组结构,不会浪费空间。

所以,使用数组结构实现堆

代码以大堆,作为插入堆、删除根节点为例子,后续不再说明:


(1)定义堆

可以看到,堆的结构定义和顺序表类似

  1. typedef int HPDataType;
  2. typedef struct Heap
  3. {
  4. HPDataType* a;
  5. int size;
  6. int capacity;
  7. }HP;

(2)初始化堆

  1. //初始化堆
  2. void HeapInit(HP* hp)
  3. {
  4. assert(hp);
  5. hp->a = NULL;
  6. hp->size = hp->capacity = 0;
  7. }

(3)交换父子结点

  1. //交换父子结点数据
  2. void Swap(HPDataType* px, HPDataType* py)
  3. {
  4. HPDataType tmp = *px;
  5. *px = *py;
  6. *py = tmp;
  7. }

(4)打印堆内数据

  1. //打印堆内数据
  2. void HeapPrint(HP* hp)
  3. {
  4. for (int i = 0; i < hp->size; ++i)
  5. {
  6. printf("%d ", hp->a[i]);
  7. }
  8. printf("\n");
  9. }

(5)堆是否为空

  1. //堆为空
  2. bool HeapEmpty(HP* hp)
  3. {
  4. assert(hp);
  5. return hp->size == 0;
  6. }

(6)取堆顶数据

  1. //获取堆顶数据
  2. HPDataType HeapTop(HP* hp)
  3. {
  4. assert(hp);
  5. assert(!HeapEmpty(hp));
  6. return hp->a[0];
  7. }

(7)销毁堆

  1. //销毁堆
  2. void HeapDestroy(HP* hp)
  3. {
  4. assert(hp);
  5. free(hp->a);
  6. hp->capacity = hp->size = 0;
  7. }

(8)✳向上调整

插入堆如何实现呢?

注意:插入数据后,还要保证堆依旧是大堆(此处以大堆为列)

分析:


所以,需要向上调整

  1. 先将元素插入到堆的末尾,即最后一个孩子之后
  2. 插入之后如果堆的性质遭到破坏,则将新插入节点顺着其双亲往上调整到合适位置即可


向上调整的执行逻辑:

  1. //向上调整(大堆)
  2. void AdjustUp(int* a, int child)
  3. {
  4. assert(a);
  5. int parent = (child - 1) / 2;
  6. //while (parent >= 0) //这样写有Bug!
  7. while (child > 0)
  8. {
  9. if (a[child] > a[parent])
  10. {
  11. /*HPDataType tmp = a[child];
  12. a[child] = a[parent];
  13. a[parent] = tmp;*/
  14. Swap(&a[child], &a[parent]);
  15. child = parent;
  16. parent = (child - 1) / 2;
  17. }
  18. else
  19. {
  20. break;
  21. }
  22. }
  23. }

(9)✳数据插入堆(拿最大堆举例,即:插入后仍是保持大堆)

  1. //往堆插入数据
  2. void HeapPush(HP* hp, HPDataType x)
  3. {
  4. assert(hp);
  5. //不够就扩容
  6. if (hp->size == hp->capacity)
  7. {
  8. int newcapacity = hp->capacity == 0 ? 4 : hp->capacity * 2;
  9. HPDataType* tmp = realloc(hp->a, sizeof(HPDataType) * newcapacity);
  10. if (tmp == NULL)
  11. {
  12. printf("realloc fail!\n");
  13. exit(-1);
  14. }
  15. hp->a = tmp;
  16. hp->capacity = newcapacity;
  17. }
  18. hp->a[hp->size] = x;
  19. hp->size++;
  20. AdjustUp(hp->a, hp->size - 1);
  21. }

对于插入堆来说,最大堆、最小堆都是向上调整

区别:

最大堆向上调整,插入后的结点数据,要不改变最大堆(ps:定义  最大堆是每个节点的数据值都大于等于其子树数据值),影响了插入节点到根节点那段路径上的结点( 产生变化!):

  • 将插入结点作为child,与其父亲parent比较,如果a[child] > a[parent],就交换节点数据值,直到child是根节点,才截至!

最小堆向上调整,插入后的结点数据,要不改变最小堆(ps:定义  最小堆是每个节点的数据值都小于等于其子树数据值),影响了插入节点到根节点那段路径上的结点( 产生变化!):

  • 将插入结点作为child,与其父亲parent比较,如果a[child] < a[parent],就交换节点数据值,直到child是根节点,才截至!

(10)✳向下调整

分析:

删除堆顶数据,还要依旧保持是大堆,则需要向下调整

  1. 将堆顶元素与堆中最后一个元素进行交换
  2. 删除堆中最后一个元素
  3. 将堆顶元素向下调整到满足堆特性为止


向下调整过程:

注意:向下调整的循环结束条件:(大堆为例!)

  1. 父亲  >=  大的孩子,则停止      即:父亲比两个孩子都大
  2. 调整到叶子结点         (叶子特征:没有左孩子,即:左孩子下标超出数组范围,就不存在了)
  1. //向下调整(大堆)
  2. void AdjustDown(int* a, int n, int parent)
  3. {
  4. assert(a);
  5. int child = parent * 2 + 1;//左孩子
  6. while (child < n)
  7. {
  8. //选出大孩子
  9. if (child + 1 < n && a[child + 1] > a[child])
  10. child++;
  11. //交换 并 迭代 parent 和 child
  12. if (a[parent] < a[child])
  13. {
  14. Swap(&a[parent], &a[child]);
  15. parent = child;
  16. child = parent * 2 + 1;
  17. }
  18. else
  19. {
  20. break;
  21. }
  22. }
  23. }

(11)✳删除堆顶数据(拿最大堆举例,即:删除后仍保持是大堆)

  1. //删除堆顶数据
  2. void HeapPop(HP* hp)
  3. {
  4. assert(hp);
  5. assert(!HeapEmpty(&hp));
  6. Swap(&hp->a[0], &hp->a[hp->size - 1]);//堆顶数据与末尾数据交换
  7. hp->size--;
  8. AdjustDown(hp->a, hp->size, 0);
  9. }

对于删除堆顶数据来说,最大堆、最小堆都是需要向下调整

需要注意的是,根节点,也就是堆顶结点,我们直接删除是不行的!!   

需要将最后一个结点与根节点位置互换,因为我们删除尾结点(堆本质上是数组嘛~~~~~)是方便的!!!

然后再进行“  根结点  ” 向下调整...................

区别:

最大堆删除堆顶数据,看是否此时还仍然符合最大堆的要求,若不符合,那么就需要向下调整,调成符合最大堆的形式。

考虑到完全二叉树中,任意一个父亲节点左孩子存在,而右孩子不一定存在(细品!!!),所以我们需要判断,避免造成非法访问数组元素。如果右孩子存在,即:child + 1 < n ,右孩子大于左孩子,即:a[child + 1] > a[ child ](选出大孩子)。那么就child指向右孩子。

(即:该代码段是用child指向左右孩子中较大孩子)

如果说,a[child] > a[parent],那么就交换父子结点,然后继续向下调整。

最小堆删除堆顶数据,看是否此时还仍然符合最小堆的要求,若不符合,那么就需要向下调整,调成符合最小堆的形式。

考虑到完全二叉树中,任意一个父亲节点左孩子存在,而右孩子不一定存在(细品!!!),所以我们需要判断,避免造成非法访问数组元素。如果右孩子存在,即:child + 1 < n ,右孩子小于左孩子,即:a[child + 1] < a[ child ](选出小孩子)。那么就child指向右孩子。

(即:该代码段是用child指向左右孩子中较小孩子)

如果说,a[child] < a[parent],那么就交换父子结点,然后继续向下调整。

测试  【插入、删除大根堆】 的效果:

※向下、向上调整的时间复杂度log2_N

所以,向上向下调整都需要调整树的高度h次,即:时间复杂度为:O(log2_N)

堆的实现总代码:

  1. //Heap.h文件
  2. #pragma once
  3. #include<stdio.h>
  4. #include<stdlib.h>
  5. #include<assert.h>
  6. #include<stdbool.h>
  7. //实现大堆
  8. typedef int HPDataType;
  9. //定义堆
  10. typedef struct Heap
  11. {
  12. HPDataType* a;
  13. int size;
  14. int capacity;
  15. }HP;
  16. //初始化堆
  17. void HeapInit(HP* hp);
  18. //交换父子结点数据
  19. void Swap(HPDataType* px, HPDataType* py);
  20. //打印堆内数据
  21. void HeapPrint(HP* hp);
  22. //堆为空
  23. bool HeapEmpty(HP* hp);
  24. //销毁堆
  25. void HeapDestroy(HP* hp);
  26. //数据插入堆
  27. void HeapPush(HP* hp, HPDataType x);
  28. //向上调整
  29. void AdjustUp(int* a, int child);
  30. //删除堆顶数据
  31. void HeapPop(HP* hp);
  32. //向下调整
  33. void AdjustDown(int* a, int n, int parent);
  34. //获取堆顶数据
  35. HPDataType HeapTop(HP* hp);
  1. //Heap.c文件
  2. #define _CRT_SECURE_NO_WARNINGS
  3. #include"Heap.h"
  4. //初始化堆
  5. void HeapInit(HP* hp)
  6. {
  7. assert(hp);
  8. hp->a = NULL;
  9. hp->size = hp->capacity = 0;
  10. }
  11. //销毁堆
  12. void HeapDestroy(HP* hp)
  13. {
  14. assert(hp);
  15. free(hp->a);
  16. hp->capacity = hp->size = 0;
  17. }
  18. //交换父子结点数据
  19. void Swap(HPDataType* px, HPDataType* py)
  20. {
  21. HPDataType tmp = *px;
  22. *px = *py;
  23. *py = tmp;
  24. }
  25. //获取堆顶数据
  26. HPDataType HeapTop(HP* hp)
  27. {
  28. assert(hp);
  29. assert(!HeapEmpty(hp));
  30. return hp->a[0];
  31. }
  32. 向上调整(大堆)
  33. //void AdjustUp(int* a, int child)
  34. //{
  35. // assert(a);
  36. //
  37. // int parent = (child - 1) / 2;
  38. // //while (parent >= 0)
  39. // while (child > 0)
  40. // {
  41. // if (a[child] > a[parent])
  42. // {
  43. // /*HPDataType tmp = a[child];
  44. // a[child] = a[parent];
  45. // a[parent] = tmp;*/
  46. // Swap(&a[child], &a[parent]);
  47. //
  48. // child = parent;
  49. // parent = (child - 1) / 2;
  50. // }
  51. // else
  52. // {
  53. // break;
  54. // }
  55. // }
  56. //}
  57. //向上调整(小堆)
  58. void AdjustUp(int* a, int child)
  59. {
  60. assert(a);
  61. int parent = (child - 1) / 2;
  62. //while (parent >= 0)
  63. while (child > 0)
  64. {
  65. if (a[child] < a[parent])
  66. {
  67. /*HPDataType tmp = a[child];
  68. a[child] = a[parent];
  69. a[parent] = tmp;*/
  70. Swap(&a[child], &a[parent]);
  71. child = parent;
  72. parent = (child - 1) / 2;
  73. }
  74. else
  75. {
  76. break;
  77. }
  78. }
  79. }
  80. //往堆插入数据
  81. void HeapPush(HP* hp, HPDataType x)
  82. {
  83. assert(hp);
  84. //不够就扩容
  85. if (hp->size == hp->capacity)
  86. {
  87. int newcapacity = hp->capacity == 0 ? 4 : hp->capacity * 2;
  88. HPDataType* tmp = realloc(hp->a, sizeof(HPDataType) * newcapacity);
  89. if (tmp == NULL)
  90. {
  91. printf("realloc fail!\n");
  92. exit(-1);
  93. }
  94. hp->a = tmp;
  95. hp->capacity = newcapacity;
  96. }
  97. hp->a[hp->size] = x;
  98. hp->size++;
  99. AdjustUp(hp->a, hp->size - 1);
  100. }
  101. //判断堆是否为空
  102. bool HeapEmpty(HP* hp)
  103. {
  104. assert(hp);
  105. return hp->size == 0;
  106. }
  107. //堆的数据个数
  108. int HeapSize(HP* hp)
  109. {
  110. assert(hp);
  111. return hp->size;
  112. }
  113. //打印堆内数据
  114. void HeapPrint(HP* hp)
  115. {
  116. for (int i = 0; i < hp->size; ++i)
  117. {
  118. printf("%d ", hp->a[i]);
  119. }
  120. printf("\n");
  121. }
  122. 向下调整(大堆)
  123. //void AdjustDown(int* a, int n, int parent)
  124. //{
  125. // assert(a);
  126. //
  127. // int child = parent * 2 + 1;//左孩子
  128. // while (child < n)
  129. // {
  130. // //选出大孩子
  131. // if (child + 1 < n && a[child + 1] > a[child])
  132. // child++;
  133. //
  134. // //交换 并 迭代 parent 和 child
  135. // if (a[parent] < a[child])
  136. // {
  137. // Swap(&a[parent], &a[child]);
  138. // parent = child;
  139. // child = parent * 2 + 1;
  140. // }
  141. // else
  142. // {
  143. // break;
  144. // }
  145. // }
  146. //}
  147. //向下调整(小堆)
  148. void AdjustDown(int* a, int n, int parent)
  149. {
  150. assert(a);
  151. int child = parent * 2 + 1;//左孩子
  152. while (child < n)
  153. {
  154. //选出小孩子
  155. if (child + 1 < n && a[child + 1] < a[child])
  156. child++;
  157. //交换 并 迭代 parent 和 child
  158. if (a[parent] > a[child])
  159. {
  160. Swap(&a[parent], &a[child]);
  161. parent = child;
  162. child = parent * 2 + 1;
  163. }
  164. else
  165. {
  166. break;
  167. }
  168. }
  169. }
  170. //删除堆顶数据
  171. void HeapPop(HP* hp)
  172. {
  173. assert(hp);
  174. assert(!HeapEmpty(&hp));
  175. Swap(&hp->a[0], &hp->a[hp->size - 1]);//堆顶数据与末尾数据交换
  176. hp->size--;
  177. AdjustDown(hp->a, hp->size, 0);
  178. }
  1. //Test.c文件
  2. #define _CRT_SECURE_NO_WARNINGS
  3. #include"Heap.h"
  4. //在N个数中找出最大的前K个最大值 (或者最小的前K个最小值)
  5. int main()
  6. {
  7. int a[] = { 70,56,30,25,15,10,75 };
  8. HP hp;
  9. HeapInit(&hp);
  10. for (int i = 0; i < sizeof(a) / sizeof(a[0]); i++)
  11. {
  12. HeapPush(&hp, a[i]);
  13. }
  14. HeapPrint(&hp);
  15. HeapPop(&hp);
  16. HeapPrint(&hp);
  17. HeapPop(&hp);
  18. HeapPrint(&hp);
  19. //销毁堆
  20. HeapDestroy(&hp);
  21. return 0;
  22. }

2、建堆的应用

  1. topk问题
  2. 堆排序算法

(1)TopK问题

首先想想为什么要用堆实现Topk问题?在N非常大的时候,其他算法为什么不行?


以选最大的K个数为例,建立小堆。


建立K个数的小堆,最终达到选出N个数中最大的K个数,在这个过程中需要注意的是:

  1. 选最大的K个数,是建立小堆。因为小堆的堆顶数据最小的,而用剩下的N-K个数去和最小的比较...然后大于它...就取代,就会保证每次选出的K个数都是到目前为止最大的K个数。
  2. 因为K<<N,所以通过该方法实现了仅仅用K个空间就可以实现N个数据中的Topk问题。空间节约了,而且没有多余去管其他的N-K个数具体的大小,针对性的解决了要求的K个数。

实现TopK算法(最小的K个数)-- 建立小堆

TopK算法(Test.c文件)

  1. //Test.c文件
  2. #define _CRT_SECURE_NO_WARNINGS
  3. #include"Heap.h"
  4. //从N个数中,选出K个最大的数
  5. //(只有建立 【小堆】 一种办法,大堆不行)
  6. void PrintTopK(int* a, int n, int k)
  7. {
  8. HP hp;
  9. HeapInit(&hp);
  10. // 1. 建堆--用a中的K个数创建一个小堆
  11. for (int i = 0; i < k; ++i)
  12. {
  13. HeapPush(&hp, a[i]);//小堆的HeapPush
  14. }
  15. // 2. 将剩余N-K个元素依次与堆顶数据比较,比他大,就替换他,进堆
  16. for (int i = k; i < n; ++i)
  17. {
  18. if (a[i] > HeapTop(&hp))
  19. {
  20. //方法1
  21. //HeapPop(&hp);//小堆的HeapPop
  22. //HeapPush(&hp, a[i]);
  23. //方法2
  24. hp.a[0] = a[i];
  25. AdjustDown(hp.a, hp.size, 0);//==>为了保持是小堆
  26. }
  27. }
  28. HeapPrint(&hp);
  29. //HeapDestroy(&hp);
  30. }
  31. void TestTopk()
  32. {
  33. int n = 10000;
  34. int* a = (int*)malloc(sizeof(int) * n);
  35. srand(time(0));
  36. for (int i = 0; i < n; ++i)
  37. {
  38. //随机生成数字,%100w就保证了数字小于100w
  39. a[i] = rand() % 1000000;
  40. }
  41. //手动设置10个大于100w的数字( 即:要选出的K个最大数 )
  42. a[5] = 1000000 + 1;
  43. a[1231] = 1000000 + 2;
  44. a[531] = 1000000 + 3;
  45. a[5121] = 1000000 + 4;
  46. a[115] = 1000000 + 5;
  47. a[2335] = 1000000 + 6;
  48. a[9999] = 1000000 + 7;
  49. a[76] = 1000000 + 8;
  50. a[423] = 1000000 + 9;
  51. a[3144] = 1000000 + 10;
  52. PrintTopK(a, n, 10);
  53. }
  54. int main()
  55. {
  56. TestTopk();
  57. return 0;
  58. }
  1. //Heap.h文件
  2. #pragma once
  3. #include<stdio.h>
  4. #include<stdlib.h>
  5. #include<assert.h>
  6. #include<stdbool.h>
  7. //实现大堆
  8. typedef int HPDataType;
  9. //定义堆
  10. typedef struct Heap
  11. {
  12. HPDataType* a;
  13. int size;
  14. int capacity;
  15. }HP;
  16. //初始化堆
  17. void HeapInit(HP* hp);
  18. //交换父子结点数据
  19. void Swap(HPDataType* px, HPDataType* py);
  20. //打印堆内数据
  21. void HeapPrint(HP* hp);
  22. //堆为空
  23. bool HeapEmpty(HP* hp);
  24. //销毁堆
  25. void HeapDestroy(HP* hp);
  26. //数据插入堆
  27. void HeapPush(HP* hp, HPDataType x);
  28. //向上调整
  29. void AdjustUp(int* a, int child);
  30. //删除堆顶数据
  31. void HeapPop(HP* hp);
  32. //向下调整
  33. void AdjustDown(int* a, int n, int parent);
  34. //获取堆顶数据
  35. HPDataType HeapTop(HP* hp);
  1. //Heap.c文件
  2. #define _CRT_SECURE_NO_WARNINGS
  3. #include"Heap.h"
  4. //初始化堆
  5. void HeapInit(HP* hp)
  6. {
  7. assert(hp);
  8. hp->a = NULL;
  9. hp->size = hp->capacity = 0;
  10. }
  11. //销毁堆
  12. void HeapDestroy(HP* hp)
  13. {
  14. assert(hp);
  15. free(hp->a);
  16. hp->capacity = hp->size = 0;
  17. }
  18. //交换父子结点数据
  19. void Swap(HPDataType* px, HPDataType* py)
  20. {
  21. HPDataType tmp = *px;
  22. *px = *py;
  23. *py = tmp;
  24. }
  25. //获取堆顶数据
  26. HPDataType HeapTop(HP* hp)
  27. {
  28. assert(hp);
  29. assert(!HeapEmpty(hp));
  30. return hp->a[0];
  31. }
  32. //向上调整(小堆)
  33. void AdjustUp(int* a, int child)
  34. {
  35. assert(a);
  36. int parent = (child - 1) / 2;
  37. //while (parent >= 0)
  38. while (child > 0)
  39. {
  40. if (a[child] < a[parent])
  41. {
  42. /*HPDataType tmp = a[child];
  43. a[child] = a[parent];
  44. a[parent] = tmp;*/
  45. Swap(&a[child], &a[parent]);
  46. child = parent;
  47. parent = (child - 1) / 2;
  48. }
  49. else
  50. {
  51. break;
  52. }
  53. }
  54. }
  55. //往堆插入数据
  56. void HeapPush(HP* hp, HPDataType x)
  57. {
  58. assert(hp);
  59. //不够就扩容
  60. if (hp->size == hp->capacity)
  61. {
  62. int newcapacity = hp->capacity == 0 ? 4 : hp->capacity * 2;
  63. HPDataType* tmp = realloc(hp->a, sizeof(HPDataType) * newcapacity);
  64. if (tmp == NULL)
  65. {
  66. printf("realloc fail!\n");
  67. exit(-1);
  68. }
  69. hp->a = tmp;
  70. hp->capacity = newcapacity;
  71. }
  72. hp->a[hp->size] = x;
  73. hp->size++;
  74. AdjustUp(hp->a, hp->size - 1);
  75. }
  76. //判断堆是否为空
  77. bool HeapEmpty(HP* hp)
  78. {
  79. assert(hp);
  80. return hp->size == 0;
  81. }
  82. //堆的数据个数
  83. int HeapSize(HP* hp)
  84. {
  85. assert(hp);
  86. return hp->size;
  87. }
  88. //打印堆内数据
  89. void HeapPrint(HP* hp)
  90. {
  91. for (int i = 0; i < hp->size; ++i)
  92. {
  93. printf("%d ", hp->a[i]);
  94. }
  95. printf("\n");
  96. }
  97. //向下调整(小堆)
  98. void AdjustDown(int* a, int n, int parent)
  99. {
  100. assert(a);
  101. int child = parent * 2 + 1;//左孩子
  102. while (child < n)
  103. {
  104. //选出小孩子
  105. if (child + 1 < n && a[child + 1] < a[child])
  106. child++;
  107. //交换 并 迭代 parent 和 child
  108. if (a[parent] > a[child])
  109. {
  110. Swap(&a[parent], &a[child]);
  111. parent = child;
  112. child = parent * 2 + 1;
  113. }
  114. else
  115. {
  116. break;
  117. }
  118. }
  119. }
  120. //删除堆顶数据
  121. void HeapPop(HP* hp)
  122. {
  123. assert(hp);
  124. assert(!HeapEmpty(&hp));
  125. Swap(&hp->a[0], &hp->a[hp->size - 1]);//堆顶数据与末尾数据交换
  126. hp->size--;
  127. AdjustDown(hp->a, hp->size, 0);
  128. }

测试效果 

(2)堆排序

堆排序 - 升序  -  空间复杂度为O(N)的写法    (不推荐)

堆排序算法(Test.c文件)

该算法是建立小堆,并且用了HeapPush等建堆操作;所以是额外开辟使用了空间,并且还得事先实现堆以及堆的操作才能做。

而堆本身就是数组,那么能否直接对数组a进行构建堆?(不用堆的任何相关操作~)

  1. //Test.c文件
  2. #define _CRT_SECURE_NO_WARNINGS
  3. #include"Heap.h"
  4. //堆排序 - 升序 - 空间复杂度( O(N) )
  5. //降序同理,Push、Pop小堆变大堆即可
  6. void HeapSort(int* a, int n)
  7. {
  8. HP hp;
  9. HeapInit(&hp);
  10. //建立一个小堆
  11. for (int i = 0; i < n; ++i)
  12. {
  13. HeapPush(&hp, a[i]);//小堆的HeapPush
  14. }
  15. //Pop N 次
  16. for (int i = 0; i < n; ++i)
  17. {
  18. a[i] = HeapTop(&hp);
  19. HeapPop(&hp);//小堆的HeapPop
  20. }
  21. //HeapDestroy(&hp);
  22. }
  23. int main()
  24. {
  25. int a[] = { 70,56,30,25,15,10,75 };
  26. for (int i = 0; i < sizeof(a) / sizeof(a[0]); ++i)
  27. {
  28. printf("%d ", a[i]);
  29. }
  30. printf("\n");
  31. HeapSort(a, sizeof(a) / sizeof(a[0]));
  32. for (int i = 0; i < sizeof(a) / sizeof(a[0]); ++i)
  33. {
  34. printf("%d ", a[i]);
  35. }
  36. printf("\n");
  37. return 0;
  38. }

该程序空间复杂度是O(N),那么能否优化到O(1)呢?

(即:不能用Heap避免了HeapPush开辟新空间,直接对数组a进行操作


堆排序 - 升序  -  空间复杂度为 O(1) 的写法    (推荐!) 

考虑到原来使用了HeapPush建堆,新开辟了N个空间。所以空间复杂度是O(N),而优化到O(1)即优化建堆的操作。

所以,优化建堆的操作就是对数组a直接操作,不开辟新空间。其有两种方式:

  1. 向上调整  (依次加入)
  2. 向下调整  (倒着走)

那么建堆完成后,如何实现升序呢?

 所以,排升序,用大堆+向下调整建堆方式

堆排序  -  (向下调整建x堆+x堆)

堆排序:

  1. 排升序,建大堆
  2. 排降序,建小堆

*注释:大小堆,在堆排序中的区别就体现在AdjustDown函数实现的是大堆还是小堆

  1. void HeapSort(int* a, int n)
  2. {
  3. //把a构建成堆(向下调整)
  4. //注意:需要找到 [最后一个节点] 的 [父亲节点] ,从那开始,向下调整
  5. int end = n - 1;//最后一个节点
  6. for (int i = (end - 1) / 2; i >= 0; i--)
  7. {
  8. AdjustDown(a, n, i);
  9. }
  10. //实现升序
  11. end = n - 1;//最后一个数
  12. for (int i = end; i > 0; i--)
  13. {
  14. Swap(&a[i], &a[0]);
  15. AdjustDown(a, i, 0);
  16. }
  17. }

排升序  -  (向下调整建大堆+大堆)

  1. //向下调整(大堆)
  2. void AdjustDown(int* a, int n, int parent);
  3. //堆排序 - 升序 -(向下调整建大堆+大堆)
  4. void HeapSort(int* a, int n)
  5. {
  6. //把a构建成堆(向下调整)
  7. //注意:需要找到 [最后一个节点] 的 [父亲节点] ,从那开始,向下调整
  8. int end = n - 1;//最后一个节点
  9. for (int i = (end - 1) / 2; i >= 0; i--)
  10. {
  11. AdjustDown(a, n, i);
  12. }
  13. //实现升序
  14. end = n - 1;//最后一个数
  15. for (int i = end; i > 0; i--)
  16. {
  17. Swap(&a[i], &a[0]);
  18. AdjustDown(a, i, 0);
  19. }
  20. }
  21. //向下调整(大堆)
  22. void AdjustDown(int* a, int n, int parent)
  23. {
  24. assert(a);
  25. int child = parent * 2 + 1;//左孩子
  26. while (child < n)
  27. {
  28. //选出大孩子
  29. if (child + 1 < n && a[child + 1] > a[child])
  30. child++;
  31. //交换 并 迭代 parent 和 child
  32. if (a[parent] < a[child])
  33. {
  34. Swap(&a[parent], &a[child]);
  35. parent = child;
  36. child = parent * 2 + 1;
  37. }
  38. else
  39. {
  40. break;
  41. }
  42. }
  43. }

整体代码

  1. //堆排序 - 升序 -(向下调整建大堆+大堆)
  2. void HeapSort(int* a, int n)
  3. {
  4. //建堆操作:
  5. //方法1:
  6. //把a构建成堆(向上调整)
  7. /*for (int i = 0; i < n; i++)
  8. {
  9. AdjustUp(a, i);
  10. }
  11. */
  12. //方法2:
  13. //把a构建成堆(向下调整)
  14. //注意:需要找到 [最后一个节点] 的 [父亲节点] ,从那开始,向下调整
  15. int end = n - 1;//最后一个节点
  16. for (int i = (end - 1) / 2; i >= 0; i--)
  17. {
  18. AdjustDown(a, n, i);
  19. }
  20. //实现升序
  21. end = n - 1;//最后一个数
  22. for (int i = end; i > 0; i--)
  23. {
  24. Swap(&a[i], &a[0]);
  25. AdjustDown(a, i, 0);
  26. }
  27. }
  28. int main()
  29. {
  30. int a[] = { 70,56,30,25,15,10,75,33,50,69 };
  31. for (int i = 0; i < sizeof(a) / sizeof(a[0]); ++i)
  32. {
  33. printf("%d ", a[i]);
  34. }
  35. printf("\n");
  36. HeapSort(a, sizeof(a) / sizeof(a[0]));
  37. for (int i = 0; i < sizeof(a) / sizeof(a[0]); ++i)
  38. {
  39. printf("%d ", a[i]);
  40. }
  41. printf("\n");
  42. return 0;
  43. }

 同理,堆排序 - 排降序是建立小堆

排降序  -  (向下调整建小堆+小堆)

  1. //向下调整(小堆)
  2. void AdjustDown(int* a, int n, int parent);
  3. //堆排序 - 降序
  4. void HeapSort(int* a, int n)
  5. {
  6. //把a构建成堆(向下调整)
  7. //注意:需要找到 [最后一个节点] 的 [父亲节点] ,从那开始,向下调整
  8. int end = n - 1;//最后一个节点
  9. for (int i = (end - 1) / 2; i >= 0; i--)
  10. {
  11. AdjustDown(a, n, i);
  12. }
  13. //实现降序
  14. end = n - 1;//最后一个数
  15. for (int i = end; i > 0; i--)
  16. {
  17. Swap(&a[i], &a[0]);
  18. AdjustDown(a, i, 0);
  19. }
  20. }
  21. //向下调整(小堆)
  22. void AdjustDown(int* a, int n, int parent)
  23. {
  24. assert(a);
  25. int child = parent * 2 + 1;//左孩子
  26. while (child < n)
  27. {
  28. //选出小孩子
  29. if (child + 1 < n && a[child + 1] < a[child])
  30. child++;
  31. //交换 并 迭代 parent 和 child
  32. if (a[parent] > a[child])
  33. {
  34. Swap(&a[parent], &a[child]);
  35. parent = child;
  36. child = parent * 2 + 1;
  37. }
  38. else
  39. {
  40. break;
  41. }
  42. }
  43. }

整体代码:

  1. //堆排序 - 降序
  2. void HeapSort(int* a, int n)
  3. {
  4. //建堆操作:
  5. //方法1:
  6. //把a构建成堆(向上调整)
  7. /*for (int i = 0; i < n; i++)
  8. {
  9. AdjustUp(a, i);
  10. }
  11. */
  12. //方法2:
  13. //把a构建成堆(向下调整)
  14. //注意:需要找到 [最后一个节点] 的 [父亲节点] ,从那开始,向下调整
  15. int end = n - 1;//最后一个节点
  16. for (int i = (end - 1) / 2; i >= 0; i--)
  17. {
  18. AdjustDown(a, n, i);
  19. }
  20. //实现降序
  21. end = n - 1;//最后一个数
  22. for (int i = end; i > 0; i--)
  23. {
  24. Swap(&a[i], &a[0]);
  25. AdjustDown(a, i, 0);
  26. }
  27. }
  28. int main()
  29. {
  30. int a[] = { 70,56,30,25,15,10,75,33,50,69 };
  31. for (int i = 0; i < sizeof(a) / sizeof(a[0]); ++i)
  32. {
  33. printf("%d ", a[i]);
  34. }
  35. printf("\n");
  36. HeapSort(a, sizeof(a) / sizeof(a[0]));
  37. for (int i = 0; i < sizeof(a) / sizeof(a[0]); ++i)
  38. {
  39. printf("%d ", a[i]);
  40. }
  41. printf("\n");
  42. return 0;
  43. }

建堆的时间复杂度O(N)证明

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