当前位置:   article > 正文

【C语言 - 数据结构】树、二叉树(下篇)_查找二叉树中值为x的结点用static对吗

查找二叉树中值为x的结点用static对吗

假设,我手头有 20张100元的和2000张1元的奖券,同时洒向了空中,大家比赛看谁最终捡的最多。如果是你,你会怎么做?

相信所有同学都会说,一定先捡 100 元的。道理非常简单,因为捡一张100元等1元的捡100 张,效率好得不是一点点。所以可以得到这样的结论,同样是捡奖券,在有限时间内,要达到最高效率,次序非常重要。对于二叉树的遍历来讲,次序同样显得很重要。

                                   超乎一切之上的一件事,就是保持青春朝气。——莎士比亚 

文章目录

  • 一、二叉树的遍历原理
  • 二、二叉树的前序、中序、后序、层序遍历
  • 三、二叉树的拓展:判断树的高度、每层的结点个数等
  • 四、二叉树oj题
  • 总结

提示:以下是本篇文章正文内容,下面案例可供参考

一、二叉树的遍历原理

1.1原理:

二叉树的遍历(traveing binary tree)是指从根结点出发,按照某种次序依次访问二叉树中所有的结点,使每个结点都被访问一次,且仅被访问一次。

        这里有两个关键词:访问和次序。

1.2.1访问

        访问其实是要根据实际的需要来确定具体做什么,比如对每个结点进行相关计算,输出打印等,它算作是一个抽象操作。在这里我们可以简单地假定就是输出结点的数据信息。

1.2.2次序

        二叉树的遍历次序不同于线性结构,最多也就是从头至尾、循环、双向等简单的遍历方式。树的结点之间不存在唯一的前驱和后继关系,在访问一个结点后,下一个被访问的结点面临着不同的选择就像你人生的道路上,高考填志愿要面临哪个城市、哪所大学、具体专业等选择,由选择方式的不同,遍历的次序就完全不同了。


二、二叉树的前序、中序、后序遍历

 2.1二叉树遍历的几种方式

        二叉树的遍历方式可以很多,如果我们限制了从左到右的习惯方式,那么主要就分为四种:

前序遍历、中序遍历、后序遍历、层次遍历。

这四种遍历方式的基本顺序和在数组中存储的形式如下图所示:

 2.2前序遍历

        规则是若 叉树为空,则空操作返回,否则先访问根结点,然后前序遍历左子树,再前序遍历右子树。如图遍历的顺序为: ABDGHCEIF

步骤:1、先造一颗树

造树:

  1. #include<stdio.h>
  2. #include<stdlib.h>
  3. typedef int BTDataType;
  4. typedef struct BinaryTreeNode
  5. {
  6. struct BinaryTreeNode* left;
  7. struct BinaryTreeNode* right;
  8. BTDataType data;
  9. }BTNode;
  10. malloc一块空间
  11. BTNode* BuyBTNode(BTDataType x)
  12. {
  13. BTNode* node = (BTNode*)malloc(sizeof(BTNode));
  14. if (node == NULL)
  15. {
  16. printf("malloc fail\n");
  17. exit(-1);
  18. }
  19. node->data = x;
  20. node->left = node->right = NULL;
  21. return node;
  22. }
  23. 紧接着实现链接
  24. BTNode* CreatBinaryTree()
  25. {
  26. BTNode* node1 = BuyBTNode(1);
  27. BTNode* node2 = BuyBTNode(2);
  28. BTNode* node3 = BuyBTNode(3);
  29. BTNode* node4 = BuyBTNode(4);
  30. BTNode* node5 = BuyBTNode(5);
  31. BTNode* node6 = BuyBTNode(6);
  32. node1->left = node2;
  33. node1->right = node4;
  34. node2->left = node3;
  35. node4->left = node5;
  36. node4->right = node6;
  37. return node1;
  38. }

2、写前序遍历和main函数

  1. void PrevOrder(BTNode* root)//前序遍历
  2. {
  3. if (root == NULL)//如果根是空就return
  4. {
  5. printf("NULL ");
  6. return;
  7. }
  8. printf("%d ", root->data);
  9. PrevOrder(root->left);//左子树
  10. PrevOrder(root->right);//右子树
  11. }
  12. int main()
  13. {
  14. BTNode* tree = CreatBinaryTree();
  15. PrevOrder(tree);
  16. return 0;
  17. }

