当前位置:   article > 正文

『初阶数据结构 • C语言』⑫ - 堆的概念&&实现(图文详解+完整源码)_c语言中满足堆图解

c语言中满足堆图解


目录

0.写在前面

1.什么是堆?

2.堆的实现

2.1 堆的结构定义

2.2 函数声明

2.3 函数实现

2.3.1 AdjustUp(向上调整算法)

2.3.2 AdjustDown(向下调整算法)

2.3.3 HeapCreate(如何建堆)

2.3.4 建堆的时间复杂度

3. 完整源码

Heap.h文件

Heap.c文件 

Test.c文件 


0.写在前面

上一章中介绍了树和二叉树的概念及结构,本章我们将学习堆的实现。其中涉及若干树和二叉树的概念,如需查看,请点击链接跳转

想要学会二叉树?树的概念与结构是必须要掌握的!快进来看看吧http://t.csdn.cn/GWQJy

1.什么是堆?

堆是一种完全二叉树。只不过堆是二叉树顺序结构的实现,说白了就是将一个数组看作二叉树。也就是说,堆的逻辑结构是一棵二叉树,存储结构是数组。

堆又分为大堆和小堆:

大堆:树中所有父亲都大于等于孩子;

小堆:树中所有父亲都小于等于孩子。

注意,不满足这两点的二叉树不能称为堆(这点很重要)。

2.堆的实现

2.1 堆的结构定义

  1. typedef int HPDataType;
  2. typedef struct Heap
  3. {
  4. HPDataType* a; //存储数据
  5. int size; //堆有效数据的大小
  6. int capacity; //堆的容量
  7. }Heap;

上文讲到,堆其实就是二叉树的顺序结构实现,所以用一个数组来存储数据。

2.2 函数声明

  1. //给出一个数组,对它进行建堆
  2. void HeapCreate(Heap* php, HPDataType* a, int n);
  3. //堆的初始化
  4. void HeapInit(Heap* php);
  5. //对申请的内存释放
  6. void HeapDestroy(Heap* php);
  7. //添加数据
  8. void HeapPush(Heap* php, HPDataType data);
  9. //删除数据
  10. void HeapPop(Heap* php);
  11. //向上调整算法
  12. void AdjustUp(HPDataType* a, int child);
  13. //向下调整算法
  14. void AdjustDown(HPDataType* a, int n, int parent);
  15. //打印堆的数据
  16. void HeapPrint(Heap* php);
  17. //判断堆是否为空
  18. bool HeapEmpty(Heap* php);
  19. //返回堆的大小
  20. int HeapSize(Heap* php);
  21. //返回堆顶的数据
  22. HPDataType HeapTop(Heap* php);
  23. //交换函数
  24. void Swap(HPDataType* p1, HPDataType* p2);

2.3 函数实现

由于堆的实现所用函数较多,这里就挑其中最难也是最重要的进行说明。

2.3.1 AdjustUp(向上调整算法)

当我们要实现在HeapPush(堆中添加数据data时),我们的做法是先将data插入到堆的尾部,再将data进行向上调整,直到它到达合适的位置。

如图,假设现在要将data=60添加到下面这个大堆中。

第一步,将60插入到堆的末尾,即数组的末尾。

第二步,比较60与它父亲节点的大小。因为要保证插入数据之后堆仍然是大堆,所以如果60大于父亲,则交换位置。

第三步,继续比较60与父亲的值,若大于父亲则交换位置。

至此,60已经到它正确的位置上了。

