当前位置:   article > 正文

数据结构-----堆(完全二叉树)_完全二叉树难点

完全二叉树难点

 目录

前言

一.堆

1.堆的概念

2.堆的存储方式

二.堆的操作方法

1.堆的结构体表示

2.数字交换接口函数

3.向上调整(难点)

4.向下调整(难点)

5.创建堆

 6.堆的插入

 7.判断空

8.堆的删除

 9.获取堆的根(顶)元素

10.堆的遍历

 11.销毁堆

完整代码

三.堆的应用(堆排序)

1.算法介绍

2.基本思想

3.代码实现

4.算法分析


前言

         今天我们开始学习一种二叉树,没错,那就是完全二叉树,完全二叉树又叫做堆,在此之前我们简单介绍过了完全二叉树的概念(链接:数据结构-----树和二叉树的定义与性质_灰勒塔德的博客-CSDN博客),这种类型的二叉树又有什么特点呢?代码怎么去实现呢?应用有那些呢?下面就一起来看看吧!

一.堆

1.堆的概念

堆(heap)是计算机科学中一类特殊的数据结构的统称。堆通常是一个可以被看做一棵树的数组对象,物理层面上是一个数组,逻辑上是一个完全二叉树。堆总是满足下列性质:

  • 堆中某个结点的值总是不大于或不小于其父结点的值;

  • 堆总是一棵完全二叉树。

  • 满足任意父节点都大于子节点的称作为大堆

  • 满足任意子节点都大于父节点的称作为小堆

  • tip:(下文会以大堆的创建为示例)

如图所示:

 

2.堆的存储方式

堆的储存原则是从上到下,从左到右,也就是说先有上面的父节点才会有子节点,先有左子节点,才会有右子节点 ,所以堆可以去通过一个数组完整的表示出来,如下图所示:

二.堆的操作方法

以下是一个堆要实现的基本功能,下面我会一一去详细解释说明

  1. void swap(DataType* a, DataType* b);//交换数据
  2. void Adjust_Up(DataType* data, int child, int n);//向上调整
  3. void Adjust_Down(DataType* data, int parent, int n);//向下调整
  4. void Heap_Create(Heap* hp, DataType* data, int n);//创建堆
  5. bool isEmpty(Heap* hp);//判断空
  6. void Heap_Insert(Heap* hp, DataType x);//堆的插入
  7. void Heap_Del(Heap* hp);//堆的删除操作
  8. DataType Heap_Root(Heap* hp);//获取根元素
  9. void Heap_show(Heap* hp);//堆的遍历
  10. void Heap_Destory(Heap* hp);//堆的销毁

1.堆的结构体表示

  1. #include<stdio.h>
  2. #include<stdlib.h>
  3. #include<assert.h>
  4. #define Maxsize 50
  5. //顺序结构
  6. //堆(完全二叉树)
  7. typedef int DataType; //定义数据的类型
  8. typedef struct Heap
  9. {
  10. int size; //当前节点数量
  11. int capacity; //最大容量
  12. DataType* data; //数据储存地址
  13. }Heap;

2.数字交换接口函数

  1. //数据交换接口
  2. void swap(DataType* a, DataType* b) {
  3. DataType temp = *a;
  4. *a = *b;
  5. *b = temp;
  6. }

3.向上调整(难点)

        创建大堆时,向上调整的目的是,在有子节点位置的情况下,进行与父节点的大小比较,如果子节点大于父节点,那么就进行交换,然后新的子节点就是上一个的父节点,依次这样比较下去,最后到根节点为止,如图所示:

 代码实现:

  1. //向上调整
  2. void Adjust_Up(DataType* data, int child, int n) {
  3. int parent = (child - 1) / 2;
  4. while (child > 0) {
  5. //如果子节点大于父节点就进行数值交换,然后此时的子节点就是前一个父节点,再找到
  6. //新的父节点,继续进行同样的操作,直到根节点为止
  7. if (data[child] > data[parent])
  8. {
  9. swap(&data[child], &data[parent]);
  10. child = parent;
  11. parent = (child - 1) / 2;
  12. }
  13. else
  14. {
  15. break;
  16. }
  17. }
  18. }

