• 【C++】树?堆?怎么实现?


    新的一周过去了,大家有没有对上星期练习的题目更加熟练呢?

    上星期和上上星期我们主要学习了顺序表,链表,和用这俩都能实现的栈和队列

    那么今天我们看看堆又是什么结构

    目录

    1.树 介绍

    2.堆 介绍

    3.堆的实现


    1.树の介绍

    不就是树,谁没见过对吧

    那么类似的计算机也有一种树,很像我们学过的树状图

    为了规范使用,先定义一些名称

    层数高度如你所见 就是你理解的意思

    其实我们发现,这个命名规则就很像遗传族谱,其中也是借用了遗传族谱的一些名称

    我们可以类比理解一下兄弟节点堂兄弟节点父子节点。。。

    节点的祖先,是指从该节点向上走的唯一路径上经历的所有节点都是他的  节点的祖先 

    我们规定:树和树之间不能交叉!!!

    森林:互不相交的树构成的结构~~

    这只是最普通的树,还有一些稍微长得特殊一点的树

    二叉树:每个父节点都有两个枝丫

    二叉树里面又有两种

    满二叉树:每一层都是满的节点

    根据等比公式,满二叉树的高度为h(本图是4)那么节点个数就是2^h-1

    第k层是满的,那么那一层的节点个数是2^(k-1) ,k从1开始 

    完全二叉树:最后一层可以不满,但是前面层数必须满,必须从左到右连续

    大家自己去算一下,和刚才的方法一样的

    完全二叉树的高度是h,那么总结点最多2^h-1  最少2^(h-1) 


    还有一些小小的二叉树计算技巧~

    1.度为0的节点个数比度为2的节点个数多1

     2.完全二叉树度为1 的节点个数不是0就是1

    3.要计算叶子节点个数的题目,就是算度为0,可以假设度为0有N0个,度为1有N1,度为2有N2

    再根据 前两个技巧建立方程就好了


    简单的基础知识就是这么多,那么真的在内存中就是有一块空间存储这些小圈和直线吗?

    当然不是!!!!!!

    这个树形状的图只是帮助我们人理解的(人家计算机根本不用借助图好吧)

    所以这个图就是一种假想的模型,术语叫做 逻辑结构!!!(逻辑不好得用他帮助理解)


    那么就该思考了,真实的内存结构(术语:物理结构)是什么样子

    对于堆 其实是数组~

    更复杂的现在不讨论  


     那么对于一般的树,他的“遗传图谱”怎么表示出来啊~~~

    有个大神,想到了一种很的方法

    1. struct TreeNode
    2. {
    3. int data;
    4. TreeNode* child;
    5. TreeNode* brother;
    6. };

    我们细想一下,如果从最开始的根,我知道他的  孩子节点  和 孩子节点的兄弟节点 

    那简直就是完美解决整个图谱

     

    直呼大佬 


    2.堆的介绍

    大家肯定在C的时候就对堆耳熟能详,现在我们主要看一下更深层次的理解

     刚才说过,堆他的存放方式是这样的

    从0开始编号,是为了和数组从0开始很好的对应

    我们可以心里想着左图  但是不能不清楚他的真实结构是右图!!! 


    一个随随便便的数组不一定是堆,但是堆一定是一个数组 

    给你一个数组怎么判断他到底是不是一个堆?

    首先我们要清楚堆的分类

    大堆:每一个父节点都比他的子节点大

    小堆:每一个父节点都比他的子节点小

    所以我们只要看是不是大/小堆其中一种就行

    比如数组{1,4,5,8,99} 1比4 5都小,4比8和99都小,所以这就是一个小堆,即堆

    从中可以看出,对于数组你能不能一眼看出谁是谁的孩子,是左孩子还是右孩子?很重要

    因为堆是完全二叉树 ,所以每个节点最多肯定是2个孩子

    第一个数据1一定是根,往下两个4 5就是根(1)的孩子节点,在前面的4是左孩子

    8,99是4,5中靠前数据(即4)的孩子节点,同样的8是左孩子


     对应上我们刚才学的父子节点,不能拿发现父子节点的下标其实有规律

    左孩子下标=父亲下标*2+1    因为左孩子的下标总是奇数!!

    右孩子下标=父亲下标*2+2    因为右孩子的下标总是偶数!!

    父亲下标=(孩子(左/右)-1)/2


    3. 堆的实现(重点重点)

     既然我们这么了解堆的结构,那么上手吧

    这个实现和顺序表就很相似

    还是定义一个结构体,也就是表示一个节点

    a表示结构体指针,可以用指针的方式访问数组

    size是数组中元素的个数

    capacity是容量,当容量和元素个数相等的时候要考虑扩容(老生常谈了)

    数据类型重定义可以方便以后存放不同类型的数据

    1. typedef int HPDataType;
    2. typedef struct Heap
    3. {
    4. HPDataType* a;
    5. int size;
    6. int capacity;
    7. }Heap;
    8. //打印
    9. void HeapPrint(Heap* hp);
    10. //交换
    11. void Swap(HPDataType* a, HPDataType* b);
    12. // 堆的构建
    13. void HeapCreate(Heap* hp, HPDataType* a, int n);
    14. //向上调整
    15. void AdjustUp(HPDataType* a, int chirld);
    16. //向下调整
    17. AdjustDown(HPDataType* a, int n, int parent);
    18. //初始化
    19. void HeapInit(Heap* hp);
    20. // 堆的销毁
    21. void HeapDestory(Heap* hp);
    22. // 堆的插入
    23. void HeapPush(Heap* hp, HPDataType x);
    24. // 堆的删除
    25. void HeapPop(Heap* hp);
    26. // 取堆顶的数据
    27. HPDataType HeapTop(Heap* hp);
    28. // 堆的数据个数
    29. int HeapSize(Heap* hp);
    30. // 堆的判空
    31. int HeapEmpty(Heap* hp);
    32. void PrintTopK(int* a, int n, int k);

    主要的几个功能就是(按我的代码顺序)


    打印(为了方便调试看结果的没什么含金量,大家自己就能完成)

    1. //打印
    2. void HeapPrint(Heap* hp)
    3. {
    4. assert(hp);
    5. for (int i=0;isize;i++)
    6. {
    7. printf("%d ", hp->a[i]);
    8. }
    9. printf("\n");
    10. }

    堆的初始化(指针a开不开空间都可以)

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

    堆的构建:给你一个杂乱的数组,把他变成堆的形式(以大堆为例)

    首先肯定是把所给数组中的数据都拷贝到结构体变量hp的指针a指向的数组里面啊

    由于我们在初始化指针a的时候是没有开空间的(开不开都行,我写的是没开的版本,开了的话要先考虑扩容问题)

    想到memcpy

    拷贝好之后就要考虑是不是符合我大堆的定义

    也就是考虑

     这三个子树是不是符合父节点比子节点大的要求,可以向下调整的方法,把父节点下标传给一个向下调整的函数作为家长节点下标,把容量和指针他也传过去

    向下调整函数

    1. AdjustDown(HPDataType* a, int n, int parent)
    2. {
    3. assert(a);
    4. int child = 2 * parent + 1;//假设是左孩子,并且左孩子是最大的孩子
    5. while (child
    6. {
    7. if (child+11] > a[child] )
    8. {
    9. child = child + 1;
    10. }
    11. //此时的child已经是最大的孩子
    12. if (a[parent] < a[child])
    13. {
    14. Swap(&a[parent], &a[child]);
    15. parent=child;
    16. child= 2 * parent + 1;
    17. }
    18. else
    19. {
    20. break;
    21. }
    22. }
    23. }

    其中还复用了一个交换值的函数,由于后面交换的地方较多,最好还是交给函数去做

    1. //交换
    2. void Swap(HPDataType* a, HPDataType* b)
    3. {
    4. HPDataType tmp = *a;
    5. *a = *b;
    6. *b = tmp;
    7. }

    我们发现我标记的1 2 3其实是连续的下标!! 

    所以堆创建函数

    1. // 堆的构建
    2. void HeapCreate(Heap* hp, HPDataType* a, int n)
    3. {
    4. assert(hp);
    5. hp->a = (HPDataType*)malloc(sizeof(HPDataType) * n);
    6. if (hp->a == NULL)
    7. {
    8. perror("HeapCreate fail");
    9. exit(-1);
    10. }
    11. memcpy(hp->a, a, n*sizeof(HPDataType));
    12. hp->size = hp->capacity=n;
    13. for (int i = (n - 1 - 1) / 2; i >=0; i--)
    14. {
    15. AdjustDown(hp->a, n, i);
    16. }
    17. }


    有创建肯定要有销毁,不然会内存泄漏

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

    判断是不是空堆

    1. // 堆的判空
    2. int HeapEmpty(Heap* hp)
    3. {
    4. assert(hp);
    5. return hp->size == 0;
    6. }

    堆的插入(尾插)

    把一个接点放进去还要考虑这个节点可能会比他的父节点还大,所以他就要开始篡改族谱了...

    和爸爸,爷爷...都比较一下,谁更大谁就在在上面,所以要一个函数:向上调整(还是以大堆为例)

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

     所以堆插就是

    1. // 堆的插入,尾插
    2. void HeapPush(Heap* hp, HPDataType x)
    3. {
    4. assert(hp);
    5. if (hp->capacity == hp->size)
    6. {
    7. //需要扩容
    8. int newcapacity = hp->capacity == 0 ? 4 : hp->capacity * 2;
    9. HPDataType* newnode = (HPDataType*)realloc(hp->a,sizeof(HPDataType) * newcapacity);
    10. if (newnode == NULL)
    11. {
    12. perror("malloc fail");
    13. exit(-1);
    14. }
    15. hp->a = newnode;
    16. hp->capacity = newcapacity;
    17. }
    18. hp->a[hp->size] = x;
    19. hp->size++;
    20. //向上调整
    21. AdjustUp(hp->a, hp->size - 1);
    22. }

    堆的删除

    不能简单覆盖,这样你千辛万苦的父子关系全乱套了

    所以先把最后一个节点和根换一下,然后尾删直接把size--

    之后就该考虑一下向下调整的问题

    1. // 堆的删除
    2. void HeapPop(Heap* 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. }

    取堆顶的数据

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

    堆数据个数

    1. // 堆的数据个数
    2. int HeapSize(Heap* hp)
    3. {
    4. assert(hp);
    5. return hp->size;
    6. }

    找出前k个最大 

    1. //找出前k个最大
    2. void PrintTopK(int* a, int n, int k)
    3. {
    4. assert(a);
    5. Heap hp;
    6. while (k--)
    7. {
    8. HeapCreate(&hp, a, k);
    9. }
    10. }

    最后最后完整的实现在下面

    1. include "Heap.h"
    2. //打印
    3. void HeapPrint(Heap* hp)
    4. {
    5. assert(hp);
    6. for (int i=0;isize;i++)
    7. {
    8. printf("%d ", hp->a[i]);
    9. }
    10. printf("\n");
    11. }
    12. //交换
    13. void Swap(HPDataType* a, HPDataType* b)
    14. {
    15. HPDataType tmp = *a;
    16. *a = *b;
    17. *b = tmp;
    18. }
    19. // 堆的判空
    20. int HeapEmpty(Heap* hp)
    21. {
    22. assert(hp);
    23. return hp->size == 0;
    24. }
    25. // 堆的构建
    26. void HeapCreate(Heap* hp, HPDataType* a, int n)
    27. {
    28. assert(hp);
    29. hp->a = (HPDataType*)malloc(sizeof(HPDataType) * n);
    30. if (hp->a == NULL)
    31. {
    32. perror("HeapCreate fail");
    33. exit(-1);
    34. }
    35. memcpy(hp->a, a, n*sizeof(HPDataType));
    36. hp->size = hp->capacity=n;
    37. for (int i = (n - 1 - 1) / 2; i >=0; i--)
    38. {
    39. AdjustDown(hp->a, n, i);
    40. }
    41. }
    42. //初始化
    43. void HeapInit(Heap* hp)
    44. {
    45. assert(hp);
    46. hp->a = NULL;
    47. hp->capacity = hp->size = 0;
    48. }
    49. // 堆的销毁
    50. void HeapDestory(Heap* hp)
    51. {
    52. assert(hp);
    53. free(hp->a);
    54. hp->a = NULL;
    55. hp->capacity = hp->size = 0;
    56. }
    57. //向上调整,大堆
    58. void AdjustUp(HPDataType* a, int child)
    59. {
    60. assert(a);
    61. int parent = (child - 1) / 2;
    62. while (child>=0)
    63. {
    64. if (a[child] > a[parent])
    65. {
    66. Swap(&a[child],& a[parent]);
    67. child = parent;
    68. parent= (child - 1) / 2;
    69. }
    70. else
    71. {
    72. break;
    73. }
    74. }
    75. }
    76. // 堆的插入,尾插
    77. void HeapPush(Heap* hp, HPDataType x)
    78. {
    79. assert(hp);
    80. if (hp->capacity == hp->size)
    81. {
    82. //需要扩容
    83. int newcapacity = hp->capacity == 0 ? 4 : hp->capacity * 2;
    84. HPDataType* newnode = (HPDataType*)realloc(hp->a,sizeof(HPDataType) * newcapacity);
    85. if (newnode == NULL)
    86. {
    87. perror("malloc fail");
    88. exit(-1);
    89. }
    90. hp->a = newnode;
    91. hp->capacity = newcapacity;
    92. }
    93. hp->a[hp->size] = x;
    94. hp->size++;
    95. //向上调整
    96. AdjustUp(hp->a, hp->size - 1);
    97. }
    98. //向下调整
    99. AdjustDown(HPDataType* a, int n, int parent)
    100. {
    101. assert(a);
    102. int child = 2 * parent + 1;//假设是左孩子,并且左孩子是最大的孩子
    103. while (child
    104. {
    105. if (child+11] > a[child] )
    106. {
    107. child = child + 1;
    108. }
    109. //此时的child已经是最大的孩子
    110. if (a[parent] < a[child])
    111. {
    112. Swap(&a[parent], &a[child]);
    113. parent=child;
    114. child= 2 * parent + 1;
    115. }
    116. else
    117. {
    118. break;
    119. }
    120. }
    121. }
    122. // 堆的删除
    123. void HeapPop(Heap* hp)
    124. {
    125. assert(hp);
    126. assert(!HeapEmpty(hp));
    127. Swap(&hp->a[0], &hp->a[hp->size - 1]);
    128. hp->size--;
    129. AdjustDown(hp->a, hp->size, 0);
    130. }
    131. // 取堆顶的数据
    132. HPDataType HeapTop(Heap* hp)
    133. {
    134. assert(hp);
    135. return hp->a[0];
    136. }
    137. // 堆的数据个数
    138. int HeapSize(Heap* hp)
    139. {
    140. assert(hp);
    141. return hp->size;
    142. }
    143. //找出前k个最大
    144. void PrintTopK(int* a, int n, int k)
    145. {
    146. assert(a);
    147. Heap hp;
    148. while (k--)
    149. {
    150. HeapCreate(&hp, a, k);
    151. }
    152. }


    学会了吗??不懂的留言~

  • 相关阅读:
    【智能座舱】- 汽车产业的变革,电动化是上半场,而智能化则是下半场
    搭建伪分布式Hadoop
    UML和模式应用~迭代、进化和敏捷
    嵌入式-C语言关系运算符
    Spring Security 自定义资源服务器实践
    STM32大小端模式测试
    Vscode - 修改插件安装目录
    【树莓派不吃灰】命令篇④ Linux 常用命令学习
    自定义MVC增删改查
    JavaScript浏览器(兼容、调试)
  • 原文地址:https://blog.csdn.net/weixin_71138261/article/details/127927562