以上就是向上调整的过程,来看看代码实现。

  1. void AdjustUp(HPDataType* a, int child)
  2. {
  3. int parent = (child - 1) / 2;
  4. while (child > 0)
  5. {
  6. //建大堆用'>',小堆用'<'
  7. if (a[child] > a[parent])
  8. {
  9. Swap(&a[child], &a[parent]);
  10. child = parent;
  11. parent = (child - 1) / 2;
  12. }
  13. else
  14. {
  15. break;
  16. }
  17. }
  18. }
  19. void HeapPush(Heap* php, HPDataType data)
  20. {
  21. assert(php);
  22. //如果容量不足就扩容
  23. if (php->size == php->capacity)
  24. {
  25. int newCapacity = php->capacity == 0 ? 4 : php->capacity * 2;
  26. HPDataType* tmp = (HPDataType*)realloc(php->a, sizeof(HPDataType)*newCapacity);
  27. if (tmp == NULL)
  28. {
  29. perror("realloc fail");
  30. exit(-1);
  31. }
  32. php->a = tmp;
  33. php->capacity = newCapacity;
  34. }
  35. //添加数据
  36. php->a[php->size] = data;
  37. php->size++;
  38. //将新入堆的data进行向上调整
  39. AdjustUp(php->a, php->size-1);
  40. }

2.3.2 AdjustDown(向下调整算法)

当我们要实现HeapPop(删除堆顶的数据)时,我们不能像往常的数组那样进行头删。因为数组再进行头删时,还要将从第二个位置起的后面的所有元素都向前平移。

但是堆这样行不通,因为随意挪动数据会造成关系的混乱。例如:

此时这个二叉树结构就不再是一个大堆了。

那么有什么好的办法不使堆的结构紊乱呢?这就得用到向下调整算法了。

例如,假设此时要删除堆顶的数据:

第一步,交换堆顶与堆尾的值,并将堆的Size--(相当于删除了末尾的元素)。

第二步,对45进行向下调整。找出45的两个孩子中值最大(是小堆就选小的)的那个,如果5小于该数字就与其进行交换。

循环此步骤,直至45到达正确的位置。

显然,此时情况较为简单,只用一步就到达了正确位置。(此时70已经不存在了,所以不用比较)

以上就是向下调整的过程,来看看代码的实现。

  1. void AdjustDown(HPDataType* a, int n, int parent)
  2. {
  3. assert(a);
  4. //先默认较大的为左孩子
  5. int child = parent * 2+1;
  6. while (child<n)
  7. {
  8. //如果右孩子比左孩子大,就++
  9. if (a[child] < a[child + 1] && child + 1 < n)
  10. {
  11. child++;
  12. }
  13. //建大堆用'>',小堆用'<'
  14. if (a[child] > a[parent])
  15. {
  16. Swap(&a[child], &a[parent]);
  17. parent = child;
  18. child = parent * 2 + 1;
  19. }
  20. else
  21. {
  22. break;
  23. }
  24. }
  25. }
  26. void HeapPop(Heap* php)
  27. {
  28. assert(php);
  29. assert(php->size>0);
  30. //将堆顶的数据与堆尾交换
  31. Swap(&php->a[0], &php->a[php->size - 1]);
  32. php->size--;
  33. //将此时堆顶的data向下调整
  34. AdjustDown(php->a, php->size, 0);
  35. }

特别注意:不管是向上调整还是向下调整,它们都得满足一个前提->

向上调整:进行向上调整的时候,该位置之前的所有数据已经是一个堆了。

向下调整:进行向下调整的时候,该位置的左子树和右子树都已经是堆了。

2.3.3 HeapCreate(如何建堆)

此函数所实现的功能是给出一个数组,对数组进行建堆(建大堆或者小堆)。

先来看看代码实现:

  1. void HeapCreate(Heap* php, HPDataType* a, int n)
  2. {
  3. php->a = (HPDataType*)malloc(sizeof(HPDataType) * n);
  4. if (php->a == NULL)
  5. {
  6. perror("malloc fail");
  7. exit(-1);
  8. }
  9. //将数组的内容全部拷贝到php->a中
  10. memcpy(php->a, a, sizeof(HPDataType) * n);
  11. php->size = php->capacity = n;
  12. //建堆算法
  13. for (int i = (n - 1 - 1) / 2; i >= 0; i--)
  14. {
  15. AdjustDown(php->a, n, i);
  16. }
  17. }

