[学懂数据结构]简单实现二叉树链式结构(篇二)

242 阅读7分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第23天,点击查看活动详情

前言

        在讲完线性表后,接下来要介绍树结构中的二叉树,本文是基于C语言实现的。

        本文就来分享一波作者对数据结构二叉树的学习心得与见解。本篇属于第十一篇,继续介绍二叉树链式结构的相关内容,建议阅读本文之前先把前面的文章看看。

        笔者水平有限,难免存在纰漏,欢迎指正交流。

层序遍历

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

image-20220810090631143

        这是一种非递归遍历,需要借助一下队列。使用队列,先让根结点入队列,当出队列的时候让它下一层的左右结点入队列,空结点就不用入。

        要用到队列的话就要把之前写过的队列结构及操作的代码拿出来用。注意队列元素怎么设计?应该设计成二叉树结点指针类型,这样的话当结点出队列的时候就可以把左右子树结点带进去。

        按照出队列的顺序访问结点并打印结点的值,顺序正好就是层序遍历的顺序。

 void TreeLevelOrderPrint(BTNode* root)
 {
     Que q;
     QueueInit(&q);
 ​
     if (root == NULL)
         return;
     
     QueuePush(&q, root);
 ​
     while (!QueueEmpty(&q))
     {
         BTNode* front = QueueFront(&q);
         QueuePop(&q);
         printf("%d ", front->data);
 ​
         if (front->left)
             QueuePush(&q, front->left);
 ​
         if (front->right)
             QueuePush(&q, front->right);
 ​
     }
     printf("\n");
 }

判断二叉树是否是完全二叉树

        能不能用层数和结点数来判断?不能,非完全二叉树和完全二叉树差别很大,最重要的是完全二叉树要保证前k-1层是连续的,如果是满二叉树倒可以用层数和结点数关系来判断。

        不妨用层序遍历,因为该遍历是一层一层、一个一个地走,遇到空以后,也要把空放入队列,后续遍历过程中不能有非空的,有非空就不是完全二叉树。

 bool IsBinaryTreeComplete(BTNode* root)
 {
     Que q;
     QueueInit(&q);
     if(root == NULL)
         return;
 ​
     QueuePush(&q, root);
     while(!QueueEmpty(&q))
     {
         BTNode* front = QueueFront(&q);
         QueuePop(&q);
         if(front == NULL)
             break;
         
         QueuePush(&q, root->left);
         QueuePush(&q, root->right);
     }
     
     while(!QueueEmpty(&q))
     {
         BTNode* front = QueueFront(&q);
         QueuePop(&q);
         if(front != NULL)
         {
             QueueDestory(&q);
             return false;
         }
     }
     
     return true;
 }

求二叉树结点数

        使用分治思想,将原问题划分为类同的若干个子问题再解决。要算整个二叉树的结点数,可以每次都划分为算当前根结点和左右子树的结点数,如图

image-20220809120057798

举个例子

        打个生活中的比方就是:学院要查人数,有可能让领导自己挨个地去统计吗?当然不可能,领导会把任务分解后派发下去给辅导员,辅导员分解任务到班级层面,把任务交给各班班长,班长再分解任务到宿舍层面,把任务交给宿舍长,那宿舍长还能再分解任务么?不能了,已经是最底层的了,要查人数直接瞟一眼宿舍就知道哪个在哪个不在了,然后各个宿舍长把自己宿舍的人数上报给各个班长,班长再把班级各个宿舍的人数统计后上报给辅导员,辅导员再把各个班级人数统计后上报给领导,最后领导就得到了学院人数情况。

image-20220809162432067

        如果遇到空结点返回0,非空结点的话返回左右子树结点和+1(加1是因为要把根结点也算上)。

 int TreeSize(BTNode* root)
 {
     return (root == NULL) ? 0 : TreeSize(root->left) + TreeSize(root->right) + 1;
 }

递归图解

二叉树递归求结点个数.png

image-20220810183746828

image-20220810183757468

求二叉树叶结点数

        其实这个就是上面那个问题的进阶版,因为上面求总结点数是遍历树,遇到非空结点就让返回值+1,而当前这个问题相当于增加了一个限制条件:叶结点,思路还是那么个思路,不断分解子问题到左右子树。

        遇到空结点不计入,遇到叶结点就返回1,而遇到非空且非叶的结点就返回左右子树的叶结点数之和。

 int TreeLeafNode(BTNode* root)
 {
     if(root == NULL)
         return 0;
         
     if(root->left == NULL && root->right == NULL)
         return 1;
     
     return TreeLeafNode(root->left) + TreeLeafNode(root->right);
 }

递归图示

image-20220810153503572