程序运行结果 :(对照1、2、3、4、5、6)上图

 2.3中序遍历

        规则是若树为空,则空操作返回,否则从根结点开始(注意并不是先访问根结 点) ,中序遍历根结点的左子树,然后是访问根结点,最后中序遍历右子树 如图所示, 遍历的顺序为GDHBAELCF.

  1. void InOrder(BTNode* root)//中序遍历
  2. {
  3. if (root == NULL)//如果根是空就return
  4. {
  5. printf("NULL ");
  6. return;
  7. }
  8. InOrder(root->left);//先左子树
  9. printf("%d ", root->data);
  10. InOrder(root->right);//再右子树
  11. }
  12. int main()
  13. {
  14. BTNode* tree = CreatBinaryTree();
  15. InOrder(tree);
  16. return 0;
  17. }

程序运行结果 

  2.4后序遍历

        规则是若树为空,则空操作返回,否则从左到右先叶子后结点的方式遍历访问左右子树,最后是访问根结点 如图所示 遍历的顺序为 GHDBIEFCA。

  1. void BackOrder(BTNode* root)
  2. {
  3. if (root == NULL)//如果根是空就return
  4. {
  5. printf("NULL ");
  6. return;
  7. }
  8. BackOrder(root->left);
  9. BackOrder(root->right);
  10. printf("%d ", root->data);
  11. }
  12. int main()
  13. {
  14. BTNode* tree = CreatBinaryTree();
  15. BackOrder(tree);
  16. return 0;
  17. }

 程序运行结果:

二叉树的遍历的几种路径 (小结):网上找的图

2.5层序遍历

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

层序遍历与之前的三种遍历情况有所不同,层序遍历的实现依赖于队列,会比较麻烦一些。

 

  1. //层序遍历
  2. void LevelOrder(BTNode* root)
  3. {
  4. Queue q;
  5. QueueInit(&q);
  6. if (root)
  7. {
  8. QueuePush(&q, root);//先插入根节点
  9. }
  10. while (QueueEmpty(&q))
  11. {
  12. BTNode* front = QueueFront(&q);
  13. QueuePop(&q);//把队列里根节点的指针拿出来,但是指针指向的节点的值没有被销毁;
  14. printf("%d ", front->data);
  15. if (front->left)
  16. {
  17. QueuePush(&q, front->left);
  18. }
  19. if (front->right)
  20. {
  21. QueuePush(&q, front->right);
  22. }
  23. printf("\n");
  24. }
  25. QueueDestry(&q);
  26. }

 三、二叉树的拓展

3.1计算树的结点的个数

low版(代码比较挫)但是容易理解

  1. int count = 0;
  2. void BTreeSize(BTNode* root)
  3. {
  4. if (root == NULL)
  5. return;
  6. ++count;
  7. BTreeSize(root->left);
  8. BTreeSize(root->right);
  9. //后序
  10. }

注意:

这里为什么不用static静态变量。

因为静态变量在静态区,是整个程序结束后才销毁,而且局部静态变量不能置零

所以如果再计算下一个树的结点就会和上一个树累加。

static只初始化一次,所以要么就是全局静态变量。

具体的调用方法://更好的计数方法,既不使用全局,也不使用静态变量-

//思想遍历加计数(传地址调用)指针

  1. void BTreeSize(BTNode* root, int* pCount)
  2. {
  3. if (root == NULL)
  4. return;
  5. ++(*pCount);//把一个变量的地址传过去
  6. BTreeSize(root->left, pCount);
  7. BTreeSize(root->right, pCount);
  8. //后序
  9. }

 3.2计算树的叶子结点的个数

 

思路:叶子结点的左右结点都为空,递归+分治思想

因此:代码如下

  1. int BTreeLaafSize(BTNode* root)
  2. {
  3. if (root == NULL)
  4. return 0;
  5. if (root->left == NULL && root->right == NULL)
  6. return 1;
  7. return BTreeLaafSize(root->left) + BTreeLaafSize(root->right);
  8. }

 3.3怎么求第k层节点的个数?

核心思路:递归返回第k-1层左右结点相加的值

  1. int BTreekLeafSize(BTNode* root, int k)
  2. {
  3. assert(k >= 1);
  4. if (root == NULL) return 0;
  5. if (k == 1) return 1;
  6. return BTreekLeafSize(root->left, k - 1) + BTreekLeafSize(root->right, k - 1);//返回左结点和右结点的上一层
  7. }

 3.4求一棵树的高度

