当前位置:   article > 正文

C++数据结构详解——堆_c++堆

c++堆

目录

1.堆的原理精讲

2.在数组中快速创建堆

3.堆的算法实现

4.插入元素 与弹出最大元素

4.1插入元素  

 4.2弹出最大元素  

5.堆的实际开发应用案例

5.1优先队列

5.2堆排序

6.堆实现及其功能完整代码


1.堆的原理精讲

最大堆特点:

当然,也有最小堆,最小堆就是将最大堆反过来,根结点为最小值~

堆是树中最有个性的树,他是用数组表示的树 。

2.在数组中快速创建堆

 1.首先我们需要找到最后一个结点的父结点如图(a),我们找到的结点是 87,然后找出该结点的最大子节点与自己比较,若该子节点比自身大,则将两个结点交换。
图(a)中,87 比左子节点 95 小,则交换之。如图(b)所示

2.我们移动到第一步前一个父节点 93,如图(c)所示.同理做第一步的比较操作,结果不需要交换。

 3.继续移动结点到前一个父结点 82,如图(d)所示,82 小于右子节点 95,则 82 与 95 交换,如图(e)所示,82 交换后,其值小于左子节点,不符合最大堆的特点,故需要继续向下调整,如图(f)所示。

 4.所有节点交换完毕,最大堆构建完成~

3.堆的算法实现

堆数据结构的定义:

  1. #define DEFAULT_CAPACITY 128
  2. typedef struct _Heap {
  3. int* arr; //存储堆元素的数组
  4. int size; //当前已存储的元素个数
  5. int capacity; //当前的存储容量
  6. }Heap;

建立最大堆算法: 

  1. bool initHeap(Heap& heap, int* original, int size);
  2. static void buildHeap(Heap& heap);
  3. static void adjustDown(Heap& heap, int index);
  4. //初始化堆
  5. bool initHeap(Heap& heap, int* original, int size) {
  6. //如果默认大小比size小,则申请默认大小的空间,否则申请size大小的空间
  7. int capacity = DEFAULT_CAPACITY > size ? DEFAULT_CAPACITY : size;
  8. heap.arr = new int[capacity];
  9. if (!heap.arr)return false;
  10. heap.capacity = capacity;
  11. heap.size = 0;
  12. //如果存在原始数据,则拷贝过来
  13. if (size > 0) {
  14. memcpy(heap.arr, original, size * sizeof(int));
  15. heap.size = size;
  16. buildHeap(heap);
  17. }
  18. return true;
  19. }
  20. /*从最后一个父节点开始(heap.size) - 1 / 2)(因为size是从1开始,所以要先减去1)
  21. 逐个调整所有的父节点(直到根节点),确保每一个父节点都是最大堆,最后
  22. 整体上形成一个最大堆*/
  23. void buildHeap(Heap& heap) {
  24. for (int i = (heap.size - 1) / 2; i >= 0; i--) {
  25. adjustDown(heap, i);
  26. }
  27. }
  28. void adjustDown(Heap& heap, int index) {
  29. int cur = heap.arr[index]; //记录父节点值
  30. int parent, child;
  31. /*怕段是否存在大于当前结点的子节点,如果不存在,则堆本身平衡,不需要
  32. 调整,如果存在,则将最大子节点与之交换,交换后,如果这个子节点还有
  33. 子节点(即parent*2+1<heap.size 成立)则要按照相同步骤对这个子节点进行
  34. 调整*/
  35. for (parent = index; (parent * 2 + 1) < heap.size; parent = child) {
  36. child = parent * 2 + 1; //左子节点
  37. //取两个子节点最大结点
  38. if (((child + 1) < heap.size) && (heap.arr[child + 1] > heap.arr[child])) {
  39. child++;
  40. }
  41. if (cur >= heap.arr[child])break;//不大于,跳出循环
  42. else {
  43. /*大于当前父节点,进行交换,然后从子节点位置继续向下调整,
  44. 即for从第二次循环开始,初始值都为上一次的子节点位置*/
  45. heap.arr[parent] = heap.arr[child];
  46. heap.arr[child] = cur;
  47. }
  48. }
  49. }

