当前位置:   article > 正文

二叉树链式结构(入门)_二叉链树

二叉链树

目录

一.二叉树的简单构建

二.二叉树的遍历

1.先序遍历

2.中序遍历

3.后序遍历

4.已知先序,中序,构建一颗二叉树

5.层序遍历

三.有关二叉树的简单oj题

1.二叉树节点个数

2.二叉树叶子节点个数

3.二叉树第k层节点个数

4.求二叉树的高度

5.二叉树查找值为x的节点

6.单值二叉树

7.检查两棵树是否相同

8.另一棵树的子树

9.翻转二叉树

10.对称二叉树

11.判断二叉树是否为一棵平衡二叉树

12.二叉树的构建

13.判断二叉树是否为一棵完全二叉树 

14.二叉树的销毁

15.二叉树的先序遍历/中序遍历/后序遍历


一.二叉树的简单构建

如下图所示,是一棵我们常见的链式二叉树.

不同于链表,二叉树每个结点并不是单一链接的,而是分别指向两个不同的结点.

即结点结构里面,存储有left,right两个指针.

将结点转化成对应的C语言代码如下:

  1. typedef int BTDataType;
  2. typedef struct BinaryTreeNode
  3. {
  4. BTDataType data;
  5. struct BinaryTreeNode* left;
  6. struct BinaryTreeNode* right;
  7. }BTNode;

 如果想要迅速手动构建一棵二叉树,我们只要实现相应创建单个结点的函数.然后再手动按照我们

的想法自己链接起来即可.

  1. //创建单个结点
  2. BTNode* BuyBTNode(BTDataType x)
  3. {
  4. //给单个结点开辟空间
  5. BTNode* tmp = (BTNode*)malloc(sizeof(BTNode));
  6. //判断是否申请空间成功
  7. if (tmp == NULL)
  8. {
  9. perror("malloc fail.\n");
  10. exit(-1);
  11. }
  12. //赋值
  13. tmp->data = x;
  14. tmp->left = tmp->right = NULL;
  15. return tmp;
  16. }

 比方说我们想要构建上面这棵这棵树.

确定好每个结点的名字后,直接手动链接即可.

  1. int main()
  2. {
  3. BTNode* n1 = BuyBTNode(1);
  4. BTNode* n2 = BuyBTNode(3);
  5. BTNode* n3 = BuyBTNode(5);
  6. BTNode* n4 = BuyBTNode(9);
  7. BTNode* n5 = BuyBTNode(11);
  8. BTNode* n6 = BuyBTNode(2);
  9. n1->left = n2;
  10. n1->right = n3;
  11. n2->left = n4;
  12. n2->right = n5;
  13. n3->right = n6;
  14. return 0;
  15. }

 当然这并非二叉树真正构建的方法,但我们可以通过这个方法,迅速构建出我们想要的二叉树.

在进一步了解二叉树之前,我们还需要换种角度看待二叉树的结构.

假如把空,也看作是一个结点的话

我们可以发现,任何一棵树,都可以看作是  根+左子树+右子树的结构

 同样的,左子树也是一棵树,因此它也依旧可以看作  根+左子树+右子树的结构

甚至一个结点也是一棵树,它也依旧可以看作 根+左子树+右子树的结构

只不过它的左子树和右子树都是空.

从这里我们也可以看出二叉树的结构特点:

每一个最小的单元,到组合成一个更大的单元,都有着相似的结构.

因此,我们也可以猜想到,二叉树和递归之间的关系是密不可分的.

而将二叉树看作三个基本单元:根节点,左子树,右子树组成,也被称之为二叉树的递归定义.

二.二叉树的遍历

由于二叉树的形状是不确定的,甚至可以全部倾向一边,构成我们熟悉的单链表.

因此,从某种意义上来说,二叉树的增删查改并没有意义,除非有着特殊结构的限定.

与此相反,二叉树的遍历问题有着重要的地位,后面很多题目的求解,其实或多或少,都借鉴了遍

历问题的思想.

那什么是遍历问题呢?

通俗点讲,就是按照什么样的搜索路径巡访树中的每个结点,使得每个结点有且仅被访问一次.

实际上,递归的过程是相同的,唯一不同的是,访问结点的时机.

1.先序遍历

