当前位置:   article > 正文

【数据结构】树-堆的详解

【数据结构】树-堆的详解

目录

1.树的概念

树的相关概念:

结点的度:

叶结点或终端结点:

非终端结点或分支结点:

双亲结点或父结点:

孩子结点或子结点:

兄弟结点:

树的度:

结点的层次:

树的高度或深度:

堂兄弟结点:

结点的祖先:

子孙:

森林:

二叉树的概念

堆的概念

大堆,小堆概念 

 堆的实现

堆的底层逻辑:动态数组 

初始化和销毁 

堆的插入 

堆的删除 

堆的判空和取堆的顶数据

整体代码及测试: 

 


1.树的概念

在学习了解堆之前,我们需要来了解树,堆是树中的一种特殊情况。 

可能我们听到树这个名字时,脑海中就会不由自主的浮现一颗树的形状,树作为数据结构的一环,它的,模型就像是一颗倒着的树一样的结构,每个节点存放着我们需要的数据,示例图如下:

树的相关概念:

结点的度:

如图所示,节点的度指的就是该节点所连接的其他节点个数,图中A节点的度为6,B节点的度为3 

叶结点或终端结点:

 叶结点或终端结点又称作叶子,就像一颗树上的叶子一样,后续没有枝干延升了,树中的叶子节点指的就是那些度为0的节点。


非终端结点或分支结点:

指的就是那些度不为0的节点


双亲结点或父结点:

如图,A为父亲的话,那么B,C,D,E,F,G都是它的孩子


孩子结点或子结点:

指的就是B,C,D,E,F,G


兄弟结点:


树的度:

以树中节点度中的最大一个为树的度,如图树中A节点的度最大为6,那么树的度就是为6


结点的层次:

从根开始定义起,根为第1层,根的子结点为第2层,以此类推


树的高度或深度:

 树中结点的最大层次; 如上图:树的高度为4


堂兄弟结点:

双亲在同一层的结点互为堂兄弟;如上图:H、I互为兄弟结点


结点的祖先:

从根到该结点所经分支上的所有结点;如上图:A是所有结点的祖先
 


子孙:

以某结点为根的子树中任一结点都称为该结点的子孙。如上图:所有结点都是A的子孙


森林:

由m(m>0)棵互不相交的树的集合称为森林
 

二叉树的概念

 二叉树是树中特殊的一种,它的每一个父亲节点的孩子都不会超过两个。

  •  二叉树不存在度大于2的结点
  • 二叉树的子树有左右之分,次序不能颠倒,因此二叉树是有序树

以下又是二叉树的各种情况: 

 

  • 满二叉树:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为K,且结点总数是 ,则它就是满二叉树。
  • 完全二叉树:完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。

堆的概念

堆就属于满二叉树或者完全二叉树的一种,堆的底层实现可以通过顺序存储或者链式存储,本次我们实现的是顺序存储。那么一个堆在底层的顺序存储又是什么样子的呢,看下图。

 通过上图我们又可以了解如何使用每一个父亲节点去计算孩子节点:

  • 左child = parent * 2 + 1  
  • 右child = parent * 2 + 2

 通过孩子计算父亲节点:

  • parent = (child - 1) /2

大堆,小堆概念 

  • 大堆:所有的父亲节点的数据都>=孩子节点
  • 小堆:所有的父亲节点的数据都<=孩子节点 

 堆的实现

堆的底层逻辑:动态数组 

  1. #include <stdio.h>
  2. #include <assert.h>
  3. #include <stdbool.h>
  4. #include <stdlib.h>
  5. // 类型定义
  6. typedef int HPDataTpye;
  7. typedef struct Heap
  8. {
  9. // 动态数组
  10. HPDataTpye* a;
  11. // 元素个数
  12. int size;
  13. // 空间容量
  14. int capacity;
  15. }Heap;

其实操作堆的底层就是对动态数组的操作,我们只需按照堆的方式进行设计即可。 

以下是我们会实现的接口功能: 

  1. // 函数声明
  2. // 堆的初始化
  3. void HeapInit(Heap* php);
  4. // 堆的销毁
  5. void HeapDestory(Heap* php);
  6. // 堆的插入
  7. void HeapPush(Heap* php, HPDataTpye x);
  8. // 堆的删除
  9. void HeapPop(Heap* php);
  10. // 堆的判空
  11. bool HeapEmpty(Heap* php);
  12. // 取堆的顶数据
  13. HPDataTpye HeapTop(Heap* php);

初始化和销毁 

  1. // 堆的初始化
  2. void HeapInit(Heap* php)
  3. {
  4. // 判空
  5. assert(php);
  6. // 初始化
  7. php->a = NULL;
  8. php->size = php->capacity = 0;
  9. }
  10. // 堆的销毁
  11. void HeapDestory(Heap* php)
  12. {
  13. // 判空
  14. assert(php);
  15. // 释放空间
  16. free(php->a);
  17. php->a = NULL;
  18. php->size = php->capacity = 0;
  19. }

堆的插入 

 这边我们先实现一个大堆的案例:

首先我们在插入堆的情况可能有以下三种情况: 

 

 