完成测试代码 :

  1. int main() {
  2. Heap hp;
  3. int orignArry[] = { 1,2,3,87,93,82,92,86,95 };
  4. if (!initHeap(hp, orignArry, sizeof(orignArry) / sizeof(orignArry[0]))) {
  5. fprintf(stderr, "初始化堆失败!\n"); //输出到控制台
  6. exit(-1);
  7. }
  8. for (int i = 0; i < hp.size; i++) {
  9. cout << hp.arr[i] << endl;
  10. }
  11. system("pause");
  12. return 0;
  13. }

运行结果:

知识点补充:

1.memcpy(内存拷贝函数)

memcpy函数的功能是从源src所指的内存地址的起始位置开始拷贝n个字节目标dest所指的内存地址的起始位置中。用于double、int、结构体等

  1. double arryDouble_Src[16];
  2. double arryDouble_Det[16];
  3. memcpy( arryDouble_Det, arryDouble_Src, sizeof(double)*16);
  1. int arryInt_Src[16];
  2. int arryInt_Dst[16];
  3. memcpy( arryInt_Dst, arryInt_Src, sizeof(int)*16);
  1. struct DataNum
  2. {
  3. int Data0;
  4. int Data1;
  5. }
  6. DataNum arryStruct_Src[16];
  7. DataNum arryStruct_Dst[16];
  8. memcpy( arryStruct_Dst, arryStruct_Src, sizeof(DataNum )*16);

2. 在一般的函数前面加上static

加了static后表示该函数失去了全局可见性,只在该函数所在作用域内可见。

当函数声明为static以后,编译器在该目标编译单元内只含有该函数入口地址,没有函数名,其它编译器便不能通过该函数名调用该函数。

4.插入元素 与弹出最大元素

4.1插入元素  

将数字99插入到下面最大堆中

 对应的数组:{95, 93, 87, 92, 86, 82}

将新进的元素插入到大顶堆的尾部,如下图 b 所示: 

对应的数组:{95, 93, 87, 92, 86, 82, 99} 

此时最大堆已经被破坏,需要重新调整, 因加入的节点比父节点大,则新节点跟父节点调换即可,如图 c所示;调整后,新节点如果比新的父节点小,则已经调整到位,如果比新的父节点大,则需要和父节点重新进行交换,如图 d, 至此,最大堆调整完成。 

 代码实现:

  1. static bool insertHeap(Heap& heap, int value);
  2. static void adjustUp(Heap& heap, int index);
  3. bool insertHeap(Heap& heap, int value) {
  4. if (heap.size == heap.capacity) {
  5. fprintf(stderr, "栈空间耗尽!\n");
  6. return false;
  7. }
  8. int index = heap.size;
  9. heap.arr[heap.size++] = value;//先赋值value,再size++
  10. adjustUp(heap, index);
  11. }
  12. void adjustUp(Heap& heap, int index) {
  13. if (index < 0 || index >= heap.size) {
  14. //如果只有一个结点(插入的结点)inedx<0,或者大于堆的最大值,return掉
  15. return;
  16. }
  17. int temp = heap.arr[index];//temp为插入的值
  18. while (index > 0) {
  19. int parent = (index - 1) / 2;
  20. if (parent >= 0) { //如果索引没有出界,就执行想要操作
  21. if (heap.arr[parent] < temp) {
  22. heap.arr[index] = heap.arr[parent];
  23. heap.arr[parent] = temp;
  24. index = parent;
  25. }
  26. else break; //如果没有比父结点大,则跳出
  27. }
  28. else break; //越界,结束循环
  29. }
  30. }

完成测试代码 : 

  1. int main() {
  2. Heap hp;
  3. int orignArry[] = { 1,2,3,87,93,82,92,86,95 };
  4. if (!initHeap(hp, orignArry, sizeof(orignArry) / sizeof(orignArry[0]))) {
  5. fprintf(stderr, "初始化堆失败!\n"); //输出到控制台
  6. exit(-1);
  7. }
  8. for (int i = 0; i < hp.size; i++) {
  9. cout << "第"<<i<<"个数为:"<<hp.arr[i] << endl;
  10. }
  11. insertHeap(hp, 99);
  12. printf("在堆中插入新的元素99,插入结果:\n");
  13. for (int i = 0; i < hp.size; i++) {
  14. cout << "第" << i << "个数为:" << hp.arr[i] << endl;
  15. }
  16. system("pause");
  17. return 0;
  18. }

运行结果:

当实现了插入功能后,对于堆的初始建立就有了两种方法。第一种就是直接将数组传入,第二种则是将元素一个个插入。相比较而言,对于一个个插入的方法比较次数更少,效率更高~ 

 4.2弹出最大元素  

 如果我们将堆顶的元素删除,那么顶部有一个空的节点,怎么处理?

当插入节点的时候,我们将新的值插入数组的尾部。现在我们来做相反的事情:我们取出数组中的最后一个元素,将它放到堆的顶部,然后再修复堆属性。 

  1. static bool popMax(Heap& heap, int& value);
  2. bool popMax(Heap& heap, int& value) {
  3. if (heap.size < 1)return false;
  4. value = heap.arr[0];
  5. //将size-1的位置值赋给根节点,size本身又--
  6. /*相当于
  7. heap.arr[0] = heap.arr[heap.size-1];
  8. heap.size--;
  9. */
  10. heap.arr[0] = heap.arr[--heap.size];
  11. adjustDown(heap, 0); //向下执行堆调整
  12. return true;
  13. }

完成测试代码 : 

  1. int main() {
  2. Heap hp;
  3. int orignArry[] = { 1,2,3,87,93,82,92,86,95 };
  4. if (!initHeap(hp, orignArry, sizeof(orignArry) / sizeof(orignArry[0]))) {
  5. fprintf(stderr, "初始化堆失败!\n"); //输出到控制台
  6. exit(-1);
  7. }
  8. for (int i = 0; i < hp.size; i++) {
  9. cout << "第"<<i<<"个数为:"<<hp.arr[i] << endl;
  10. }
  11. /*向堆中插入元素
  12. insertHeap(hp, 99);
  13. printf("在堆中插入新的元素99,插入结果:\n");
  14. for (int i = 0; i < hp.size; i++) {
  15. cout << "第" << i << "个数为:" << hp.arr[i] << endl;
  16. }*/
  17. int value;
  18. while (popMax(hp, value)) {
  19. cout << "依次出列最大元素:" << value << endl;
  20. }
  21. system("pause");
  22. return 0;
  23. }

运行结果:   

 


使用堆方法依次弹出最大值,执行效率高于冒泡排序~

5.堆的实际开发应用案例

5.1优先队列

 操作系统内核作业调度是优先队列的一个应用实例,它根据优先级的高低而不是先到先服务的方式来进行调度;

 

如果最小键值元素拥有最高的优先级,那么这种优先队列叫作 升序优先队列(即总是先删除最小的元素),类似的,如果最大键值元素拥有最高的优先级,那么这种优先队列叫作 降序优先队列(即总是先删除最大的元素);由于这两种类型是完全对称的,所以只需要关注其中一种,如升序优先队列。

5.2堆排序

堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法,它是选择排序的一种。可以利用数组的特点快速定位指定索引的元素。
(选择排序工作原理 -——第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后再从剩余的未排序元素中寻找到最小(大)元素,然后放到已排序的序列的末尾。以此类推,直到全部待排序的数据元素的个数为零) 

核心排序如下:

 