假如按照根结点,再左子树,最后右子树的顺序访问,我们称之为先序遍历.

结合下面的例子,就是先访问根节点n1,再访问其左子树,对于左子树来说,n2又是它的根节点.

以此类推,假如按照先序遍历来打印这棵树,得到的结果是

其代码的实现,在理解递归和其思想后,也比较容易实现

  1. // 二叉树前序遍历
  2. void BinaryTreePrevOrder(BTNode* root)
  3. {
  4. //递归返回条件
  5. if (root == NULL)
  6. return;
  7. //打印根节点
  8. printf("%d ", root->data);
  9. //递归访问左子树
  10. BinaryTreePrevOrder(root->left);
  11. //递归访问右子树
  12. BinaryTreePrevOrder(root->right);
  13. }

2.中序遍历

同理,假如按照先左子树,再根节点,最后右子树的顺序,我们称之为中序遍历

结合下面的例子,就是先访问n1的左子树,然后访问n2的左子树,一直到访问n4的左子树,遇到

空指针,返回,打印n2的左子树n4,然后才打印n2(根),最后递归调用,访问n2的右子树n5.

以此类推,假如按照中序遍历来打印这棵树,得到的结果是

  1. // 二叉树中序遍历
  2. void BinaryTreeInOrder(BTNode* root)
  3. {
  4. //递归返回条件
  5. if (root == NULL)
  6. return;
  7. //中序遍历左子树
  8. BinaryTreeInOrder(root->left);
  9. //访问根结点
  10. printf("%d ", root->data);
  11. //中序遍历右子树
  12. BinaryTreeInOrder(root->right);
  13. }

3.后序遍历

类似,先左子树,后右子树,最后才访问根节点的顺序,我们称之为后序遍历

结合下面的例子,就是先访问n1的左子树,递归访问n2的左子树,紧接着访问n4的左子树,为空

返回,访问n4的右子树,也为空,返回.这个时候才打印n4(根)

以此类推,假如按照后序遍历来打印这棵树,得到的结果是

  1. void BinaryTreePostOrder(BTNode* root)
  2. {
  3. //递归返回条件
  4. if (root == NULL)
  5. return;
  6. //递归后序遍历左子树
  7. BinaryTreePostOrder(root->left);
  8. //递归后序遍历右子树
  9. BinaryTreePostOrder(root->right);
  10. //访问根节点
  11. printf("%d ", root->data);
  12. }

4.已知先序,中序,构建一颗二叉树

我们经常会遇到类似这样的问题,假如知道前序遍历序列,中序遍历序列,如何得到后序遍历序列

呢?

我们知道先序遍历,是按照根,左子树,右子树顺序访问一棵树的. 

那先序遍历,得到的第一个值,这里是5,肯定就是整棵树的根.

我们又知道中序遍历,是按照左子树,根,右子树顺序访问一棵树

那我们知道根是5后,自然就可以把中序遍历划分为下面的结构.

 我们再看左子树4  7,在先序遍历时,得到的第一个值,一定是树的根.

那结合先序遍历,我们就可以知道,左子树的根是7,再看回中序遍历,4在7的左边,因此4是7的

左子树.这样我们就可以复原出原本这棵树的样子.

 

得到树原本的逻辑结构,得到后序遍历也就很轻松了.

最后答案是  4  7  6  1  2  9  5.

类似的,我们知道中序遍历,后序遍历,也可以确定一棵二叉树.

但值得注意的是,只知道先序,后序序列,是无法确定一棵树的.

理由也很简单,没有中序遍历,我们无法进一步划分左子树,右子树的形状是如何的,先序和后序

仅仅为我们提供了根的位置.

单拿上面的例子来看

上述两棵树都可以得到相同的先序,后序序列,但它们中序遍历的结果却是不同的. 

5.层序遍历

除了上述的按先序,中序,后序遍历一棵二叉树,还可以从上到下,从左到右按层次遍历,我们称

之为层序遍历.

对于原本我们的例子来说,就是一层一层从左到右访问,先访问n1,然后访问n2,再访问n3

以此类推,直到访问一棵树所有的结点为止.得到的结果如下:

而不同于前面遍历实现的思想,我们需要借助队列来实现层序遍历.

每个结点入列后,在出列的同时,我们把与它相链接非空的结点,也一并入列.