如果插入的数据是图一的情况,我们直接不用管理了,插入之后还是一个大堆,但是如果是图二或者图三的情况,我们就需要对堆进行一个调整,将它调整为大堆的情况。

首先我们需要计算出最后插入数据的父亲节点,让后进行比较,如果刚插入的孩子节点大我们就进行交换,简单点说就是谁大谁当爹,然后通过这样不断的调整,直到插入的孩子到顶,或者中途有父亲节点比它大才结束。

以下是图形过程:

代码实现: 

  1. // 向上调整
  2. void AdjustUp(HPDataTpye* a, int child)
  3. {
  4. // 算父亲节点
  5. int parent = (child - 1) / 2;
  6. // 循环向上排序
  7. while (child > 0)
  8. {
  9. if (a[child] > a[parent])
  10. {
  11. // 交换
  12. Swap(&a[child], &a[parent]);
  13. // 把父亲赋值给孩子
  14. child = parent;
  15. parent = (child - 1) / 2;
  16. }
  17. else {
  18. break;
  19. }
  20. }
  21. }
  22. // 堆的插入
  23. void HeapPush(Heap* php, HPDataTpye x)
  24. {
  25. // 判空
  26. assert(php);
  27. // 判断空间是否满
  28. if (php->size == php->capacity)
  29. {
  30. // 看原来的空间容量是否为0,为0分配4个空间,不为0空间*2
  31. int newcapacity = php->capacity == 0 ? 4 : php->capacity * 2;
  32. // 分配动态空间
  33. HPDataTpye* tmp = (HPDataTpye*)realloc(php->a, newcapacity * sizeof(HPDataTpye));
  34. if (NULL == tmp)
  35. {
  36. perror("malloc fail");
  37. return;
  38. }
  39. php->a = tmp;
  40. php->capacity = newcapacity;
  41. }
  42. // 插入
  43. php->a[php->size - 1] = x;
  44. php->size++;
  45. // 向上调整
  46. AdjustUp(php->a, php->size-1);
  47. }

 测试一下:

  1. void HeapText01()
  2. {
  3. Heap h;
  4. // 初始化
  5. HeapInit(&h);
  6. int arr[] = { 4,2,8,1,5,6,9,7};
  7. for (int i = 0; i < sizeof(arr) / sizeof(int); i++)
  8. {
  9. HeapPush(&h, arr[i]);
  10. }
  11. // 销毁
  12. HeapDestory(&h);
  13. }
  14. int main()
  15. {
  16. HeapText01();
  17. return 0;
  18. }

 

那么实现小堆模型呢,其实很简单,我们只需要将判定大小的条件改一下即可 

  1. // 向上调整
  2. void AdjustUp(HPDataTpye* a, int child)
  3. {
  4. // 算父亲节点
  5. int parent = (child - 1) / 2;
  6. // 循环向上排序
  7. while (child > 0)
  8. {
  9. if (a[child] < a[parent])
  10. {
  11. // 交换
  12. Swap(&a[child], &a[parent]);
  13. // 把父亲赋值给孩子
  14. child = parent;
  15. parent = (child - 1) / 2;
  16. }
  17. else {
  18. break;
  19. }
  20. }
  21. }

堆的删除 

 

  1. // 向下调整
  2. void AdjustDown(HPDataTpye* a, int n, int parent)
  3. {
  4. int child = parent * 2 + 1;
  5. while (child < n)
  6. {
  7. if (child + 1 < n && a[child] < a[child + 1])
  8. {
  9. child++;
  10. }
  11. if (a[child] > a[parent])
  12. {
  13. // 交换
  14. Swap(&a[child], &a[parent]);
  15. parent = child;
  16. child = parent * 2 + 1;
  17. }
  18. else {
  19. break;
  20. }
  21. }
  22. }
  23. // 堆的删除
  24. void HeapPop(Heap* php)
  25. {
  26. // 判空
  27. assert(php);
  28. assert(php->size > 0);
  29. // 交换
  30. Swap(&php->a[0], &php->a[php->size - 1]);
  31. php->size--;
  32. // 向下调整
  33. AdjustDown(php->a, php->size, 0);
  34. }

堆的判空和取堆的顶数据

  1. // 堆的判空
  2. bool HeapEmpty(Heap* php)
  3. {
  4. // 判空
  5. assert(php);
  6. return php->size == 0;
  7. }
  8. // 取堆的顶数据
  9. HPDataTpye HeapTop(Heap* php)
  10. {
  11. // 判空
  12. assert(php);
  13. return php->a[0];
  14. }

时间复杂度计算 

以下图片是向上调整和向下调整的时间复杂度计算:

 