4.向下调整(难点)

        同样的还有向下调整,如果有了当前的父节点位置,那么就要跟子节点进行比较,但是子节点有左和右子节点,所以左右子节点也要去比较,取到其中比较大的子节点与父节点比较,如果这个字节点大于父节点的话,那就进行数字交换,然后新的父节点就是上一个的子节点,依次往下遍历进行同样的操作。

代码实现: 

  1. //向下调整
  2. void Adjust_Down(DataType* data, int parent, int n) {
  3. int child = parent * 2 + 1;
  4. while (child <n ) {
  5. if (child+1 < n && data[child] < data[child+1])
  6. {
  7. //如果右子节点大于左子节点,那就child+1,选中到右子节点
  8. child++;
  9. }
  10. if (data[child] > data[parent]) {
  11. //同样的,有了当前父节点,然后找到子节点,进行向下遍历调整操作
  12. swap(&data[child], &data[parent]);
  13. parent = child;
  14. child = parent * 2 + 1;
  15. }
  16. else
  17. {
  18. break;
  19. }
  20. }
  21. }

5.创建堆

已有一个数组{ 5,1,2,3,6,4,8 },怎么把这个数组放入堆里面呢?同样的,空间申请去申请到一块连续的空间,然后依次把数据存入到这个数组里面去,最后进行向下调整,以达到堆的形式。

放入堆之后如下图所示: 

代码实现:

  1. //创建堆
  2. void Heap_Create(Heap* hp, DataType* data, int n) {
  3. assert(hp);
  4. hp->data = (DataType*)malloc(sizeof(DataType) * n);
  5. if (!hp->data) {
  6. printf("ERROR\n");
  7. exit(-1);
  8. }
  9. for (int i = 0; i < n; i++) {
  10. hp->data[i] = data[i];//赋值
  11. }
  12. hp->size = n;
  13. hp->capacity = Maxsize;
  14. for (int j = (n - 1) / 2; j >= 0; j--) {
  15. //创建完成了之后,就要进行向下调整
  16. Adjust_Down(hp->data, j ,hp->size);
  17. }
  18. }

 6.堆的插入

堆的插入,就是在堆的最后面去添加一个元素,添加完成之后,就要去进行向上调整操作,如下图所示:

代码实现: 

  1. //堆的插入
  2. void Heap_Insert(Heap* hp, DataType x) {
  3. assert(hp);
  4. //如果此时的堆空间满了,那么就要去扩容空间
  5. if (hp->size == hp->capacity) {
  6. DataType* temp = (DataType*)realloc(hp->data,sizeof(DataType) * (hp->capacity+1));//追加1个空间
  7. if (!temp) {
  8. printf("ERROR\n");
  9. exit(-1);
  10. }
  11. hp->data = temp;
  12. hp->data[hp->size] = x;
  13. hp->size++;
  14. hp->capacity++;
  15. }
  16. else
  17. {
  18. hp->data[hp->size] = x;
  19. hp->size++;
  20. }
  21. Adjust_Up(hp->data, hp->size - 1, hp->size);//插入后进行向上调整
  22. }

 7.判断空

  1. //判断空
  2. bool isEmpty(Heap* hp) {
  3. assert(hp);
  4. return hp->size == 0;
  5. }

8.堆的删除