队列的先进先出结构,也保证了一层层访问结点.

具体来说,我们先把n1入列.

访问n1时,n1需要出列,同时我们把和1相链接的两个结点n2,n3入列. 

同理,访问n2时,n2需要出列,同时我们把n4,n5两个结点入列.

以此类推,我们就可以实现层序遍历.

有关C语言队列的实现,具体可以参照之前相关的文章.

  1. // 层序遍历
  2. void BinaryTreeLevelOrder(BTNode* root)
  3. {
  4. Queue q;
  5. QueueInit(&q);
  6. //如果根结点不为空,就先让其入列
  7. if (root)
  8. QueuePush(&q, root);
  9. //不断有元素出列,直到队列为空才停止
  10. while (!QueueEmpty(&q))
  11. {
  12. BTNode* front = QueueFront(&q);
  13. printf("%d ", front->data);
  14. QueuePop(&q);
  15. //如果左孩子不为空
  16. if (front->left)
  17. QueuePush(&q, front->left);
  18. //如果右孩子不为空
  19. if (front->right)
  20. QueuePush(&q, front->right);
  21. }
  22. printf("\n");
  23. QueueDestroy(&q);
  24. }

 leetcode也有二叉树遍历的题目,不过由于C语言没有STL库,队列这种数据结构需要我们自己构

造,会导致C语言实现起来会比较麻烦.

  1. //题目描述种限定节点只有2000个
  2. #define MAX_SUM 2000
  3. typedef struct TreeNode* QDataType;
  4. typedef struct QueueNode {
  5. QDataType data;
  6. struct QueueNode* next;
  7. }QNode;
  8. typedef struct Queue {
  9. QNode* head;
  10. QNode* rear;
  11. int size;
  12. }Queue;
  13. void QueueInit(Queue* ps)
  14. {
  15. ps->head = ps->rear = NULL;
  16. ps->size = 0;
  17. }
  18. void DestroyQueue(Queue* ps)
  19. {
  20. QNode* cur = ps->head;
  21. while (cur)
  22. {
  23. QNode* next = cur->next;
  24. free(cur);
  25. cur = next;
  26. }
  27. ps->head = ps->rear = NULL;
  28. ps->size = 0;
  29. }
  30. bool QueueEmpty(Queue* ps)
  31. {
  32. return ps->head == NULL && ps->rear == NULL;
  33. }
  34. void QueuePush(Queue* ps, QDataType x)
  35. {
  36. QNode* newnode = (QNode*)malloc(sizeof(QNode));
  37. newnode->data = x;
  38. newnode->next = NULL;
  39. if (ps->head == NULL)
  40. {
  41. ps->head = ps->rear = newnode;
  42. }
  43. else
  44. {
  45. ps->rear->next = newnode;
  46. ps->rear = newnode;
  47. }
  48. ps->size++;
  49. }
  50. void QueuePop(Queue* ps)
  51. {
  52. if (!QueueEmpty(ps))
  53. {
  54. if (ps->head->next == NULL)
  55. {
  56. free(ps->head);
  57. ps->head = ps->rear = NULL;
  58. }
  59. else
  60. {
  61. QNode* del = ps->head;
  62. ps->head = ps->head->next;
  63. free(del);
  64. }
  65. ps->size--;
  66. }
  67. }
  68. QDataType QueueFront(Queue* ps)
  69. {
  70. return ps->head->data;
  71. }
  72. int QueueSize(Queue* ps)
  73. {
  74. return ps->size;
  75. }
  76. int** levelOrder(struct TreeNode* root, int* returnSize, int** returnColumnSizes){
  77. //根节点是空指针,直接返回NULL
  78. if (!root)
  79. {
  80. *returnSize = 0;
  81. return NULL;
  82. }
  83. //构造一个二维数组
  84. int** a = (int**)malloc(sizeof(int*)*MAX_SUM);
  85. //创建一个队列,队列此时每个元素类型是Struct TreeNode
  86. Queue q;
  87. QueueInit(&q);
  88. QueuePush(&q,root);
  89. int returncnt = 0;
  90. //记录每一列有多少元素的数组
  91. int *column =(int *)malloc(sizeof(int)*MAX_SUM);
  92. //如果队列不为空
  93. while (!QueueEmpty(&q))
  94. {
  95. //判断第几层有几个元素(队列长度)
  96. int sz = QueueSize(&q);
  97. //二维数组指向相应动态数组
  98. a[returncnt] = (int*)malloc(sizeof(int)*sz);
  99. // 记录不同列有多少个元素
  100. column[returncnt] = sz;
  101. // 每一层有多少元素,就循环多少次,赋值相应多少元素
  102. for (int i = 0;i < sz;++i)
  103. {
  104. //取出队列元素
  105. struct TreeNode* node = QueueFront(&q);
  106. a[returncnt][i] = node->val;
  107. //相应节点的左孩子和右孩子入列,左孩子和右孩子不为空才入列
  108. if (node->left)
  109. QueuePush(&q,node->left);
  110. if (node->right)
  111. QueuePush(&q,node->right);
  112. //左孩子和右孩子都入列后,节点才出列
  113. QueuePop(&q);
  114. }
  115. returncnt++;
  116. }
  117. *returnSize= returncnt;
  118. *returnColumnSizes = column;
  119. DestroyQueue(&q);
  120. return a;
  121. }

