• 数据结构——二叉树


    目录

    前言

    一、二叉树链式结构

    二、二叉树的遍历

    2.1 前序遍历

    2.2 中序遍历

    2.3 后序遍历

    2.4 层序遍历

    三、二叉树的节点

    ​编辑

    3.1 二叉树节点个数

    3.2 二叉树的深度

    3.3 二叉树叶子节点个数

    3.4 二叉树第k层节点个数

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

    四、二叉树的创建和销毁

    4.1 二叉树的创建

    4.2 二叉树的销毁

    4.3 是否为完全二叉树

    总结


    前言

    我们之前讲了二叉树的顺序存储的一种——堆,今天我们来了解二叉树的链式存储。了解二叉树的遍历,和二叉树的节点。通过递归分治的思想来实现二叉树的功能。


    一、二叉树链式结构

    为了先了解到二叉树的基本操作,我们先手动创造一个二叉树(与后面的二叉树的创建无关)。等二叉树结构了解的差不多时,我们反过头再来研究二叉树真正的创建方式。

    二叉树的节点:

    1. typedef int BTDataType;
    2. typedef struct BTDataType {
    3. BTDataType val;
    4. struct BTDataType* left;//左子树
    5. struct BTDataType* right;//右子树
    6. }BTNode;

    创建一个节点:

    1. // 创建节点
    2. BTNode* BuyNode(BTDataType x) {
    3. BTNode* n = (BTNode*)malloc(sizeof(BTNode));
    4. if (n == NULL)
    5. {
    6. perror("malloc fail");
    7. }
    8. n->val = x;
    9. n->right = n->left = NULL;
    10. return n;
    11. }

    现在我们可以手动创建一个二叉树了

    1. //手动创建树
    2. BTNode* CreatBinaryTree()
    3. {
    4. BTNode* n1 = BuyNode(1);
    5. BTNode* n2 = BuyNode(2);
    6. BTNode* n3 = BuyNode(3);
    7. BTNode* n4 = BuyNode(4);
    8. BTNode* n5 = BuyNode(5);
    9. BTNode* n6 = BuyNode(6);
    10. n1->left = n2;
    11. n1->right = n4;
    12. n2->left = n3;
    13. n4->left = n5;
    14. n4->right = n6;
    15. return n1;
    16. }
    再看二叉树基本操作前,再回顾下二叉树的概念,二叉树是:
    1. 空树
    2. 非空:根节点,根节点的左子树、根节点的右子树组成的。

    从概念中可以看出,二叉树定义是递归式的,因此后序基本操作中基本都是按照该概念实现的。

    二、二叉树的遍历

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

    按照规则,二叉树的遍历有:前序/中序/后序的递归结构遍历
    1. 前序遍历 (Preorder Traversal 亦称先序遍历)——访问根结点的操作发生在遍历其左右子树之前。
    2. 中序遍历 (Inorder Traversal)——访问根结点的操作发生在遍历其左右子树之中(间)。
    3. 后序遍历 (Postorder Traversal)——访问根结点的操作发生在遍历其左右子树之后。

    由于被访问的结点必是某子树的根,所以N(Node)、L(Left subtree)和R(Right subtree)又可解释为根、根的左子树和根的右子树。NLR、LNR和LRN分别又称为先根遍历、中根遍历和后根遍历。
    1. // 二叉树前序遍历
    2. void PreOrder(BTNode* root);
    3. // 二叉树中序遍历
    4. void InOrder(BTNode* root);
    5. // 二叉树后序遍历
    6. void PostOrder(BTNode* root);

    2.1 前序遍历

    1. // 二叉树前序遍历
    2. void PreOrder(BTNode* root) {
    3. if (root == NULL)
    4. {
    5. printf("N ");
    6. return;
    7. }
    8. printf("%d ", root->val);
    9. PreOrder(root->left);
    10. PreOrder(root->right);
    11. }

    前序遍历的遍历顺序为:根   左子树   右子树

    2.2 中序遍历

    1. //二叉树中序遍历
    2. void InOrder(BTNode* root) {
    3. if (root == NULL)
    4. {
    5. printf("N ");
    6. return;
    7. }
    8. InOrder(root->left);
    9. printf("%d ", root->val);
    10. InOrder(root->right);
    11. }

    中序遍历的遍历顺序为:左子树  根   右子树

    2.3 后序遍历

    1. // 二叉树后序遍历
    2. void PostOrder(BTNode* root) {
    3. if (root == NULL)
    4. {
    5. printf("N ");
    6. return;
    7. }
    8. PostOrder(root->left);
    9. PostOrder(root->right);
    10. printf("%d ", root->val);
    11. }

    后序遍历的遍历顺序为:左子树   右子树  根

    三种遍历的运行结果为(其中N代表节点为空):

    2.4 层序遍历

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

    与上面三种通过递归来遍历二叉树的不同,层序遍历使用的循环来遍历二叉树。

    为了实现二叉树的层序遍历,我们通过之前学到的队列先进先出的特性来实现。

    1. //层序遍历
    2. void BinaryTreeLevelOrder(BTNode* root) {
    3. Queue pq;//创建队列
    4. QueueInit(&pq);
    5. if (root != NULL)
    6. QueuePush(&pq, root);
    7. while (!QueueEmpty(&pq))
    8. {
    9. BTNode* foront = QueueFront(&pq);//保存根节点
    10. QueuePop(&pq);
    11. if (foront != NULL)
    12. {
    13. printf("%d ", foront->val);//根节点出队列它的左右子树入队列
    14. QueuePush(&pq, foront->left);
    15. QueuePush(&pq, foront->right);
    16. }
    17. else
    18. {
    19. printf("N ");
    20. }
    21. }
    22. QueueDestroy(&pq);
    23. }

    运行结果:

    三、二叉树的节点

    了解到二叉树的遍历后,我们来求二叉树节点个数以及高度等,实现这些我们需要理解递归分治。

    找到递归子问题和返回条件(最小子问题)

    以下图为例:

    3.1 二叉树节点个数

    1. // 二叉树节点个数
    2. int BinaryTreeSize(BTNode* root) {
    3. return root==NULL?0:BinaryTreeSize(root->left) + BinaryTreeSize(root->right) + 1;
    4. }

    递归子问题:找根的左右子树的节点数

    返回条件:当节点为空时

    3.2 二叉树的深度

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

    递归子问题:找左右子树的深度最大的

    返回条件:当节点为空时

    注意:返回时本节点深度也要算,即返回深度要加1。

    3.3 二叉树叶子节点个数

    1. // 二叉树叶子节点个数
    2. int BinaryTreeLeafSize(BTNode* root) {
    3. if (root == NULL)
    4. return 0;
    5. if (root->left == NULL && root->right == NULL)
    6. return 1;
    7. return BinaryTreeLeafSize(root->left) + BinaryTreeLeafSize(root->right);
    8. }

    递归子问题:左右子树的叶子节点数的和

    返回条件:当节点为空时和左右子树同时为空

    3.4 二叉树第k层节点个数

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

    递归子问题:在k-1层左右子树的节点

    返回条件:当节点为空时和k等于1

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

    1. // 二叉树查找值为x的节点
    2. BTNode* BinaryTreeFind(BTNode* root, BTDataType x) {
    3. if (root == NULL)
    4. return NULL;
    5. if (root->val == x)
    6. return root;
    7. //如果找到了直接返回
    8. BTNode* ret1 = BinaryTreeFind(root->left, x);
    9. if (ret1)
    10. return ret1;
    11. BTNode* ret2 = BinaryTreeFind(root->right, x);
    12. if (ret2)
    13. return ret2;
    14. }

    递归子问题:在左和右子树分别寻找

    返回条件:当节点为空时和当节点找到

    四、二叉树的创建和销毁

    我们之前是通过手动来创建一个二叉树的,现在我们来实现二叉树真正的创建。

    4.1 二叉树的创建

    通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树,其中#代表节点为空
    1. //其中a[]="abc##de#g##f###",pi为下标
    2. BTNode* BinaryTreeCreate(BTDataType* a, int* pi){
    3. if(a[*pi]=='#')
    4. {
    5. (*pi)++;
    6. return NULL;
    7. }
    8. BTNode* root=(BTNode*)malloc(sizeof(BTNode));
    9. root->val=a[(*pi)++];
    10. //创建左子树
    11. root->left=BinaryTreeCreate(a,pi);
    12. //创建右子树
    13. root->right=BinaryTreeCreate(a,pi);
    14. return root;
    15. }

    这里的递归子问题为:分别创建左右子树

    返回条件:当遇到#即返回

    4.2 二叉树的销毁

    1. // 二叉树销毁
    2. void BinaryTreeDestory(BTNode** root) {
    3. if (root == NULL)
    4. return;
    5. //分别销毁节点的左右子树
    6. BinaryTreeDestory(&(*root)->left);
    7. BinaryTreeDestory(&(*root)->right);
    8. free(*root);
    9. *root = NULL;
    10. }

    我们这里使用二级指针来销毁二叉树,也可以用一级指针,只不过需要在函数外面将根节点手动置空。

    4.3 是否为完全二叉树

    1. //是否为完全二叉树
    2. bool BinaryTreeComplete(BTNode* root) {
    3. Queue pq;
    4. QueueInit(&pq);
    5. if (root != NULL)
    6. QueuePush(&pq, root);
    7. while (!QueueEmpty(&pq))
    8. {
    9. BTNode* foront = QueueFront(&pq);
    10. QueuePop(&pq);
    11. if (foront == NULL)
    12. break;
    13. QueuePush(&pq, foront->left);
    14. QueuePush(&pq, foront->right);
    15. }
    16. while (!QueueEmpty(&pq))
    17. {
    18. BTNode* foront = QueueFront(&pq);
    19. QueuePop(&pq);
    20. if (foront != NULL)
    21. return false;
    22. }
    23. return true;
    24. QueueDestroy(&pq);
    25. }

    我们运用层序遍历来解决这个问题,只要遇到空结点,判断后面节点是否为空,只要有一个不为空,就不是完全二叉树。


    总结

    以上文章,我们讲了二叉树的链式结构的遍历和实现,希望对你有所帮助。

  • 相关阅读:
    Vue学习(9月3号)
    打造千万级流量秒杀第十八课 热更新:如何解决程序升级中的稳定性问题?
    模拟实现memcpy memmove,字符类库函数的介绍,strerror,strtok的使用讲解。
    Flink Cep 源码分析
    消防安全知识答题小程序v3.0已迭代完成
    使用svnsync sync方法同步
    RabbitMQ
    前端进击笔记第二十二节 如何进行性能分析的自动化实现
    公司电脑文件加密防泄密软件系统——「天锐绿盾」
    java后端社招面试经历
  • 原文地址:https://blog.csdn.net/2301_76613753/article/details/138136864