经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 其他 » 计算机原理 » 查看文章
顺序与链式二叉树的原理与实现(万字)
来源:cnblogs  作者:HJfjfK  时间:2024/8/7 15:08:10  对本文有异议

一、树概念及结构

树的概念

树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。

  • 有一个特殊的结点,称为根结点,根节点没有前驱结点

  • 除根节点外,其余结点被分成M(M>0)个互不相交的集合T1、T2、……、Tm,其中每一个集合Ti(1<= i
    <= m)又是一棵结构与树类似的子树。每棵子树的根结点有且只有一个前驱,可以有0个或多个后继

因此,树是递归定义的。

注意:树形结构中,子树之间不能有交集,否则就不是树形结构

image-20240805211042676

树的相关概念

image-20240805211021150

  • 节点的度:一个节点含有的子树的个数称为该节点的度; 如上图:A的为6
  • 叶节点或终端节点:度为0的节点称为叶节点; 如上图:B、C、H、I...等节点为叶节点
  • 非终端节点或分支节点:度不为0的节点; 如上图:D、E、F、G...等节点为分支节点
  • 双亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点; 如上图:A是B的父节点
  • 孩子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点; 如上图:B是A的孩子节点
  • 兄弟节点:具有相同父节点的节点互称为兄弟节点; 如上图:B、C是兄弟节点
  • 树的度:一棵树中,最大的节点的度称为树的度; 如上图:树的度为6
  • 节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推;
  • 树的高度或深度:树中节点的最大层次; 如上图:树的高度为4
  • 堂兄弟节点:双亲在同一层的节点互为堂兄弟;如上图:H、I互为兄弟节点
  • 节点的祖先:从根到该节点所经分支上的所有节点;如上图:A是所有节点的祖先
  • 子孙:以某节点为根的子树中任一节点都称为该节点的子孙。如上图:所有节点都是A的子孙
  • 森林:由m(m>0)棵互不相交的树的集合称为森林;

树的表示

树结构相对线性表就比较复杂了,要存储表示起来就比较麻烦了,既然保存值域,也要保存结点和结点之间
的关系,实际中树有很多种表示方式如:双亲表示法,孩子表示法、孩子双亲表示法以及孩子兄弟表示法
等。简单的了解其中最常用的孩子兄弟表示法。

  1. typedef int DataType;
  2. struct Node
  3. {
  4. struct Node* _firstChild1; // 第一个孩子结点
  5. struct Node* _pNextBrother; // 指向其下一个兄弟结点
  6. DataType _data; // 结点中的数据域
  7. };

image-20240805211149246

树在实际中的运用(表示文件系统的目录树结构)

image-20240805211223360

二、二叉树概念及结构

概念

一棵二叉树是结点的一个有限集合,该集合:

  1. 或者为空
  2. 由一个根节点加上两棵别称为左子树和右子树的二叉树组成

image-20240805211342983

从上图可以看出:

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

对于任意的二叉树都是由以下几种情况复合而成的:

image-20240805211453621

特殊的二叉树

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

image-20240805211526297

二叉树的性质

  1. 若规定根节点的层数为1,则一棵非空二叉树的第i层上最多有 个结点.

  2. 若规定根节点的层数为1,则深度为h的二叉树的最大结点数是 .

  3. 对任何一棵二叉树, 如果度为0其叶结点个数为 , 度为2的分支结点个数为 ,则有 = +1

  4. 若规定根节点的层数为1,具有n个结点的满二叉树的深度,h= . (ps: 是log以2
    为底,n+1为对数)

  5. 对于具有n个结点的完全二叉树,如果按照从上至下从左至右的数组顺序对所有节点从0开始编号,则对
    于序号为i的结点有:

    1. 若i>0,i位置节点的双亲序号:(i-1)/2;i=0,i为根节点编号,无双亲节点

    2. 若2i+1<n,左孩子序号:2i+1,2i+1>=n否则无左孩子

    3. 若2i+2<n,右孩子序号:2i+2,2i+2>=n否则无右孩子

二叉树的存储结构

二叉树一般可以使用两种结构存储,一种顺序结构,一种链式结构。

  1. 顺序存储
    顺序结构存储就是使用数组来存储,一般使用数组只适合表示完全二叉树,因为不是完全二叉树会有空
    间的浪费。而现实中使用中只有堆才会使用数组来存储,关于堆我们后面的章节会专门讲解。二叉树顺
    序存储在物理上是一个数组,在逻辑上是一颗二叉树。

image-20240805211657030

链式存储
二叉树的链式存储结构是指,用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。 通常的方法是
链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所
在的链结点的存储地址 。链式结构又分为二叉链和三叉链,当前简单二叉树一般都是二叉链,红黑树等会用到三叉链。