6.堆实现及其功能完整代码

  1. #include<iostream>
  2. using namespace std;
  3. #define DEFAULT_CAPACITY 128
  4. typedef struct _Heap {
  5. int* arr; //存储堆元素的数组
  6. int size; //当前已存储的元素个数
  7. int capacity; //当前的存储容量
  8. }Heap;
  9. bool initHeap(Heap& heap, int* original, int size);
  10. static void buildHeap(Heap& heap);
  11. static void adjustDown(Heap& heap, int index);
  12. static bool insertHeap(Heap& heap, int value);
  13. static void adjustUp(Heap& heap, int index);
  14. static bool popMax(Heap& heap, int& value);
  15. //初始化堆
  16. bool initHeap(Heap& heap, int* original, int size) {
  17. //如果默认大小比size小,则申请默认大小的空间,否则申请size大小的空间
  18. int capacity = DEFAULT_CAPACITY > size ? DEFAULT_CAPACITY : size;
  19. heap.arr = new int[capacity];
  20. if (!heap.arr)return false;
  21. heap.capacity = capacity;
  22. heap.size = 0;
  23. //如果存在原始数据,则拷贝过来
  24. if (size > 0) {
  25. memcpy(heap.arr, original, size * sizeof(int));
  26. heap.size = size;
  27. buildHeap(heap);
  28. }
  29. return true;
  30. }
  31. /*从最后一个父节点开始(heap.size) - 1 / 2)(因为size是从1开始,所以要先减去1)
  32. 逐个调整所有的父节点(直到根节点),确保每一个父节点都是最大堆,最后
  33. 整体上形成一个最大堆*/
  34. void buildHeap(Heap& heap) {
  35. for (int i = (heap.size - 1) / 2; i >= 0; i--) {
  36. adjustDown(heap, i);
  37. }
  38. }
  39. void adjustDown(Heap& heap, int index) {
  40. int cur = heap.arr[index]; //记录父节点值
  41. int parent, child;
  42. /*怕段是否存在大于当前结点的子节点,如果不存在,则堆本身平衡,不需要
  43. 调整,如果存在,则将最大子节点与之交换,交换后,如果这个子节点还有
  44. 子节点(即parent*2+1<heap.size 成立)则要按照相同步骤对这个子节点进行
  45. 调整*/
  46. for (parent = index; (parent * 2 + 1) < heap.size; parent = child) {
  47. child = parent * 2 + 1; //左子节点
  48. //取两个子节点最大结点
  49. if (((child + 1) < heap.size) && (heap.arr[child + 1] > heap.arr[child])) {
  50. child++;
  51. }
  52. if (cur >= heap.arr[child])break;//不大于,跳出循环
  53. else {
  54. /*大于当前父节点,进行交换,然后从子节点位置继续向下调整,
  55. 即for从第二次循环开始,初始值都为上一次的子节点位置*/
  56. heap.arr[parent] = heap.arr[child];
  57. heap.arr[child] = cur;
  58. }
  59. }
  60. }
  61. static bool popMax(Heap& heap, int& value);
  62. bool popMax(Heap& heap, int& value) {
  63. if (heap.size < 1)return false;
  64. value = heap.arr[0];
  65. //将size-1的位置值赋给根节点,size本身又--
  66. /*相当于
  67. heap.arr[0] = heap.arr[heap.size-1];
  68. heap.size--;
  69. */
  70. heap.arr[0] = heap.arr[--heap.size];
  71. adjustDown(heap, 0); //向下执行堆调整
  72. return true;
  73. }
  74. bool insertHeap(Heap& heap, int value) {
  75. if (heap.size == heap.capacity) {
  76. fprintf(stderr, "栈空间耗尽!\n");
  77. return false;
  78. }
  79. int index = heap.size;
  80. heap.arr[heap.size++] = value;//先赋值value,再size++
  81. adjustUp(heap, index);
  82. }
  83. void adjustUp(Heap& heap, int index) {
  84. if (index < 0 || index >= heap.size) {
  85. //如果只有一个结点(插入的结点)inedx<0,或者大于堆的最大值,return掉
  86. return;
  87. }
  88. int temp = heap.arr[index];//temp为插入的值
  89. while (index > 0) {
  90. int parent = (index - 1) / 2;
  91. if (parent >= 0) { //如果索引没有出界,就执行想要操作
  92. if (heap.arr[parent] < temp) {
  93. heap.arr[index] = heap.arr[parent];
  94. heap.arr[parent] = temp;
  95. index = parent;
  96. }
  97. else break; //如果没有比父结点大,则跳出
  98. }
  99. else break; //越界,结束循环
  100. }
  101. }
  102. int main() {
  103. Heap hp;
  104. int orignArry[] = { 1,2,3,87,93,82,92,86,95 };
  105. if (!initHeap(hp, orignArry, sizeof(orignArry) / sizeof(orignArry[0]))) {
  106. fprintf(stderr, "初始化堆失败!\n"); //输出到控制台
  107. exit(-1);
  108. }
  109. for (int i = 0; i < hp.size; i++) {
  110. cout << "第"<<i<<"个数为:"<<hp.arr[i] << endl;
  111. }
  112. //向堆中插入元素
  113. insertHeap(hp, 99);
  114. printf("在堆中插入新的元素99,插入结果:\n");
  115. for (int i = 0; i < hp.size; i++) {
  116. cout << "第" << i << "个数为:" << hp.arr[i] << endl;
  117. }
  118. int value;
  119. while (popMax(hp, value)) {
  120. cout << "依次出列最大元素:" << value << endl;
  121. }
  122. system("pause");
  123. return 0;
  124. }

运行截图:

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

闽ICP备14008679号