整体代码及测试: 

  1. #pragma once
  2. #include <stdio.h>
  3. #include <assert.h>
  4. #include <stdbool.h>
  5. #include <stdlib.h>
  6. // 类型定义
  7. typedef int HPDataTpye;
  8. typedef struct Heap
  9. {
  10. // 动态数组
  11. HPDataTpye* a;
  12. // 元素个数
  13. int size;
  14. // 空间容量
  15. int capacity;
  16. }Heap;
  17. // 函数声明
  18. // 堆的初始化
  19. void HeapInit(Heap* php);
  20. // 堆的销毁
  21. void HeapDestory(Heap* php);
  22. // 堆的插入
  23. void HeapPush(Heap* php, HPDataTpye x);
  24. // 堆的删除
  25. void HeapPop(Heap* php);
  26. // 堆的判空
  27. bool HeapEmpty(Heap* php);
  28. // 取堆的顶数据
  29. HPDataTpye HeapTop(Heap* php);
  1. #include "Heap.h"
  2. // 堆的初始化
  3. void HeapInit(Heap* php)
  4. {
  5. // 判空
  6. assert(php);
  7. // 初始化
  8. php->a = NULL;
  9. php->size = php->capacity = 0;
  10. }
  11. // 堆的销毁
  12. void HeapDestory(Heap* php)
  13. {
  14. // 判空
  15. assert(php);
  16. // 释放空间
  17. free(php->a);
  18. php->a = NULL;
  19. php->size = php->capacity = 0;
  20. }
  21. // 交换
  22. void Swap(HPDataTpye* p1, HPDataTpye* p2)
  23. {
  24. HPDataTpye tmp = *p1;
  25. *p1 = *p2;
  26. *p2 = tmp;
  27. }
  28. // 向上调整
  29. void AdjustUp(HPDataTpye* a, int child)
  30. {
  31. // 算父亲节点
  32. int parent = (child - 1) / 2;
  33. // 循环向上排序
  34. while (child > 0)
  35. {
  36. if (a[child] > a[parent])
  37. {
  38. // 交换
  39. Swap(&a[child], &a[parent]);
  40. // 把父亲赋值给孩子
  41. child = parent;
  42. parent = (child - 1) / 2;
  43. }
  44. else {
  45. break;
  46. }
  47. }
  48. }
  49. // 堆的插入
  50. void HeapPush(Heap* php, HPDataTpye x)
  51. {
  52. // 判空
  53. assert(php);
  54. // 判断空间是否满
  55. if (php->size == php->capacity)
  56. {
  57. // 看原来的空间容量是否为0,为0分配4个空间,不为0空间*2
  58. int newcapacity = php->capacity == 0 ? 4 : php->capacity * 2;
  59. // 分配动态空间
  60. HPDataTpye* tmp = (HPDataTpye*)realloc(php->a, newcapacity * sizeof(HPDataTpye));
  61. if (NULL == tmp)
  62. {
  63. perror("malloc fail");
  64. return;
  65. }
  66. php->a = tmp;
  67. php->capacity = newcapacity;
  68. }
  69. // 插入
  70. php->a[php->size] = x;
  71. php->size++;
  72. // 向上调整
  73. AdjustUp(php->a, php->size - 1);
  74. }
  75. // 向下调整
  76. void AdjustDown(HPDataTpye* a, int n, int parent)
  77. {
  78. int child = parent * 2 + 1;
  79. while (child < n)
  80. {
  81. if ((child + 1) < n && a[child + 1] > a[child])
  82. {
  83. ++child;
  84. }
  85. if (a[child] > a[parent])
  86. {
  87. // 交换
  88. Swap(&a[child], &a[parent]);
  89. parent = child;
  90. child = parent * 2 + 1;
  91. }
  92. else {
  93. break;
  94. }
  95. }
  96. }
  97. // 堆的删除
  98. void HeapPop(Heap* php)
  99. {
  100. // 判空
  101. assert(php);
  102. assert(php->size > 0);
  103. // 交换
  104. Swap(&php->a[0], &php->a[php->size - 1]);
  105. php->size--;
  106. // 向下调整
  107. AdjustDown(php->a, php->size, 0);
  108. }
  109. // 堆的判空
  110. bool HeapEmpty(Heap* php)
  111. {
  112. // 判空
  113. assert(php);
  114. return php->size == 0;
  115. }
  116. // 取堆的顶数据
  117. HPDataTpye HeapTop(Heap* php)
  118. {
  119. // 判空
  120. assert(php);
  121. assert(php->size > 0);
  122. return php->a[0];
  123. }
  1. #include "Heap.h"
  2. void HeapText()
  3. {
  4. Heap h1;
  5. // 初始化
  6. HeapInit(&h1);
  7. int arr[] = { 4,2,8,1,5,6,9,7};
  8. for (int i = 0; i < sizeof(arr) / sizeof(int); i++)
  9. {
  10. HeapPush(&h1, arr[i]);
  11. }
  12. int i = 0;
  13. while (!HeapEmpty(&h1))
  14. {
  15. printf("%d ", HeapTop(&h1));
  16. //arr[i++] = HeapTop(&h1);
  17. HeapPop(&h1);
  18. }
  19. printf("\n");
  20. //计算前十最富有的人
  21. /*int k = 0;
  22. scanf_s("%d", &k);
  23. while (k--)
  24. {
  25. printf("%d ", HeapTop(&h1));
  26. HeapPop(&h1);
  27. }*/
  28. // 销毁
  29. HeapDestory(&h1);
  30. }
  31. int main()
  32. {
  33. HeapText();
  34. return 0;
  35. }

 

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

闽ICP备14008679号