三.有关二叉树的简单oj题

为了加深对链式二叉树和递归相关知识点的理解,我们可以尝试入手几道简单的题目来加深印象.

1.二叉树节点个数

依旧拿我们先前那棵树做例子,我们如何得到这棵树有多少个节点呢?

我们直到,递归的思想,就是将大问题,转化成小问题进行解决.

依旧是抽象出树的递归结构,我们怎么得到它的节点呢? 

一个简单的思路,就是假如我们得到左子树的节点个数,又得到右子树的节点个数,再加上1(根节

点),那总的节点个数 = 左子树的节点个数 + 右子树的节点个数  + 1.

假设是空子树,则返回0,于是我们便有了实现该题的思路.

  1. // 二叉树节点个数
  2. //法一
  3. int BinaryTreeSize(BTNode* root)
  4. {
  5. //递归返回条件,遇到空树,返回0
  6. if (root == NULL)
  7. return 0;
  8. return BinaryTreeSize(root->left) + BinaryTreeSize(root->right) + 1;
  9. }
  10. //法二:用三目运算符简化程序
  11. int BinaryTreeSize(BTNode* root)
  12. {
  13. return root == NULL ? 0:BinaryTreeSize(root->left) + BinaryTreeSize(root->right) + 1;
  14. }

2.二叉树叶子节点个数

对比第一题,难度又加大了一点,这次需要判断叶子节点的个数,叶子节点就是没有孩子的节点.

我们又该如何解决这个问题呢?

叶子节点的特点就是没有孩子,在程序中对应,left和right指针都为空.

一棵树叶子节点 = 左子树叶子节点个数 + 右子树叶子节点个数

同样,假设是空子树,则返回0,我们便有了实现该题的思路.

  1. // 二叉树叶子节点个数
  2. int BinaryTreeLeafSize(BTNode* root)
  3. {
  4. //递归返回条件,根节点为空
  5. if (root == NULL)
  6. return 0;
  7. //left和right指针都为空,则说明是一个叶子节点
  8. if (root->left == NULL && root->right == NULL)
  9. return 1;
  10. return BinaryTreeLeafSize(root->left) + BinaryTreeLeafSize(root->right);
  11. }

3.二叉树第k层节点个数

在第二题基础上,我们又进了一步,这一次需要解决第k层节点的个数.

同样,我们用二叉树的递归结构图来思考.

这里,我们设第1层,为k==1,那树为空的时候,对应的就是k==0(当然也可以设第一层为k==0,但此时空对应k == -1)

那假如我们要求解第1层的节点个数,我们可以自信的说,有1个,也就是一个根节点.

那求解第2层,k == 2时呢?

对于根节点来说,它们是第二层,但假如把它们也看作树,它们就是自己树的根节点,对应的是它

们自己的第一层.

因此,第k层的节点个数 = 将所有第k层节点作为子树时,相应根节点的个数.

  1. //二叉树第k层节点个数
  2. int BinaryTreeLevelKSize(BTNode* root, int k)
  3. {
  4. if (root == NULL)
  5. return 0;
  6. //假如k==1,则说明此时已经到达第k层,可以统计是否存在根节点
  7. if (k == 1)
  8. return 1;
  9. return BinaryTreeLevelKSize(root->left, k - 1)+ BinaryTreeLevelKSize(root->right, k - 1);
  10. }