思想:比较左右子树的高度,并且返回高度大的加一(原因:加根结点)

  1. int BTreeDepth(BTNode* root)
  2. {
  3. if (root == NULL)
  4. return 0;
  5. int leftDepth = BTreeDepth(root->left);
  6. int rightDepth = BTreeDepth(root->right);
  7. return leftDepth > rightDepth ? leftDepth + 1 : rightDepth + 1;
  8. }

 3.5二叉树查找值为x的结点

 

思路:用前序遍历去递归搜索,先搜左子树,如果左子树没有,就返回一个NULL到根结点,然后根结点再递归搜索右树,如果右树有就返回那个点的值。

  1. BTNode* BTreeFind(BTNode* root, BTDataType x)
  2. {
  3. if (root == NULL)
  4. return NULL;
  5. if (root->data == x)
  6. return root;
  7. //用前序遍历的思想
  8. BTNode* ret1 = BTreeFind(root->left, x);//先去递归左树
  9. if (ret1)//如果左边返回的不是NULL
  10. {
  11. return ret1;
  12. }
  13. BTNode* ret2 = BTreeFind(root->right, x);
  14. if (ret2)
  15. {
  16. return ret2;
  17. }
  18. return NULL;
  19. }

3.6判断一棵树是不是完全二叉树 

思路:就是把空也当作二叉树的节点放进去,然后运用层序遍历,

如果在遍历的中间过程中遇到空就说明不是完全二叉树。

队列不能直接像数组一样遍历 

 

  1. //判断一棵树是不是完全二叉树
  2. bool BinaryTreeComplete(BTNode* root)
  3. {
  4. Queue q;
  5. QueueInit(&q);
  6. if (root)
  7. {
  8. QueuePush(&q, root);//先插入根节点
  9. }
  10. while (!QueueEmpty(&q))
  11. {
  12. BTNode* front = QueueFront(&q);//会等于队头第一个数据的值
  13. QueuePop(&q);
  14. if (front == NULL)
  15. {
  16. break;
  17. }
  18. QueuePush(&q, front->left);
  19. QueuePush(&q, front->right);
  20. }
  21. while (!QueueEmpty(&q))
  22. {
  23. BTNode* front = QueueFront(&q);//会等于队头第一个数据的值
  24. QueuePop(&q);
  25. if (front)//如果出到非空,那么就不是完全二叉树
  26. {
  27. return false;
  28. }
  29. }
  30. return true;
  31. }

 

 四、二叉树oj题

1、965. 单值二叉树 - 力扣(LeetCode)

  1. bool isUnivalTree(struct TreeNode* root)
  2. {
  3. if(root == NULL) return true;
  4. if(root->left && root->left->val != root->val)//如果左结点不为空,且左树结点的值不等于根的值,返回false
  5. return false;
  6. if(root->right && root->right->val != root->val)//如果右结点不为空,且右树结点的值不等于根的值,返回false
  7. return false;
  8. return isUnivalTree(root->left) && isUnivalTree(root->right);//递归判断
  9. }

 2、100. 相同的树 - 力扣(LeetCode)

 

思路:先判断两棵树是不是都是空树,再判断如果一个为空一个不为空,最后递归

  1. bool isSameTree(struct TreeNode* p, struct TreeNode* q)
  2. {
  3. //判断两个树的根是不是都为空
  4. if(p == NULL && q == NULL)
  5. return true;
  6. //判断两个树的其中一颗树的结点为空时,另一个不为空
  7. if(p == NULL || q == NULL)
  8. return false;
  9. //判断两个树的根节点是否是同值
  10. if(p->val != q->val)
  11. return false;
  12. //递归判断
  13. return isSameTree(p->left, q->left) && isSameTree(p->right, q->right);
  14. }
  15. 注意:这里的判断不能写成p->left->val != q->left->val
  16. 会报错

 3、101. 对称二叉树 - 力扣(LeetCode)

思路:写一个辅助函数,舍弃根结点,判断左边这个树是否与右边这个树对称

  1. bool isSymmetricTree(struct TreeNode* p, struct TreeNode* q)
  2. {
  3. //如果root的左和右节点都为空
  4. if(p == NULL && q == NULL)
  5. return true;
  6. //如果一个为空一个不为空
  7. if(p == NULL || q == NULL)
  8. return false;
  9. return p->val == q->val
  10. && isSymmetricTree(p->left, q->right)
  11. && isSymmetricTree(p->right, q->left);
  12. }
  13. bool isSymmetric(struct TreeNode* root)
  14. {
  15. if(root == NULL)
  16. return true;
  17. return isSymmetricTree(root->left, root->right);
  18. }

 4、144. 二叉树的前序遍历 - 力扣(LeetCode)

 题目意思解释:Note: The returned array must be malloced, assume caller calls free().