求二叉树的高度

        问题可以分解为求左右子树分别的高度,取较大的那个+1(加1是因为要把根结点那一层算上)。

 int TreeHeight(BTNode* root)
 {
     if(root == NULL)
         return 0;
     
     int leftHeight = TreeHeight(root->left);
     int rightHeight = TreeHeight(root->right);
     return leftHeight > rightHeight ? LeftHeight + 1 : rightHeight + 1;
 }
 ​

递归图解

image-20220810155044916

求二叉树第k层结点数

        问题转换成求取左右子树的分别的第k - 1层结点数之和。

        比如说以A为根结点,那么H所在层次是第四层,而以B为根结点,那么H所在的层次就是第3层,以E为根结点的话,H所在层次就是第2层,再以H为根结点的话,H所在层次就是第1层了,这时候就找到原先的第4层了,也就是说,随着问题分解,k逐渐递减,当递减到为1时,就说明到了我们要求的那一层次了。

image-20220810173128575

 int TreeKLevelSize(BTNode* root, int k)
 {
     assert(k > 0);
     
     if(root == NULL)
         return 0;
         
     if(k == 1)
         return 1;
     
     return TreeKLevelSize(root->left, k - 1) + TreeKLevelSize(root->right, k - 1);  
 }

递归图解

image-20220810175210322

二叉树查找值为x的结点

        还是用分治思想不断分解问题后求解,问题分解为到左右子树去查找。先看当前根结点的值是不是查找的值,不是的话去左子树找,找不到再去右子树找,还是找不到就说明当前子树结构中找不到目标值,就返回NULL。

 BTNode* TreeFind(BTNode* root, BTDataType x)
 {
     if(root == NULL)
         return NULL;
     if(root->data == x)
         return root;
         
     BTNode* left = TreeFind(root->left, x);
     if(left)
         return left;
         
     BTNode* right = TreeFind(root->right, x);
     if(right)
         return right;
     
     return NULL;
 }

递归图解

image-20220810183134385

根据先序遍历结果构建二叉树

        例如如下的先序遍历字符串: ABC##DE#G##F### 其中“#”表示的是空格,空格字符代表空树。建立起二叉树。

        字符串用字符数组装,要构建二叉树要同时遍历字符串,用下标,不过要传下标地址,这样才能遍历字符串。

        给了前序遍历的结果,那我们就按照它来构建二叉树。前序遍历的顺序是:根结点->左子树->右子树,我们从根结点开始,如果不是空结点就创建结点并赋值,同时创建它的左右子树的结点并链接,函数有返回值,返回的就是结点指针,空结点直接返回NULL

 typedef char BTDataType;
 typedef struct BinaryTreeNode
 {
     BTDataType data;
     struct BinaryTreeNode* left;
     struct BinaryTreeNode* right;
 }BTNode;
 ​
 BTNode* BinaryTreeCreate(BTDataType* a, int* pi)
 {
     if(a[*pi] == '#')
     {
         (*pi)++;
         return NULL;  
     }
            
     BTNode* newNode = (BTNode*)malloc(sizeof(BTNode));
     if(newNode == NULL)
     {
         perror("malloc fail");
         exit(-1);
     }
     
     newNode->data = a[*pi];
     (*pi)++;
     
     newNode->left = BinaryTreeCreate(a, pi);
     newNode->right = BinaryTreeCreate(a, pi);
     return newNode;
 }

二叉树销毁

        二叉树的结点都是动态开辟的,在使用完后需要销毁,那如何销毁呢?要把每一个结点都free掉,那可以从上向下释放吗?不行,释放了就找不到下面的结点了,所以要从下往上释放。其实也就是后序遍历来释放。

 void BinaryTreeFree(BTNode* root)
 {
     if(root == NULL)
         return;
     
     BinaryTreeFree(root->left);
     BinaryTreeFree(root->right);
     free(root);
 }

二叉树oj题推荐

  1. 单值二叉树。965. 单值二叉树 - 力扣(LeetCode)
  2. 检查两颗树是否相同。100. 相同的树 - 力扣(LeetCode)
  3. 对称二叉树。101. 对称二叉树 - 力扣(LeetCode)
  4. 二叉树的前序遍历。144. 二叉树的前序遍历 - 力扣(LeetCode)
  5. 二叉树中序遍历 。94. 二叉树的中序遍历 - 力扣(LeetCode)
  6. 二叉树的后序遍历 。145. 二叉树的后序遍历 - 力扣(LeetCode)
  7. 另一颗树的子树。572. 另一棵树的子树 - 力扣(LeetCode)
  8. 二叉树遍历和构建二叉树遍历牛客题霸牛客网 (nowcoder.com)

以上就是本文全部内容,感谢观看,你的支持就是对我最大的鼓励~

src=http___c-ssl.duitang.com_uploads_item_201708_07_20170807082850_kGsQF.thumb.400_0.gif&refer=http___c-ssl.duitang.gif