4.求二叉树的高度

对于下面这棵树,它的树高是4,具体是如何计算出来的呢?

我们可以发现,一棵树的高度 = 左子树和右子树高度较大值 + 1

当遇到空的时候,对应的高度就是0,递归就可以返回.

拿上图举例子,这棵树的高度 = 两棵子树中较大的高度(3)+ 1 == 4

  1. // 求二叉树的树高
  2. int BinaryTreeHeight(BTNode* root)
  3. {
  4. if (root == NULL)
  5. return 0;
  6. //记录左子树的高度
  7. int leftheight = BinaryTreeHeight(root->left);
  8. //记录右子树的高度
  9. int rightheight = BinaryTreeHeight(root->right);
  10. return leftheight > rightheight ? leftheight + 1 : rightheight + 1;
  11. }

5.二叉树查找值为x的节点

想要找到值为x的节点非常简单,可以转化成去左子树中找,再去右子树中去找的递归小问题.

但问题的关键是如何在递归的程序中,将对应节点的指针返回回来呢?

像第2,3题中,直接最后返回,显然不太现实,指针做加减不符合我们的要求,利用与或等等运算

也不是我们想要的,我们需要具体的指针的值.

这里我们借鉴第4题的思路,将指针的值,保存下来,如果不为空,则返回回来.

  1. // 二叉树查找值为x的节点
  2. BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
  3. {
  4. if (root == NULL)
  5. return NULL;
  6. //如果root->data == x,则说明找到该节点,返回对应指针
  7. if (root->data == x)
  8. return root;
  9. //递归去左子树中找,无论是否找到,都将指针保存下来
  10. BTNode* ret1 = BinaryTreeFind(root->left, x);
  11. //指针不为空,则层层返回
  12. if (ret1)
  13. return ret1;
  14. //递归去右子树中找,无论是否找到,都将指针保存下来
  15. BTNode* ret2 = BinaryTreeFind(root->right, x);
  16. if (ret2)
  17. return ret2;
  18. //左子树和右子树中都没有,则返回空,代表不存在该节点
  19. return NULL;
  20. }

6.单值二叉树

题目描述如下:

 很多人做这道题的时候,会把它分解为左子树,根,右子树三者的值是否相等的子问题.

但是卡在了如何将判断的值返回出去的问题上

但我们还需要注意一点,实际上并没有必要这样判断,毕竟有可能左子树为空,右子树也可能为

空,这需要判断的东西实际上非常多.

正确的思路是,当左子树不为空的时候,先判断左子树的值和根节点的的值是否相等,不相等,则

绝对不可能是一棵单值二叉树.

当左子树满足的情况下,再判断右子树和根节点的值是否相等.

  1. bool isUnivalTree(struct TreeNode* root){
  2. if (root == NULL)
  3. return true;
  4. //判断左子树和根节点是否相等
  5. if (root->left && root->left->val != root->val)
  6. return false;
  7. //判断右子树和根节点是否相等
  8. if (root->right && root->right->val != root->val)
  9. return false;
  10. //递归调用判断左子树和右子树是否分别是一棵单值二叉树
  11. return isUnivalTree(root->left) && isUnivalTree(root->right);
  12. }

7.检查两棵树是否相同

有了前面题目的思路,判断两棵树是否相等,实际上就是转化为递归判断它们的左子树和右子树分

别是否相同. 

那对于一棵足够小的树,如何判断它们是否相等呢?

经过思考,我们可以将其划分为3个阶段进行判断:

1.假如两棵树都是空,那两者肯定是相同的.

2.假如一棵树为空,另一棵树不为空,那两者肯定不相同

3.假如两棵树都不为空,那对应的根节点分别进行判断就好.

  1. bool isSameTree(struct TreeNode* p, struct TreeNode* q){
  2. if (p == NULL && q == NULL)
  3. return true;
  4. //p与q中有一个为空
  5. if (p == NULL || q == NULL)
  6. return false;
  7. if (p->val != q->val)
  8. return false;
  9. return isSameTree(p->left,q->left) && isSameTree(p->right,q->right);
  10. }