这句话的意思是数组要malloc, 然后caller系统会帮你free掉

int* returnSize的意思是返回结点的个数

代码如下所示:

  1. int TreeSize(struct TreeNode* root)//计算树的结点个数,方便malloc空间
  2. {
  3. return root == NULL ? 0 : TreeSize(root->left) +
  4. TreeSize(root->right) + 1;
  5. }
  6. //定义一个子函数去完成前序遍历
  7. void _preorder(struct TreeNode* root, int* a,int *pi)
  8. {
  9. if(root == NULL)
  10. return;
  11. a[(*pi) ++] = root->val;//控一下优先级*的优先级低于++,所以要加()
  12. _preorder(root->left, a, pi);
  13. _preorder(root->right, a, pi);
  14. }
  15. int* preorderTraversal(struct TreeNode* root, int* returnSize)//int*returnSize是输出型参数
  16. {
  17. int size = TreeSize(root);
  18. //不考虑动态扩容
  19. int* a = malloc(sizeof(int)*size);
  20. int i = 0;
  21. *returnSize = size;
  22. _preorder(root, a, &i);
  23. return a;
  24. }

 因为之后的二叉树中序以及后序遍历思路差不多,所以如果读者有兴趣可以根据这个思路去做。

5、572. 另一棵树的子树 - 力扣(LeetCode)

 

 

思路:左边树中每一个子树都比较isSameTree

遍历左边的每个节点,做子树的根,跟右边的子树都比较一下isSameTree

 

  1. bool isSameTree(struct TreeNode* p, struct TreeNode* q)
  2. {
  3. //两棵树的根都为空
  4. if (p == NULL && q == NULL)
  5. return true;
  6. //只有一颗树为空 此时已经有同位置的节点不相等
  7. if(p == NULL || q == NULL)
  8. return false;
  9. if(p->val != q->val)
  10. return false;
  11. return isSameTree(p->left, q->left) && isSameTree(p->right, q->right);
  12. }
  13. bool isSubtree(struct TreeNode* root, struct TreeNode* subRoot)
  14. {
  15. if(root == NULL)
  16. return;
  17. if(isSameTree(root, subRoot))
  18. {
  19. return true;
  20. }
  21. return isSubtree(root->left, subRoot) || isSubtree(root->right, subRoot);
  22. }

 6.二叉树遍历_牛客题霸_牛客网 (nowcoder.com)

 先序遍历字符串构造的二叉树(前序)

 递归、分治的思想

 

  1. #include<stdio.h>
  2. #include<string.h>
  3. //构造结构体
  4. typedef struct BinaryTreeNode
  5. {
  6. char data;
  7. struct BinaryTreeNode* left;
  8. struct BinaryTreeNode* right;
  9. }BTNode;
  10. //造树
  11. BTNode* CreatTree(char* a, int* pi)
  12. {
  13. if(a[*pi] == '#')
  14. {
  15. (*pi)++;
  16. return NULL;
  17. }
  18. BTNode* root = (BTNode*)malloc(sizeof(BTNode));
  19. root->data = a[(*pi)++];
  20. root->left = CreatTree(a, pi);
  21. root->right = CreatTree(a, pi);
  22. return root;
  23. }
  24. void InOrder(BTNode* root)
  25. {
  26. if(root == NULL)//这里root直接等于NULL判断便可,不需要‘#
  27. {
  28. return;
  29. }
  30. InOrder(root->left);
  31. printf("%c ", root->data);
  32. InOrder(root->right);
  33. }
  34. //main函数部分
  35. int main ()
  36. {
  37. char a[101];
  38. scanf("%s", a);
  39. int i = 0;
  40. BTNode* tree = CreatTree(a, &i);
  41. InOrder(tree);
  42. return 0;
  43. }

总结

         本文写了近8000字大致总结了二叉树的遍历算法,从二叉树的遍历原理、以及四个常见的二叉树的遍历算法:前、中、后、层序遍历算法,以及二叉树衍生出的6个拓展问题,6道二叉树oj题四个点展开,争取把二叉树的遍历讲透,希望大家读后能够有所收获。

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

闽ICP备14008679号