堆的删除操作是删除掉根节点,过程是,先把最后一个节点与根节点进行交换,然后重新进行向下调整。(堆的删除操作,删除掉的是根节点!

代码实现: 

  1. //堆的删除,删除根节点
  2. void Heap_Del(Heap* hp) {
  3. assert(hp);
  4. if (!isEmpty(hp)) {
  5. swap(&hp->data[hp->size - 1], &hp->data[0]);//根节点和尾节点进行交换
  6. hp->size--;
  7. Adjust_Down(hp->data, 0, hp->size);//向下调整
  8. }
  9. }

 9.获取堆的根(顶)元素

  1. //获取堆顶元素
  2. DataType Heap_Root(Heap* hp) {
  3. assert(hp);
  4. if (!isEmpty(hp))
  5. return hp->data[0];
  6. else
  7. exit(0);
  8. }

10.堆的遍历

堆的遍历就直接按照数组的顺序去遍历就行了,完全二叉树的逻辑上是从上到下,从左到右去遍历的,代码如下:

  1. //输出堆元素(按照顺序)
  2. void Heap_show(Heap* hp) {
  3. assert(hp);
  4. if (isEmpty(hp)) {
  5. printf("The Heap is etmpy\n");
  6. return;
  7. }
  8. for (int i = 0; i < hp->size; i++)
  9. printf("%d ", hp->data[i]);
  10. }

 11.销毁堆

  1. //堆的销毁
  2. void Heap_Destory(Heap* hp) {
  3. assert(hp);
  4. hp->size = hp->capacity = 0;
  5. free(hp);//释放空间
  6. }

完整代码

  1. #include<stdio.h>
  2. #include<stdlib.h>
  3. #include<assert.h>
  4. #define Maxsize 50
  5. //顺序结构
  6. //堆(完全二叉树)
  7. typedef int DataType; //定义数据的类型
  8. typedef struct Heap
  9. {
  10. int size; //当前节点数量
  11. int capacity; //最大容量
  12. DataType* data; //数据储存地址
  13. }Heap;
  14. //数据交换接口
  15. void swap(DataType* a, DataType* b) {
  16. DataType temp = *a;
  17. *a = *b;
  18. *b = temp;
  19. }
  20. //向上调整
  21. void Adjust_Up(DataType* data, int child, int n) {
  22. int parent = (child - 1) / 2;
  23. while (child > 0) {
  24. //如果子节点大于父节点就进行数值交换,然后此时的子节点就是前一个父节点,再找到
  25. //新的父节点,继续进行同样的操作,直到根节点为止
  26. if (data[child] > data[parent])
  27. {
  28. swap(&data[child], &data[parent]);
  29. child = parent;
  30. parent = (child - 1) / 2;
  31. }
  32. else
  33. {
  34. break;
  35. }
  36. }
  37. }
  38. //向下调整
  39. void Adjust_Down(DataType* data, int parent, int n) {
  40. int child = parent * 2 + 1;
  41. while (child <n ) {
  42. if (child+1 < n && data[child] < data[child+1])
  43. {
  44. //如果右子节点大于左子节点,那就child+1,选中到右子节点
  45. child++;
  46. }
  47. if (data[child] > data[parent]) {
  48. //同样的,有了当前父节点,然后找到子节点,进行向下遍历调整操作
  49. swap(&data[child], &data[parent]);
  50. parent = child;
  51. child = parent * 2 + 1;
  52. }
  53. else
  54. {
  55. break;
  56. }
  57. }
  58. }
  59. //创建堆
  60. void Heap_Create(Heap* hp, DataType* data, int n) {
  61. assert(hp);
  62. hp->data = (DataType*)malloc(sizeof(DataType) * n);
  63. if (!hp->data) {
  64. printf("ERROR\n");
  65. exit(-1);
  66. }
  67. for (int i = 0; i < n; i++) {
  68. hp->data[i] = data[i];//赋值
  69. }
  70. hp->size = n;
  71. hp->capacity = Maxsize;
  72. for (int j = (n - 1) / 2; j >= 0; j--) {
  73. //创建完成了之后,就要进行向下调整
  74. Adjust_Down(hp->data, j ,hp->size);
  75. }
  76. }
  77. //判断空
  78. bool isEmpty(Heap* hp) {
  79. assert(hp);
  80. return hp->size == 0;
  81. }
  82. //堆的插入
  83. void Heap_Insert(Heap* hp, DataType x) {
  84. assert(hp);
  85. //如果此时的堆空间满了,那么就要去扩容空间
  86. if (hp->size == hp->capacity) {
  87. DataType* temp = (DataType*)realloc(hp->data,sizeof(DataType) * (hp->capacity+1));//追加1个空间
  88. if (!temp) {
  89. printf("ERROR\n");
  90. exit(-1);
  91. }
  92. hp->data = temp;
  93. hp->data[hp->size] = x;
  94. hp->size++;
  95. hp->capacity++;
  96. }
  97. else
  98. {
  99. hp->data[hp->size] = x;
  100. hp->size++;
  101. }
  102. Adjust_Up(hp->data, hp->size - 1, hp->size);//插入后进行向上调整
  103. }
  104. //堆的删除,取出根节点
  105. void Heap_Del(Heap* hp) {
  106. assert(hp);
  107. if (!isEmpty(hp)) {
  108. swap(&hp->data[hp->size - 1], &hp->data[0]);//根节点和尾节点进行交换
  109. hp->size--;
  110. Adjust_Down(hp->data, 0, hp->size);//向下调整
  111. }
  112. }
  113. //获取堆顶元素
  114. DataType Heap_Root(Heap* hp) {
  115. assert(hp);
  116. if (!isEmpty(hp))
  117. return hp->data[0];
  118. else
  119. exit(0);
  120. }
  121. //输出堆元素(按照顺序)
  122. void Heap_show(Heap* hp) {
  123. assert(hp);
  124. if (isEmpty(hp)) {
  125. printf("The Heap is etmpy\n");
  126. return;
  127. }
  128. for (int i = 0; i < hp->size; i++)
  129. printf("%d ", hp->data[i]);
  130. }
  131. //堆的销毁
  132. void Heap_Destory(Heap* hp) {
  133. assert(hp);
  134. hp->size = hp->capacity = 0;
  135. free(hp);//释放空间
  136. }

三.堆的应用(堆排序

1.算法介绍

        堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。

2.基本思想

利用大顶堆(小顶堆)堆顶记录的是最大关键字(最小关键字)这一特性,使得每次从无序中选择最大记录(最小记录)变得简单。

① 将待排序的序列构造成一个最大堆,此时序列的最大值为根节点
② 依次将根节点与待排序序列的最后一个元素交换
③ 再维护从根节点到该元素的前一个节点为最大堆,如此往复,最终得到一个递增序列

3.代码实现

  1. #include<stdio.h>
  2. #include<assert.h>
  3. //数据交换接口
  4. void swap(int *a, int *b) {
  5. int temp = *a;
  6. *a = *b;
  7. *b = temp;
  8. }
  9. //向下调整
  10. void Adjust_Down(int* data, int parent, int n) {
  11. int child = parent * 2 + 1;
  12. while (child < n) {
  13. if (child + 1 < n && data[child] < data[child + 1])
  14. {
  15. //如果右子节点大于左子节点,那就child+1,选中到右子节点
  16. child++;
  17. }
  18. if (data[child] > data[parent]) {
  19. //同样的,有了当前父节点,然后找到子节点,进行向下遍历调整操作
  20. swap(&data[child], &data[parent]);
  21. parent = child;
  22. child = parent * 2 + 1;
  23. }
  24. else
  25. {
  26. break;
  27. }
  28. }
  29. }
  30. //堆排序算法
  31. void Heap_sort(int* arr, int n) {
  32. assert(arr);
  33. for (int i = (n - 2) / 2; i >= 0; i--) {
  34. Adjust_Down(arr, i, n);
  35. }//先形成最大堆
  36. int end = n - 1;
  37. //从小到大排序
  38. while (end > 0) {
  39. swap(&arr[0], &arr[end]);
  40. Adjust_Down(arr, 0, end);
  41. end--; //此时最后一个也就是当前的最大值已经排序好了
  42. }
  43. }
  44. int main() {
  45. int a[9] = { 5,1,2,3,6,4,8,2,10 };
  46. Heap_sort(a, sizeof(a) / sizeof(int));
  47. for (int i = 0; i < sizeof(a) / sizeof(int); i++) {
  48. printf("%d ", a[i]);
  49. }
  50. }
  51. //输出
  52. //1 2 2 3 4 5 6 8 10

4.算法分析

  • 平均时间复杂度:O(nlogn)
  • 最佳时间复杂度:O(nlogn)
  • 最差时间复杂度:O(nlogn)
  • 稳定性:不稳定

 以上就是本期的内容,我们下次见!

 分享一张壁纸:

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

闽ICP备14008679号