8.另一棵树的子树

 题目有两点需要我们注意的,子树为一个节点时,或者和原树相同,也都属于原树的子树.

一个简单解决这道题的思路是,调用我们前面实现过的判断两棵树是否为相同的树的函数.

只要遍历原树的每一个节点,和子树进行判断是否为相同的树即可.

  1. bool issameTree(struct TreeNode* r1,struct TreeNode * r2)
  2. {
  3. if (r1 == NULL && r2 == NULL)
  4. return true;
  5. if (r1 == NULL || r2 == NULL)
  6. return false;
  7. if (r1->val != r2->val)
  8. return false;
  9. return issameTree(r1->left,r2->left) && issameTree(r1->right,r2->right);
  10. }
  11. bool isSubtree(struct TreeNode* root, struct TreeNode* subRoot){
  12. //递归返回,假如节点为空,则返回false,代表不存在子树
  13. if (root == NULL)
  14. return false;
  15. if (issameTree(root,subRoot))
  16. return true;
  17. //利用或运算即可,只要找到存在子树,就返回true
  18. return isSubtree(root->left,subRoot) || isSubtree(root->right,subRoot);
  19. }

9.翻转二叉树

那这道题的思路其实也比较清晰,想要翻转一棵二叉树,那就保存左节点,将左指针指向右节点,

右指针再指向我们保存的左节点即可.

  1. struct TreeNode* invertTree(struct TreeNode* root){
  2. if (root == NULL)
  3. return NULL;
  4. struct TreeNode * tmp = invertTree(root->left);
  5. root->left = invertTree(root->right);
  6. root->right = tmp;
  7. return root;
  8. }

10.对称二叉树

第一个思路是将树翻转过来,再判断是否相同,这固然可以,但关键是改变了原树,那再翻转回来

实际上会比较麻烦. 

其实,和判断两棵二叉树是否相同类似,两棵二叉树是否是对称二叉树.

实际上只需要判断,左子树和右子树是否相等,右子树和左子树又是否相等即可.

判断两棵树是否相同,我们依旧可以照猫画虎将其划分为3个阶段进行判断:

1.假如两棵树都是空,那两者肯定是相同的.

2.假如一棵树为空,另一棵树不为空,那两者肯定不相同

3.假如两棵树都不为空,那对应的根节点分别进行判断就好.

  1. bool isSymmetric_tree(struct TreeNode * p,struct TreeNode * q)
  2. {
  3. if (p == NULL && q == NULL)
  4. return true;
  5. if (p == NULL || q == NULL)
  6. return false;
  7. if (p->val != q->val)
  8. return false;
  9. //递归判断左子树和右子树,右子树和左子树是否分别相同
  10. return isSymmetric_tree(p->left,q->right) && isSymmetric_tree(p->right,q->left);
  11. }
  12. bool isSymmetric(struct TreeNode* root){
  13. //假如根节点为空,直接返回false
  14. return root && isSymmetric_tree(root->left,root->right);
  15. }

11.判断二叉树是否为一棵平衡二叉树

前面我们已经实现过求树的高度,那遍历树的每一个节点,再判断左子树和右子树高度之差是否大

于1即可.

  1. int TreeHeight(struct TreeNode * root)
  2. {
  3. if (root == NULL)
  4. return 0;
  5. int leftheight = TreeHeight(root->left);
  6. int rightheight = TreeHeight(root->right);
  7. return leftheight > rightheight?leftheight + 1:rightheight + 1;
  8. }
  9. bool isBalanced(struct TreeNode* root){
  10. if (root == NULL)
  11. return true;
  12. //判断左子树和右子树高度之差是否大于1
  13. if (fabs(TreeHeight(root->left) - TreeHeight(root->right))>1)
  14. return false;
  15. return isBalanced(root->left) && isBalanced(root->right);
  16. }

不过这样的过程,是一个自顶向下的过程,对于下面底层的叶子节点而言,实际上被重复计算过很

多次高度,当为一条链的时候,时间复杂度甚至会达到O(n^2),这样的效率实际上并不高.

仿照第4题,求解树的高度,我们需要记录下来树高,自底向上来判断,这样就可以大大提高效率.