这里采用向下调整算法的的思路是,既然向下调整和向上调整都是有前提的,就不能直接进行使用。但是我们发现即使这个二叉树的数据是紊乱的,但是总有可以当作堆的一部分来使用向下调整(不用向上调整是因为不好控制)。例如:

在这个堆的底部(3个黑色圆圈里的部分)可以看作是堆,可以满足进行向下调整的条件。当把底层的三个堆建好以后,我们发现两个红色圆圈中的部分又可以看作满足条件的堆,对这两部分在进行向下调整。

完成之后,我们发现堆顶元素的左子树和右子树都已经是堆了,最后再将堆顶的元素进行向下调整,就建好一个完整的堆了。

总结起来就是如下图的步骤:

2.3.4 建堆的时间复杂度

因为堆是完全二叉树,而满二叉树也是完全二叉树,此处为了简化使用满二叉树来证明(时间复杂度本来看的就是近似值,多几个节点不影响最终结果):

因此,建堆的时间复杂度为O(N)。

3. 完整源码

Heap.h文件

  1. #pragma once
  2. #include<stdio.h>
  3. #include<stdlib.h>
  4. #include<assert.h>
  5. #include<string.h>
  6. #include<stdbool.h>
  7. typedef int HPDataType;
  8. typedef struct Heap
  9. {
  10. HPDataType* a; //存储数据
  11. int size; //堆有效数据的大小
  12. int capacity; //堆的容量
  13. }Heap;
  14. //给出一个数组,对它进行建堆
  15. void HeapCreate(Heap* php, HPDataType* a, int n);
  16. //堆的初始化
  17. void HeapInit(Heap* php);
  18. //对申请的内存释放
  19. void HeapDestroy(Heap* php);
  20. //添加数据
  21. void HeapPush(Heap* php, HPDataType data);
  22. //删除数据
  23. void HeapPop(Heap* php);
  24. //向上调整算法
  25. void AdjustUp(HPDataType* a, int child);
  26. //向下调整算法
  27. void AdjustDown(HPDataType* a, int n, int parent);
  28. //打印堆的数据
  29. void HeapPrint(Heap* php);
  30. //判断堆是否为空
  31. bool HeapEmpty(Heap* php);
  32. //返回堆的大小
  33. int HeapSize(Heap* php);
  34. //返回堆顶的数据
  35. HPDataType HeapTop(Heap* php);
  36. //交换函数
  37. void Swap(HPDataType* p1, HPDataType* p2);