image-20240805211747308

  1. typedef int BTDataType;
  2. // 二叉链
  3. struct BinaryTreeNode
  4. {
  5. struct BinTreeNode* _pLeft; // 指向当前节点左孩子
  6. struct BinTreeNode* _pRight; // 指向当前节点右孩子
  7. BTDataType _data; // 当前节点值域
  8. }
  9. // 三叉链
  10. struct BinaryTreeNode
  11. {
  12. struct BinTreeNode* _pParent; // 指向当前节点的双亲
  13. struct BinTreeNode* _pLeft; // 指向当前节点左孩子
  14. struct BinTreeNode* _pRight; // 指向当前节点右孩子
  15. BTDataType _data; // 当前节点值域

三、二叉树的顺序结构及实现

二叉树的顺序结构

普通的二叉树是不适合用数组来存储的,因为可能会存在大量的空间浪费。而完全二叉树更适合使用顺序结
构存储。现实中我们通常把堆(一种二叉树)使用顺序结构的数组来存储,需要注意的是这里的堆和操作系统
虚拟进程地址空间中的堆是两回事,一个是数据结构,一个是操作系统中管理内存的一块区域分段。

image-20240805212045854

堆的概念及结构

如果有一个关键码的集合K = { , , ,…, },把它的所有元素按完全二叉树的顺序存储方式存储
在一个一维数组中,并满足: <= 且 <= ( >= 且 >= ) i = 0,1,
2…,则称为小堆(或大堆)。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。
堆的性质:

  • 堆中某个节点的值总是不大于或不小于其父节点的值;
  • 堆总是一棵完全二叉树。

image-20240805212116179

堆的实现

堆向下调整算法

给出一个数组,逻辑上看做一颗完全二叉树。我们通过从根节点开始的向下调整算法可以把它调整
成一个小堆。向下调整算法有一个前提:左右子树必须是一个堆,才能调整。

  1. int array[] = {27,15,19,18,28,34,65,49,25,37};

image-20240805212201348

堆的创建

给出一个数组,这个数组逻辑上可以看做一颗完全二叉树,但是还不是一个堆,现在我们通过算
法,把它构建成一个堆。根节点左右子树不是堆,我们怎么调整呢?这里我们从倒数的第一个非叶子节点的
子树开始调整,一直调整到根节点的树,就可以调整成堆。

  1. int a[] = {1,5,3,8,7,6};

image-20240805212305378

建堆时间复杂度

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

image-20240805212337869

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

堆的插入

先插入一个10到数组的尾上,再进行向上调整算法,直到满足堆。

image-20240805212407088

堆的删除

删除堆是删除堆顶的数据,将堆顶的数据根最后一个数据一换,然后删除数组最后一个数据,再进行向下调
整算法。

image-20240805212422731

堆的代码实现

Heap.h
  1. #define _CRT_SECURE_NO_WARNINGS 1
  2. #pragma once
  3. #include<stdio.h>
  4. #include<assert.h>
  5. #include<stdlib.h>
  6. #include<stdbool.h>
  7. #include<string.h>
  8. typedef int HeapDataType;
  9. typedef struct Heap
  10. {
  11. HeapDataType *a;
  12. int size;
  13. int capacity;
  14. //顺序表思想
  15. }Heap;
  16. void HeapPrint(Heap *ps);
  17. void HeapInit(Heap *ps);
  18. void HeapDestroy(Heap *ps);
  19. void HeapPush(Heap *ps,HeapDataType x);
  20. void HeapPop(Heap*ps);
  21. int HeapSize(Heap *ps);
  22. bool HeapEmpty(Heap *ps);
  23. //接受一个堆,数组, 数组大小。建一个堆
  24. void HeapCreate(Heap*ps, HeapDataType *a, int size);
  25. void HeapSort(HeapDataType *a, int size);
  26. void AdjustDown(HeapDataType *a, int size, int parent);
  27. void AdjustUp(HeapDataType *a, int child);
  28. void Swap(HeapDataType *p1, HeapDataType *p2);
Heap.c
  1. #include"Heap.h"
  2. void Swap(HeapDataType *p1, HeapDataType *p2)
  3. {
  4. HeapDataType tmp = 0;
  5. tmp = *p1;
  6. *p1 = *p2;
  7. *p2 = tmp;
  8. }
  9. //向上调整算法:从0插入建堆 以及 在堆的前提下插入 //不适合直接建堆(把数组从1开始向下建堆)
  10. void AdjustUp(HeapDataType *a,int child)
  11. {
  12. assert(a);
  13. //小堆改成a[child] < a[parent]
  14. int parent = (child - 1) / 2;
  15. while (parent >= 0 && child > 0 && a[child] > a[parent]) //把边界和条件全丢进去
  16. { //这个没问题,没有重复出现两个相同的if。可以把条件一起放在while里
  17. Swap(&a[child], &a[parent]);
  18. child = parent;
  19. parent = (child - 1) / 2;
  20. }
  21. }
  22. //向下调整算法:前提:左右子树是堆 //适合建堆:递归从最小父亲开始(虽然可以从最小孩子)
  23. void AdjustDown(HeapDataType *a,int size, int parent) //小堆
  24. {
  25. ////比较两个孩子中最大/小的进行交换
  26. //int child = parent * 2 + 1;
  27. //if (child < size && parent < size && a[child] < a[child + 1]) //// ------------这种逻辑是不对的,一个在while外面,一个在while里面,重复,当size == child的时候卡掉一个
  28. //{ //如果出现,务必消除,把while和if条件拆分出来
  29. // child = child + 1;
  30. //}
  31. ////调整到叶子结束 //&&先判断左边
  32. //while (child < size && parent < size && a[parent] < a[child]) //child 可能越界,先保证child<size
  33. //{
  34. // Swap(&a[parent], &a[child]);
  35. // parent = child;
  36. // child = parent * 2 + 1;
  37. // if (child < size && parent < size && a[child] < a[child + 1])//-----------------------------------------------------------------------
  38. // {
  39. // child = child + 1;
  40. // }
  41. //}
  42. int child = parent * 2 + 1;
  43. while (child < size)
  44. {
  45. if (child + 1 < size &&a[child] > a[child + 1])
  46. {
  47. child++;
  48. }
  49. if (a[child] < a[parent])//小堆大于最小孩子就交换,
  50. { //大堆小于最大孩子就交换
  51. Swap(&a[child], &a[parent]);
  52. parent = child;
  53. child = parent * 2 + 1;
  54. }
  55. else
  56. {
  57. break;
  58. }
  59. }
  60. }
  61. //-------------------------------------------------------------------------
  62. void HeapPrint(Heap *ps)
  63. {
  64. assert(ps);
  65. for (int i = 0; i < ps->size; i++)
  66. {
  67. printf("%d ", ps->a[i]);
  68. }
  69. }
  70. void HeapInit(Heap *ps)
  71. {
  72. assert(ps);
  73. ps->a = NULL;
  74. ps->size = 0;
  75. ps->capacity = 0;
  76. }
  77. //接受一个堆,一个数组,一个数组大小
  78. void HeapCreate(Heap *ps, HeapDataType *a, int size)
  79. {
  80. assert(ps);
  81. //开辟堆中数组空间
  82. ps->a = (HeapDataType *)malloc(size * sizeof(HeapDataType));
  83. if (ps->a == NULL)
  84. {
  85. perror("malloc fail");
  86. exit(1);
  87. }
  88. memcpy(ps->a, a, size *sizeof(HeapDataType));//拷贝数组过去
  89. ps->size = size;
  90. ps->capacity = 2 * size;
  91. //建堆
  92. //从最小父亲开始向下调整
  93. //循环,直到父亲/孩子不为0
  94. int parent = (size - 1 - 1) / 2;
  95. while (parent >= 0)
  96. {
  97. AdjustDown(ps->a, ps->size, parent);
  98. parent--;
  99. }
  100. }
  101. void HeapDestroy(Heap *ps)
  102. {
  103. assert(ps);
  104. free(ps->a);
  105. ps->capacity = ps->size = 0;
  106. }
  107. void HeapPush(Heap *ps,HeapDataType x)
  108. {
  109. assert(ps);
  110. if (ps->size == ps->capacity)//开辟/扩容
  111. {
  112. int newCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
  113. HeapDataType *tmp = (HeapDataType *)realloc(ps->a, newCapacity * sizeof(HeapDataType));
  114. if (tmp == NULL)
  115. {
  116. perror("realloc fail!");
  117. exit(-1);
  118. }
  119. ps->a = tmp;
  120. ps->capacity = newCapacity;
  121. }
  122. ps->a[ps->size] = x;
  123. ps->size++;
  124. AdjustUp(ps->a, ps->size - 1);
  125. }
  126. void HeapPop(Heap *ps)
  127. {
  128. assert(ps);
  129. assert(ps->size > 0); // 保证堆中存在数据
  130. Swap(&ps->a[ps->size-1], &ps->a[0]);
  131. ps->size--;
  132. AdjustDown(ps->a,ps->size, 0);
  133. }
  134. //选数相当快,只需要logN次
  135. HeapDataType HeapTop(Heap *ps)
  136. {
  137. assert(ps);
  138. assert(ps->size > 0);
  139. return ps->a[0];
  140. }
  141. int HeapSize(Heap *ps)
  142. {
  143. assert(ps);
  144. return ps->size;
  145. }
  146. bool HeapEmpty(Heap *ps)
  147. {
  148. assert(ps);
  149. return ps->size == 0;
  150. }

堆的应用

堆排序

堆排序即利用堆的思想来进行排序,总共分为两个步骤:

  1. 建堆
    升序:建大堆
    降序:建小堆
  2. 利用堆删除思想来进行排序

建堆和堆删除中都用到了向下调整,因此掌握了向下调整,就可以完成堆排序。

堆排序代码实现

HeapSort.c
  1. #include"Heap.h"
  2. void HeapSort(HeapDataType *a, int size)
  3. {
  4. assert(a);
  5. // ---- 向下调整算法建堆 ----
  6. //时间复杂度O(n)
  7. int parent = (size - 1 - 1) / 2;
  8. while (parent >= 0)
  9. {
  10. AdjustDown(a, size, parent);
  11. parent--;
  12. }
  13. // ---- 选数 ----
  14. //时间复杂度O(n*logN),好像是说n个数都要高度次调整
  15. //int end = size - 1;//
  16. while (size>0)
  17. {
  18. Swap(&a[0], &a[size-1]);//选数,选完就少一个
  19. size--;
  20. AdjustDown(a, size, 0);//
  21. }
  22. }
时间复杂度分析
  1. /*
  2. 向下调整算法建堆时间复杂度计算
  3. 假设满二叉树树高度h
  4. 各层的节点数为
  5. 第一层 2 ^ 0 ------向下调整h-1次
  6. 第二层 2 ^ 1 ------向下调整h-2次
  7. 第三层 2 ^ 2 ------向下调整h-3次
  8. ... ...
  9. 第h - 1层 2 ^ (h - 2) ------向下调整1次
  10. 第h层 2 ^ (h - 1)
  11. 向下调整算法建堆是从最小父亲开始,即第h-1层的最后一个节点 parent = (size-1-1)/2
  12. 最坏情况下所有节点需要执行的次数为
  13. f(h) = 2^(h-2)*1 + 2^(h-3)*2 + ... + 2^1*(h-2) + 2^0*(h-1) 错位相减
  14. 2*f(h) = 2^(h-1)*1 + 2^(h-2)*2 + ... + 2^2*(h-2) + 2^1*(h-1)
  15. 作差、合并得f(h) = 2^h -h-1
  16. 其中 满二叉树节点数N = 2^h-1,即h = log(N+1) 代入得
  17. f(N) = N - 1 - log(N+1) , 舍去logN(数量级)
  18. 所以O(n) = n
  19. -------------------------------------------------------------------------------
  20. 而向上调整算法建堆时间复杂度比较吃亏,见图
  21. 假设满二叉树树高度h
  22. 各层的节点数为
  23. 第一层 2 ^ 0
  24. 第二层 2 ^ 1 ------向上调整1次
  25. 第三层 2 ^ 2 ------向上调整2次
  26. ... ...
  27. 第h - 1层 2 ^ (h - 2) ------向上调整h-2次
  28. 第h层 2 ^ (h - 1) ------向上调整h-1次
  29. 计算方法还是错位相减,由图显然可发现向上调整算法执行次数数量级明显提高
  30. 不再计算
  31. O(n) = n*logN
  32. 总结:向下调整算法跳过最下层最多节点层,且从下层开始节点多执行次数少。快
  33. 向上调整算法从上开始从节点少往节点多执行次数成倍增加,前面的加起来都没最后一层多,慢
  34. */
  35. //建堆---选数,放到最后,排除掉,重新选数
  36. //合计时间复杂度 O(n + n*logN) = O(n*logN)

TOP-K问题

TOP-K问题:即求数据结合中前K个最大的元素或者最小的元素,一般情况下数据量都比较大。
比如:专业前10名、世界500强、富豪榜、游戏中前100的活跃玩家等。
对于Top-K问题,能想到的最简单直接的方式就是排序,但是:如果数据量非常大,排序就不太可取了(可能
数据都不能一下子全部加载到内存中)。最佳的方式就是用堆来解决,基本思路如下:

  1. 用数据集合中前K个元素来建堆
    前k个最大的元素,则建小堆
    前k个最小的元素,则建大堆
  2. 用剩余的N-K个元素依次与堆顶元素来比较,不满足则替换堆顶元素

将剩余N-K个元素依次与堆顶元素比完之后,堆中剩余的K个元素就是所求的前K个最小或者最大的元素。

代码示例
TopK.c
  1. #define _CRT_SECURE_NO_WARNINGS 1
  2. #include"Heap.h"
  3. #include<time.h>
  4. //取前K个最大数
  5. #define K 10
  6. void TopK()
  7. {
  8. int n = 10000;//int n = 10000;//数据量
  9. int randMax = 10000;//int randMax = 10000;
  10. int minHeap[K] ;
  11. //int K = 100;
  12. memset(minHeap, 0, sizeof(minHeap));//初始化数组,且用sizeof(arr)---不用取地址
  13. srand((size_t)time(0));//不用加size_t也行,NULL实际上会被time强制转换成(void)0
  14. //TopK问题思路s
  15. //建一个大小为K的堆
  16. FILE *fin = fopen("data.txt", "w");
  17. if (fin == NULL)
  18. {
  19. perror("fopen fail");
  20. return;
  21. }
  22. //随机写入数据,rand , 使用fprintf("fout","%d ",)
  23. int randK = K;//int randK = K;
  24. for (int i = 0; i < n ;i++)
  25. {
  26. int val = rand() % randMax; //最大值不超过randMax
  27. if (val % 3 == 0 && randK-- > 0)
  28. {
  29. val = randMax + randK; //插入自定义最大值
  30. }
  31. fprintf(fin, "%d ", val);
  32. }
  33. //如果插入的最大值不够K个,就在末尾补上 --------出现的概率很小,忽略
  34. while (randK-- > 0)
  35. {
  36. fprintf(fin, "%d ", randMax+randK);//注意分隔符,分隔给人看的,使用空格或\n方便用fscanf默认的分隔符识别
  37. }
  38. fprintf(fin, "%d ", 99999); //--- 验证能否插入且插入到最后
  39. fclose(fin);
  40. //for (int i = 0 ; i < K; i++)
  41. //{
  42. // AdjustDown(minHeap, K, 0);
  43. //}
  44. FILE *fout = fopen("data.txt", "r");//重置文件指针
  45. if (fout == NULL)
  46. {
  47. perror("fopen fail");
  48. return;
  49. }
  50. int tmp;
  51. while (fscanf(fout, "%d", &tmp) != EOF) //注意,由于设置了scanf的默认分隔符空格或\n,随意不用在格式%d后带分隔符识别
  52. {
  53. //从文件中提取数据
  54. //提一个就和堆顶比较一次
  55. //随着文件指针走下去会比较完文件所有数据
  56. //ASCII文件使用fscanf("流","格式","dest");
  57. if (tmp >minHeap[0])
  58. {
  59. minHeap[0] = tmp;
  60. }
  61. AdjustDown(minHeap, K, 0);//换一次调堆一次
  62. }
  63. fclose(fout);
  64. for (int i = 0; i < K; i++)//打印数组
  65. {
  66. printf("%d ", minHeap[i]);
  67. }
  68. }

四、二叉树链式结构的代码实现

二叉树的遍历

前序、中序以及后序遍历
学习二叉树结构,最简单的方式就是遍历。所谓二叉树遍历(Traversal)是按照某种特定的规则,依次对二叉
树中的节点进行相应的操作,并且每个节点只操作一次。访问结点所做的操作依赖于具体的应用问题。 遍历
是二叉树上最重要的运算之一,也是二叉树上进行其它运算的基础。

按照规则,二叉树的遍历有:前序/中序/后序的递归结构遍历:

  1. 前序遍历(Preorder Traversal 亦称先序遍历)——访问根结点的操作发生在遍历其左右子树之前。
  2. 中序遍历(Inorder Traversal)——访问根结点的操作发生在遍历其左右子树之中(间)。
  3. 后序遍历(Postorder Traversal)——访问根结点的操作发生在遍历其左右子树之后。

由于被访问的结点必是某子树的根,所以N(Node)、L(Left subtree)和R(Right subtree)又可解释为
根、根的左子树和根的右子树。NLR、LNR和LRN分别又称为先根遍历、中根遍历和后根遍历。

层序遍历

层序遍历:除了先序遍历、中序遍历、后序遍历外,还可以对二叉树进行层序遍历。设二叉树的根节点所在
层数为1,层序遍历就是从所在二叉树的根节点出发,首先访问第一层的树根节点,然后从左到右访问第2层
上的节点,接着是第三层的节点,以此类推,自上而下,自左至右逐层访问树的结点的过程就是层序遍历。

image-20240805213406908

代码实现

Tree.h

  1. #define _CRT_SECURE_NO_WARNINGS 1
  2. #pragma once
  3. #include<stdio.h>
  4. #include<stdlib.h>
  5. #include<stdbool.h>
  6. //树的遍历
  7. //根据不同的递归遍历方法 访问 每个树(结构体)的data和左右孩子 进行操作。
  8. //每次递归到一颗新树(每个节点都是一颗树),都要先判断是否空树 ---- 以防止野指针错误
  9. //---- 创建二叉树 ----
  10. typedef int BTDataType;
  11. typedef struct BinaryTreeNode
  12. {
  13. BTDataType data;
  14. struct BinaryTreeNode *left; //left sub tree
  15. struct BinaryTreeNode *right; //right sub tree
  16. }BTNode;
  17. /*
  18. node->data = x;
  19. node->left = NULL;
  20. node->right = NULL;
  21. */
  22. BTNode* BuyBTNode(BTDataType x);//构建值为x的节点
  23. /*
  24. printf("%d", root->data);
  25. PrevOrder(root->left);
  26. PrevOrder(root->right);
  27. */
  28. void PrevOrder(BTNode *root);//先根遍历
  29. /*
  30. PrevOrder(root->left);s
  31. printf("%d", root->data);
  32. PrevOrder(root->right);
  33. */
  34. void InOrder(BTNode *root); //中根遍历
  35. /*
  36. PrevOrder(root->left);
  37. PrevOrder(root->right);
  38. printf("%d", root->data);
  39. */
  40. void PostOrder(BTNode *root); //后根遍历
  41. void LevelOrder(BTNode *root); //层序遍历
  42. /*
  43. return BTSize(root->left) + BTSize(root->right) + 1;
  44. */
  45. int BTSize(BTNode* root); //求二叉树节点个数
  46. /**/
  47. int BTLeafSize(BTNode* root); //求二叉树叶子节点个数
  48. int BTHeight(BTNode *root);//求二叉树高度
  49. int BTLevelKSize(BTNode *root, int k);//计算二叉树第k层节点
  50. BTNode *BTFind(BTNode* root, BTDataType x); //二叉树查找某节点地址
  51. void BTDestroy(BTNode*root); //二叉树销毁
  52. BTNode * rebuildBinaryTree(BTDataType* str, int *pi); //构建一颗二叉树, pi为数组的下标地址
  53. bool isBTComplete(BTNode *root);

构建二叉树与基本功能

tree.c

  1. #include"Tree.h"
  2. /*创建一个值为x的二叉树节点*/
  3. BTNode* BuyBTNode(BTDataType x) //创建值为x的节点
  4. {
  5. BTNode *node = (BTNode*)malloc(sizeof(BTNode));//使用指针要指向实际空间
  6. if (!node)
  7. {
  8. perror("malloc fail!");
  9. exit(1);
  10. }
  11. node->data = x;
  12. node->left = NULL;
  13. node->right = NULL;
  14. return node;
  15. }
  16. /*前序遍历*/
  17. void PrevOrder(BTNode *root) //previous order顺序 NLR:前序遍历(Preorder Traversal 亦称
  18. {
  19. if (!root )
  20. {
  21. printf("NULL ");
  22. return;
  23. }
  24. printf("%d ", root->data);
  25. PrevOrder(root->left);
  26. PrevOrder(root->right);
  27. }
  28. /*中序遍历*/
  29. void InOrder(BTNode *root) // ..中
  30. {
  31. if (!root )
  32. {
  33. printf("NULL ");
  34. return;
  35. }
  36. InOrder(root->left);
  37. printf("%d ", root->data);
  38. InOrder(root->right);
  39. }
  40. /*后续遍历*/
  41. void PostOrder(BTNode *root) // post- 前缀 ..之后 ..后面 ..后
  42. {
  43. if (!root )
  44. {
  45. printf("NULL ");
  46. return;
  47. }
  48. PostOrder(root->left);
  49. PostOrder(root->right);
  50. printf("%d ", root->data);
  51. }
  52. /*求二叉树所有节点个数*/
  53. int BTSize(BTNode* root)
  54. {
  55. /*
  56. if (root == NULL)
  57. {
  58. return 0;
  59. }
  60. //思想:左子树 和 右子树 和 自己 的节点数 的和
  61. return BTSize(root->left) + BTSize(root->right) + 1;
  62. */
  63. //两个重复语法可以合并
  64. return !root ? 0 : BTSize(root->left) + BTSize(root->right) + 1;
  65. }
  66. /*求二叉树叶子节点个数*/
  67. int BTLeafSize(BTNode* root)
  68. {
  69. if (!root)
  70. {
  71. return 0;
  72. }
  73. //思想:左右节点为空即为叶子
  74. if(!root->left && !root->right)
  75. {
  76. return 1;
  77. }
  78. return BTLeafSize(root->left) + BTLeafSize(root->right);
  79. }
  80. /*求二叉树高度*/
  81. int BTHeight(BTNode *root)
  82. {
  83. if (!root)
  84. {
  85. return 0;
  86. }
  87. //思路:比较左右子树高度,不要小的 只有关系运算符能够实现:比较两边选一边舍弃另一边
  88. //方法一:
  89. //return BTHeight(root->left) > BTHeight(root->right)
  90. // ? BTHeight(root->left) + 1
  91. // : BTHeight(root->right) + 1;
  92. /*
  93. 点评;问题很大,递归之后栈帧会销毁,数据不再保存,
  94. 每次递归都会计算两次子树高度,每个子树高度计算都是指数级*/
  95. //优化:方法二:
  96. int leftHeight = BTHeight(root->left);
  97. int rightHeight = BTHeight(root->right);
  98. return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
  99. /* 点评:通过记下当前栈帧数据,避免重复计算*/
  100. }
  101. /*求二叉树第k层节点个数*/
  102. int BTLevelKSize(BTNode *root , int k)
  103. {
  104. /*遇到第k层或空怎么做*/
  105. if (!root)
  106. {
  107. return 0;
  108. }
  109. if (k == 1) //最后一层,第k层
  110. {
  111. return 1;
  112. }
  113. /*没到第k层怎么做*/
  114. return BTLevelKSize(root->left,k-1) + BTLevelKSize(root->right,k-1) ; //不是第k层,继续深入,找到第k层
  115. //为什么是k-1,因为传的是k
  116. }
  117. /*二叉树查找*/
  118. BTNode *BTFind(BTNode* root, BTDataType x)
  119. {
  120. if (!root )
  121. {
  122. return NULL;
  123. }
  124. if (root->data == x)
  125. {
  126. return root;
  127. }
  128. BTNode*left = BTFind(root->left ,x);
  129. if (left)
  130. {
  131. return left;
  132. }
  133. BTNode*right = BTFind(root->right,x);
  134. if (right)
  135. {
  136. return right;
  137. }
  138. //可以写成下面这种形式,看起来好看
  139. /*
  140. if (right)
  141. return right;)*/
  142. return NULL; //root !=NULL , 值不为x ,左右子树找不到,返回空
  143. }
  144. /*构建一个二叉树*/
  145. BTNode *rebuildBinaryTree(BTDataType* str, int *pi) // pi为数组的下标地址
  146. {
  147. if (str[*pi] == '#')
  148. {
  149. (*pi)++;
  150. return NULL;
  151. }
  152. BTNode*root = (BTNode*)malloc(sizeof(BTNode));
  153. root->data = str[(*pi)++];
  154. root->left = rebuildBinaryTree(str, pi);
  155. root->right = rebuildBinaryTree(str, pi);
  156. return root;
  157. }
  158. /*二叉树销毁*/
  159. void BTDestroy(BTNode*root) /*一级指针传值,调用后需要在函数外置空root*/
  160. {
  161. if (!root)
  162. {
  163. return;
  164. }
  165. BTDestroy(root->left);
  166. BTDestroy(root->right);
  167. free(root);
  168. root->left = root->right = NULL;
  169. }
  170. /*
  171. 遍历命名
  172. N(Node)、L(Left subtree)和R(Right subtree)
  173. 根据访问结点操作发生位置命名:
  174. ① NLR:前序遍历(Preorder Traversal 亦称(先序遍历))
  175. ——访问根结点的操作发生在遍历其左右子树之前。
  176. ② LNR:中序遍历(Inorder Traversal)
  177. ——访问根结点的操作发生在遍历其左右子树之中(间)。
  178. ③ LRN:后序遍历(Postorder Traversal)
  179. ——访问根结点的操作发生在遍历其左右子树之后。
  180. 前三种次序与后三种次序对称
  181. */
中序遍历(非递归)
  1. vector<int> inorderTraversal(TreeNode* root) {
  2. vector<int> v;
  3. stack<TreeNode*> st;
  4. TreeNode* cur = root;
  5. while(cur || !st.empty())
  6. {
  7. while(cur)
  8. {
  9. st.push(cur);
  10. cur = cur->left;
  11. }
  12. TreeNode* top = st.top();
  13. v.push_back(top->val);
  14. st.pop();
  15. cur = top->right;
  16. }
  17. return v;
  18. }
层序遍历
  1. #define _CRT_SECURE_NO_WARNINGS 1
  2. #include"Tree.h"
  3. #include"Queue.h"
  4. void LevelOrder(BTNode *root)
  5. {
  6. //通过队列实现
  7. Queue queue;//不带指针==给空间 , 带指针==只给指针空间,成员无空间
  8. QueueInit(&queue);
  9. if (root)
  10. QueuePush(&queue, root);
  11. //通过队列缓存子树滚动数据实现遍历
  12. while (!QueueEmpty(&queue))
  13. {
  14. BTNode* front = QueueFront(&queue);//把数据从队列中取出
  15. printf("%d ", front->data);
  16. QueuePop(&queue);
  17. if (front->left)
  18. {
  19. QueuePush(&queue, front->left);
  20. }
  21. if (front->right)
  22. {
  23. QueuePush(&queue, front->right);
  24. }
  25. }
  26. QueueDestroy(&queue);
  27. }
  28. void LevelOrder2(BTNode *root)
  29. {
  30. if (!root)
  31. {
  32. return;
  33. }
  34. Queue queue;
  35. QueueInit(&queue);
  36. int levelSize = 0;
  37. QueuePush(&queue, root);
  38. levelSize = 1;
  39. while (!QueueEmpty(&queue))
  40. {
  41. while (levelSize--)
  42. {
  43. BTNode* front = QueueFront(&queue);
  44. printf("%d ", front->data);
  45. QueuePop(&queue);
  46. if (front->left)
  47. QueuePush(&queue, front->left);
  48. if (front->right)
  49. QueuePush(&queue, front->right);
  50. }
  51. printf("\n");
  52. levelSize = QueueSize(&queue);
  53. }
  54. QueueDestroy(&queue);
  55. }
节点个数
  1. #define _CRT_SECURE_NO_WARNINGS 1
  2. #include"Tree.h"
  3. int TreeSize(struct BinaryTreeNode *root)
  4. {
  5. return !root ? 0 : TreeSize(root->left) + TreeSize(root->right) + 1;
  6. }
  7. void _preorderTraversal(struct BinaryTreeNode *root, int *str, int* pi)
  8. {
  9. if (!root)
  10. {
  11. return ;
  12. }
  13. str[(*pi)++] = root->data;
  14. _preorderTraversal(root->left, str, pi);
  15. _preorderTraversal(root->right, str, pi);
  16. }
  17. int* preorderTraversal(struct BinaryTreeNode* root, int* returnSize){
  18. *returnSize = TreeSize(root);
  19. int *a = (int*)malloc(*returnSize * sizeof(int));
  20. int i = 0;
  21. _preorderTraversal(root, a, &i);
  22. return a;
  23. }
判断是否完全二叉树
  1. #include"Tree.h"
  2. #include"Queue.h"
  3. bool isBTComplete(BTNode *root)
  4. {
  5. if (!root)
  6. {
  7. return false;
  8. }
  9. Queue queue;
  10. QueueInit(&queue);
  11. QueuePush(&queue, root);
  12. while (!QueueEmpty(&queue))
  13. {
  14. BTNode* front = QueueFront(&queue);
  15. QueuePop(&queue);
  16. if (!front) //一定存在下一层节点,所以直接跳出去,不用插入所有节点
  17. {
  18. QueuePop(&queue);
  19. break;
  20. }
  21. QueuePush(&queue, front->left);
  22. QueuePush(&queue, front->right);
  23. }
  24. while (!QueueEmpty(&queue))
  25. {
  26. if (QueueFront(&queue))
  27. {
  28. QueueDestroy(&queue);
  29. return false;
  30. }
  31. QueuePop(&queue);
  32. }
  33. QueueDestroy(&queue);
  34. return true;
  35. }
判断是否是子树
  1. #include<stdio.h>
  2. struct TreeNode
  3. {
  4. int val;
  5. struct TreeNode* left; //left sub tree
  6. struct TreeNode* right; //right sub tree
  7. };
  8. bool isSubtree(struct TreeNode* root, struct TreeNode* subRoot){
  9. if (subRoot == NULL)
  10. {
  11. return true;
  12. }
  13. if (root == NULL)
  14. {
  15. return false;
  16. }
  17. if (root->val == subRoot->val) //如果相等,且是相同子树,返回真
  18. {
  19. if (isSameTree(root, subRoot))
  20. {
  21. return true;
  22. }
  23. }
  24. return isSubtree(root->left, subRoot)
  25. || isSubtree(root->right, subRoot);
  26. }
判断两棵树是否相同相同
  1. #include<stdio.h>
  2. struct TreeNode
  3. {
  4. int val;
  5. struct TreeNode* left; //left sub tree
  6. struct TreeNode* right; //right sub tree
  7. };
  8. bool isSameTree(struct TreeNode* p, struct TreeNode* q){
  9. if (p == NULL && q == NULL)
  10. {
  11. return 1;
  12. }
  13. else if (p == NULL&&p != q || q == NULL && p != q)
  14. { /*注意逻辑运算先后顺序,整个if-else是判断有1个NULL以上为前提,运算符要求先有空*/
  15. //可以优化成 p == NULL && q == NULL,因为已有前提;
  16. return 0;
  17. }
  18. if (p->val != q->val)
  19. {
  20. return 0;
  21. }
  22. else
  23. return isSameTree(p->left, q->left) && isSameTree(p->right, q->right);
  24. }
两个树是否对称
  1. #include "Tree.h"
  2. bool _isSymmetric(BTNode *root1, BTNode *root2)
  3. {
  4. if (!root1 &&!root2)
  5. {
  6. return true;
  7. }
  8. if (!root1 || !root2)
  9. {
  10. return false;
  11. }
  12. if (root1->data != root2->data)
  13. {
  14. return false;
  15. }
  16. return _isSymmetric(root1->left, root2->right) && _isSymmetric(root1->right, root2->left);
  17. }
  18. bool isSymmetric(BTNode *root)
  19. {
  20. return !root || _isSymmetric(root->left, root->right);
  21. }
两棵树是否对称2
  1. //检查左右子树是否对称
  2. //检查方法:
  3. // 1.两个节点都为空为对称
  4. // 2.两节点的值相等 && a节点左与b节点右对称 && a节点右与b节点左
  5. class Solution {
  6. public:
  7. bool isSymmetric(TreeNode* root) {
  8. if (root == nullptr)
  9. return true;
  10. return check(root->left, root->right);
  11. }
  12. bool check(TreeNode* p, TreeNode* q) {
  13. if (!q && !p)
  14. return true; // 两个都是空
  15. if (!q || !p)
  16. return false; // 两个都不是空的前提,其中一个为空,pass
  17. return check(p->left, q->right) && check(p->right, q->left) &&
  18. p->val == q->val;
  19. }
  20. };
二叉树的直径

543. 二叉树的直径 - 力扣(LeetCode)

  1. /**
  2. * Definition for a binary tree node.
  3. * struct TreeNode {
  4. * int val;
  5. * TreeNode *left;
  6. * TreeNode *right;
  7. * TreeNode() : val(0), left(nullptr), right(nullptr) {}
  8. * TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
  9. * TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
  10. * };
  11. */
  12. /*
  13. 递归分解:
  14. 分别计算root左右节点的最大直径,然后求和与flag作比较+置换
  15. (每个节点都要这么做)
  16. flag是最长路径的节点个数,flag-1就是路径数
  17. (先计算节点数,最后再-1得到路径)
  18. */
  19. class Solution {
  20. public:
  21. int answer = 0; //最长经过的节点数
  22. int diameterOfBinaryTree(TreeNode* root) {
  23. answer = 1; //为空时满足return条件
  24. depth(root);
  25. return answer-1;
  26. }
  27. int depth(TreeNode * root ){
  28. if(root == nullptr) return 0;
  29. int L = depth(root->left); //root的左子树深度 = 左子树最长路径节点数
  30. int R = depth(root->right);//root的右子树节点数 = ...
  31. answer = max(L+R+1,answer);//左右连起来最长路径节点数,与旧值比较
  32. return max(L,R)+1; //返回root的最长子树的深度
  33. }
  34. };
单值二叉树

如果二叉树每个节点都具有相同的值,那么该二叉树就是单值二叉树

  1. #include<stdio.h>
  2. struct TreeNode
  3. {
  4. int val;
  5. struct TreeNode* left; //left sub tree
  6. struct TreeNode* right; //right sub tree
  7. };
  8. bool isUnivalTree(struct TreeNode* root){
  9. if (root == NULL)
  10. {
  11. return true;
  12. }
  13. if (root->left && root->left->val != root->val)
  14. {
  15. return false;
  16. }
  17. if (root->right && root->right->val != root->val)
  18. {
  19. return false;
  20. }
  21. return isUnivalTree(root->left) && isUnivalTree(root->right);
  22. }
翻转二叉树
  1. TreeNode* invertTree(TreeNode* root) {
  2. if(root == nullptr) return nullptr;
  3. TreeNode* left = invertTree(root->left);
  4. TreeNode* right = invertTree(root->right);
  5. root->left = right;
  6. root->right =left;
  7. return root;
  8. }
最大深度
  1. int maxDepth(TreeNode* root) {
  2. if(root == nullptr) return 0;
  3. return max(maxDepth(root->left),maxDepth(root->right))+1;
  4. }

用到的队列

Quque.h
  1. #pragma once
  2. #define _CRT_SECURE_NO_WARNINGS 1
  3. #include<stdio.h>
  4. #include<stdlib.h>
  5. #include<stdbool.h>
  6. #include<assert.h>
  7. //#include"tree.h"
  8. struct BinaryTreeNode; //类型声明 : 类型是可以声明的,只要变量名就可以---原理,搜索整个源码
  9. typedef struct BinaryTreeNode* QDataType;
  10. typedef struct QueueNode
  11. {
  12. struct QueueNode *next;
  13. QDataType data;
  14. }QNode;
  15. //控制变量结构体
  16. //只有一个值,就不用定义结构体,有多个就定义结构题。
  17. typedef struct Queue
  18. {
  19. struct QueueNode *head; //队头,出队,头删
  20. struct QueueNode *tail; //队尾,入队,尾插
  21. }Queue;
  22. //是指针变量就传二级指针,是普通变量就传一级
  23. void QueueInit(Queue *pq);
  24. void QueueDestroy(Queue *pq);
  25. void QueuePush(Queue *pq, QDataType x);
  26. void QueuePop(Queue *pq);
  27. QDataType QueueFront(Queue *pq);
  28. QDataType QueueBack(Queue *pq);
  29. int QueueSize(Queue *pq);
  30. bool QueueEmpty(Queue *pq);
Queue.c
  1. #include "Queue.h"
  2. void QueueInit(Queue *pq)
  3. {
  4. assert(pq);
  5. pq->head = NULL;
  6. pq->tail = NULL;
  7. }
  8. void QueueDestroy(Queue* pq)
  9. {
  10. assert(pq);
  11. QNode * next = NULL;
  12. QNode *cur = pq->head;
  13. while (cur)
  14. {
  15. next = cur->next;
  16. free(cur);
  17. cur = next;
  18. }
  19. pq->head=pq->tail = NULL;
  20. }
  21. void QueuePush(Queue *pq,QDataType x)
  22. {
  23. assert(pq);
  24. QNode *newnode = (QNode*)malloc(sizeof(QNode));
  25. if (newnode == NULL)
  26. {
  27. perror("malloc fail ");
  28. exit(-1);
  29. }
  30. //先初始化再使用
  31. newnode->data = x;
  32. newnode->next = NULL;
  33. //单链表队列-尾插头删。
  34. if (pq->tail == NULL)//头尾都行,判断一个就可以了
  35. {
  36. pq->head = pq->tail = newnode;
  37. }
  38. else //尾插
  39. {
  40. pq->tail->next = newnode; //队尾指向的节点链接上新节点
  41. pq->tail = newnode; //队尾指向新节点
  42. }
  43. }
  44. void QueuePop(Queue *pq)
  45. {
  46. assert(pq);
  47. assert(!QueueEmpty(pq));
  48. if (pq->head->next == NULL)//只有一个节点
  49. {
  50. free(pq->head);//先释放
  51. pq->tail = pq->head = NULL;//后置空
  52. }
  53. else
  54. {
  55. QNode *next = pq->head->next;//记住下一个
  56. free(pq->head);//释放头节点
  57. pq->head = next;//下个节点成为新节点
  58. }
  59. }
  60. QDataType QueueFront(Queue *pq)
  61. {
  62. assert(pq);
  63. assert(!QueueEmpty(pq));
  64. return pq->head->data;
  65. }
  66. QDataType QueueBack(Queue *pq)
  67. {
  68. assert(pq);
  69. assert(!QueueEmpty(pq));
  70. return pq->tail->data;
  71. }
  72. int QueueSize(Queue *pq)
  73. {
  74. assert(pq);
  75. QNode *cur = pq->head;
  76. int size = 0;
  77. while (cur)
  78. {
  79. size++;
  80. cur = cur->next;
  81. }
  82. return size;
  83. }
  84. bool QueueEmpty(Queue *pq)
  85. {
  86. assert(pq);
  87. //return QueueSize(pq) == 0;
  88. return pq->head == NULL ;//只要有一个就可以了
  89. //head为空tail也为空
  90. }

原文链接:https://www.cnblogs.com/DSCL-ing/p/18344162

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号