一旦我们发现子树不是一棵平衡二叉树,则直接返回就好,不需要再进行判断.

  1. int Height(struct TreeNode* root)
  2. {
  3. //如果树为空,则返回0
  4. if (root == NULL)
  5. return 0;
  6. //及时记录左子树高度
  7. int leftheight = Height(root->left);
  8. //记录右子树高度
  9. int rightheight = Height(root->right);
  10. //如果左子树或右子树高度有等于-1的,则证明存在子树不是平衡二叉树,直接层层返回
  11. if (leftheight == -1||rightheight == -1||fabs(rightheight - leftheight) > 1)
  12. return -1;
  13. else
  14. return fmax(rightheight,leftheight)+1;
  15. }
  16. bool isBalanced(struct TreeNode* root){
  17. return Height(root) >= 0;
  18. }

12.二叉树的构建

有了前面的基础(递归)后,我们就可以尝试学习其中一种构建链式二叉树的方法.

题目难点在于如何根据先序遍历,构建出一棵二叉树,毕竟中序遍历,我们其实前面已经实现过

了,而且难度也比较小.

我们用一个字符数组存储输入数据,遇到"#",则证明是一个空结点,返回NULL即可.

借鉴先序遍历的思想,只不过这里访问节点,并不是打印数据,而是创建新节点,并赋值.

构建一棵树,只需要创建好根节点,左子树构建好,根节点的left指向左子树,右子树构建好,根

节点的right指向右子树即可.

PS:这里还需要注意,由于我们采取递归的方式构建树,因此我们传递i的时候,应该传指针进去,

这样每次修改值,都是针对同一个i进行修改.

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. typedef struct BTNode {
  4. char val;
  5. struct BTNode* left;
  6. struct BTNode* right;
  7. }BTNode;
  8. BTNode* CreateTree(char* data, int* pi)
  9. {
  10. if (data[*pi] == '#')
  11. {
  12. //遇到空,返回指针后,数组也要往后移动
  13. (*pi)++;
  14. return NULL;
  15. }
  16. //创建新节点
  17. BTNode* node = (BTNode*)malloc(sizeof(BTNode));
  18. node->val = data[(*pi)++];
  19. //新节点的left,指向构建好的左子树
  20. node->left = CreateTree(data, pi);
  21. //新节点的right,指向构建好的右子树
  22. node->right = CreateTree(data, pi);
  23. return node;
  24. }
  25. void InorderTree(BTNode* root)
  26. {
  27. if (root == NULL)
  28. return;
  29. InorderTree(root->left);
  30. printf("%c ", root->val);
  31. InorderTree(root->right);
  32. }
  33. int main() {
  34. char data[100];
  35. scanf("%s", data);
  36. int i = 0;
  37. BTNode* root = CreateTree(data, &i);
  38. InorderTree(root);
  39. return 0;
  40. }

13.判断二叉树是否为一棵完全二叉树 

和前面题目思想不同,前面大多是采用递归,将一个大任务,拆分成一个个小任务.

而判断一棵完全二叉树,采用的则是层序遍历的思想.

依旧拿下面的二叉树做例子,我们可以发现,它并不是一棵完全二叉树,因为n5,n6之间,缺了相

应的节点. 

如何表示缺了相应的节点呢?

对应的就是队列遇到了空指针.

于是我们就有了思路:

层序遍历二叉树,不管是否是空节点,每次出列的时候,就把其相链接的节点全部压入队列.

假如队列遇到空指针,则判断队列里面是否还有元素,假如还存在元素,则绝对不是一个完全二叉

树,反之则为一棵完全二叉树.

PS:此时存进去队列的元素,和我们之前的int类型不同,是一个二叉树结点指针.

  1. // 判断二叉树是否是完全二叉树
  2. // 采取层序遍历的思想
  3. bool BinaryTreeComplete(BTNode* root)
  4. {
  5. if (root == NULL)
  6. return true;
  7. //创建一个队列
  8. Queue q;
  9. QueueInit(&q);
  10. BTNode* ret = root;
  11. QueuePush(&q, ret);
  12. //只要队列不为空
  13. while (!QueueEmpty(&q))
  14. {
  15. BTNode* ret = QueueFront(&q);
  16. QueuePop(&q);
  17. //如果ret为空,则循环跳出
  18. if (!ret)
  19. break;
  20. QueuePush(&q, ret->left);
  21. QueuePush(&q, ret->right);
  22. }
  23. while (!QueueEmpty(&q))
  24. {
  25. BTNode* ret = QueueFront(&q);
  26. //如果在空指针后,还有不为空的指针,则一定不是一棵完全二叉树
  27. if (ret)
  28. {
  29. QueueDestroy(&q);
  30. return false;
  31. }
  32. QueuePop(&q);
  33. }
  34. QueueDestroy(&q);
  35. return true;
  36. }