Heap.c文件 

  1. #define _CRT_SECURE_NO_DEPRECATE 1
  2. #include"Heap.h"
  3. void HeapCreate(Heap* php, HPDataType* a, int n)
  4. {
  5. php->a = (HPDataType*)malloc(sizeof(HPDataType) * n);
  6. if (php->a == NULL)
  7. {
  8. perror("malloc fail");
  9. exit(-1);
  10. }
  11. //将数组的内容全部拷贝到堆中
  12. memcpy(php->a, a, sizeof(HPDataType) * n);
  13. php->size = php->capacity = n;
  14. //建堆算法
  15. for (int i = (n - 1 - 1) / 2; i >= 0; i--)
  16. {
  17. AdjustDown(php->a, n, i);
  18. }
  19. }
  20. void HeapInit(Heap* php)
  21. {
  22. assert(php);
  23. php->a = NULL;
  24. php->size = php->capacity = 0;
  25. }
  26. void HeapPrint(Heap* php)
  27. {
  28. assert(php);
  29. for (int i = 0; i < php->size; i++)
  30. {
  31. printf("%d ", php->a[i]);
  32. }
  33. }
  34. void HeapDestroy(Heap* php)
  35. {
  36. assert(php);
  37. free(php->a);
  38. php->a = NULL;
  39. php->capacity = php->size = 0;
  40. }
  41. void HeapPush(Heap* php, HPDataType data)
  42. {
  43. assert(php);
  44. //如果容量不足就扩容
  45. if (php->size == php->capacity)
  46. {
  47. int newCapacity = php->capacity == 0 ? 4 : php->capacity * 2;
  48. HPDataType* tmp = (HPDataType*)realloc(php->a, sizeof(HPDataType)*newCapacity);
  49. if (tmp == NULL)
  50. {
  51. perror("realloc fail");
  52. exit(-1);
  53. }
  54. php->a = tmp;
  55. php->capacity = newCapacity;
  56. }
  57. //添加数据
  58. php->a[php->size] = data;
  59. php->size++;
  60. //将新入堆的data进行向上调整
  61. AdjustUp(php->a, php->size-1);
  62. }
  63. void HeapPop(Heap* php)
  64. {
  65. assert(php);
  66. assert(php->size>0);
  67. //将堆顶的数据与堆尾交换
  68. Swap(&php->a[0], &php->a[php->size - 1]);
  69. php->size--;
  70. //将此时堆顶的data向下调整
  71. AdjustDown(php->a, php->size, 0);
  72. }
  73. void AdjustDown(HPDataType* a, int n, int parent)
  74. {
  75. assert(a);
  76. //先默认较大的为左孩子
  77. int child = parent * 2+1;
  78. while (child<n)
  79. {
  80. //如果右孩子比左孩子大,就++
  81. if (a[child] < a[child + 1] && child + 1 < n)
  82. {
  83. child++;
  84. }
  85. //建大堆用'>',小堆用'<'
  86. if (a[child] > a[parent])
  87. {
  88. Swap(&a[child], &a[parent]);
  89. parent = child;
  90. child = parent * 2 + 1;
  91. }
  92. else
  93. {
  94. break;
  95. }
  96. }
  97. }
  98. void AdjustUp(HPDataType* a, int child)
  99. {
  100. int parent = (child - 1) / 2;
  101. while (child > 0)
  102. {
  103. //建大堆用'>',小堆用'<'
  104. if (a[child] > a[parent])
  105. {
  106. Swap(&a[child], &a[parent]);
  107. child = parent;
  108. parent = (child - 1) / 2;
  109. }
  110. else
  111. {
  112. break;
  113. }
  114. }
  115. }
  116. HPDataType HeapTop(Heap* php)
  117. {
  118. assert(php);
  119. assert(php->size>0);
  120. return php->a[0];
  121. }
  122. int HeapSize(Heap* php)
  123. {
  124. assert(php);
  125. return php->size;
  126. }
  127. bool HeapEmpty(Heap* php)
  128. {
  129. assert(php);
  130. return !php->size;
  131. }
  132. void Swap(HPDataType* p1, HPDataType* p2)
  133. {
  134. HPDataType tmp = *(p1);
  135. *(p1) = *(p2);
  136. *(p2) = tmp;
  137. }

Test.c文件 

  1. #define _CRT_SECURE_NO_DEPRECATE 1
  2. #include"Heap.h"
  3. void test()
  4. {
  5. HPDataType arr[10] = { 12,34,45,78,56,74,3,7,9,5 };
  6. Heap hp;
  7. HeapCreate(&hp, arr, 10);
  8. //HeapInit(&hp);
  9. //HeapPush(&hp, 10);
  10. //HeapPush(&hp, 70);
  11. //HeapPush(&hp, 15);
  12. //HeapPush(&hp, 30);
  13. //HeapPush(&hp, 25);
  14. //HeapPrint(&hp);
  15. //HeapPop(&hp);
  16. //HeapPrint(&hp);
  17. //HeapPop(&hp);
  18. //HeapPrint(&hp);
  19. //HeapPop(&hp);
  20. HeapPrint(&hp);
  21. }
  22. int main()
  23. {
  24. test();
  25. return 0;
  26. }

至此,本章的内容就结束了,下一章将进行堆的实际应用——堆排序以及TopK问题的说明。

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

闽ICP备14008679号