14.二叉树的销毁

二叉树的销毁,有三种方式供我们选择,先序,中序,和后序.

但仔细思索后,我们会发现先序和中序其实都不行.

假设我们采用先序遍历销毁二叉树,根节点被free,那它的左子树和右子树,显然我们就再也找不

到.

同理,中序遍历也是如此,我们找不到根节点的右子树.

所以,二叉树的销毁,只能采取后序遍历的方式.

PS:这里并没有采取二级指针的方式,因此,使用该函数的时候,还需要手动将根节点指针置空.

  1. // 二叉树销毁
  2. void BinaryTreeDestory(BTNode* root)
  3. {
  4. if (root == NULL)
  5. return;
  6. //只能采用后序遍历的方式
  7. //先销毁左子树
  8. BinaryTreeDestory(root->left);
  9. //再销毁右子树
  10. BinaryTreeDestory(root->right);
  11. //最后释放根节点
  12. free(root);
  13. }

15.二叉树的先序遍历/中序遍历/后序遍历

和我们先前实现的二叉树先序遍历还不同,leetcode的题目,还需要返回开辟数组的大小.

因此,在构建上,我们除了要实现先序遍历构建新数组外,还需要额外构建TreeSize函数.

  1. int TreeSize(struct TreeNode* root)
  2. {
  3. return root == NULL?0:TreeSize(root->left) + TreeSize(root->right)+1;
  4. }
  5. void Preorder(struct TreeNode* root,int* ret,int* i)
  6. {
  7. if (root == NULL)
  8. return;
  9. ret[(*i)++] = root->val;
  10. Preorder(root->left,ret,i);
  11. Preorder(root->right,ret,i);
  12. }
  13. int* preorderTraversal(struct TreeNode* root, int* returnSize){
  14. int size = TreeSize(root);
  15. *returnSize = size;
  16. int* ret = (int* )malloc(sizeof(int)*size);
  17. int i = 0;
  18. Preorder(root,ret,&i);
  19. return ret;
  20. }

中序遍历和后序遍历也是类似的思路,这里不再细讲.

  1. int TreeSize(struct TreeNode* root)
  2. {
  3. return root == NULL?0:TreeSize(root->left) + TreeSize(root->right)+1;
  4. }
  5. void Inorder(struct TreeNode* root,int* ret,int* i)
  6. {
  7. if (root == NULL)
  8. return;
  9. Inorder(root->left,ret,i);
  10. ret[(*i)++] = root->val;
  11. Inorder(root->right,ret,i);
  12. }
  13. //中序遍历
  14. int* inorderTraversal(struct TreeNode* root, int* returnSize){
  15. int size = TreeSize(root);
  16. *returnSize = size;
  17. int* ret = (int* )malloc(sizeof(int)*size);
  18. int i = 0;
  19. Inorder(root,ret,&i);
  20. return ret;
  21. }

  1. int TreeSize(struct TreeNode* root)
  2. {
  3. return root == NULL?0:TreeSize(root->left) + TreeSize(root->right)+1;
  4. }
  5. void Postorder(struct TreeNode* root,int* ret,int* i)
  6. {
  7. if (root == NULL)
  8. return;
  9. Postorder(root->left,ret,i);
  10. Postorder(root->right,ret,i);
  11. ret[(*i)++] = root->val;
  12. }
  13. //后序遍历
  14. int* postorderTraversal(struct TreeNode* root, int* returnSize){
  15. int size = TreeSize(root);
  16. *returnSize = size;
  17. int* ret = (int* )malloc(sizeof(int)*size);
  18. int i = 0;
  19. Postorder(root,ret,&i);
  20. return ret;
  21. }

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

闽ICP备14008679号