携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第23天,点击查看活动详情
前言
在讲完线性表后,接下来要介绍树结构中的二叉树,本文是基于C语言实现的。
本文就来分享一波作者对数据结构二叉树的学习心得与见解。本篇属于第十一篇,继续介绍二叉树链式结构的相关内容,建议阅读本文之前先把前面的文章看看。
笔者水平有限,难免存在纰漏,欢迎指正交流。
层序遍历
层序遍历:除了先序遍历、中序遍历、后序遍历外,还可以对二叉树进行层序遍历。设二叉树的根节点所在层数为1,层序遍历就是从所在二叉树的根节点出发,首先访问第一层的树根节点,然后从左到右访问第2层 上的节点,接着是第三层的节点,以此类推,自上而下,自左至右逐层访问树的结点的过程就是层序遍历。
这是一种非递归遍历,需要借助一下队列。使用队列,先让根结点入队列,当出队列的时候让它下一层的左右结点入队列,空结点就不用入。
要用到队列的话就要把之前写过的队列结构及操作的代码拿出来用。注意队列元素怎么设计?应该设计成二叉树结点指针类型,这样的话当结点出队列的时候就可以把左右子树结点带进去。
按照出队列的顺序访问结点并打印结点的值,顺序正好就是层序遍历的顺序。
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;
}
求二叉树结点数
使用分治思想,将原问题划分为类同的若干个子问题再解决。要算整个二叉树的结点数,可以每次都划分为算当前根结点和左右子树的结点数,如图
举个例子
打个生活中的比方就是:学院要查人数,有可能让领导自己挨个地去统计吗?当然不可能,领导会把任务分解后派发下去给辅导员,辅导员分解任务到班级层面,把任务交给各班班长,班长再分解任务到宿舍层面,把任务交给宿舍长,那宿舍长还能再分解任务么?不能了,已经是最底层的了,要查人数直接瞟一眼宿舍就知道哪个在哪个不在了,然后各个宿舍长把自己宿舍的人数上报给各个班长,班长再把班级各个宿舍的人数统计后上报给辅导员,辅导员再把各个班级人数统计后上报给领导,最后领导就得到了学院人数情况。
如果遇到空结点返回0,非空结点的话返回左右子树结点和+1(加1是因为要把根结点也算上)。
int TreeSize(BTNode* root)
{
return (root == NULL) ? 0 : TreeSize(root->left) + TreeSize(root->right) + 1;
}
递归图解
求二叉树叶结点数
其实这个就是上面那个问题的进阶版,因为上面求总结点数是遍历树,遇到非空结点就让返回值+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);
}
递归图示
求二叉树的高度
问题可以分解为求左右子树分别的高度,取较大的那个+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;
}
递归图解
求二叉树第k层结点数
问题转换成求取左右子树的分别的第k - 1层结点数之和。
比如说以A为根结点,那么H所在层次是第四层,而以B为根结点,那么H所在的层次就是第3层,以E为根结点的话,H所在层次就是第2层,再以H为根结点的话,H所在层次就是第1层了,这时候就找到原先的第4层了,也就是说,随着问题分解,k逐渐递减,当递减到为1时,就说明到了我们要求的那一层次了。
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);
}
递归图解
二叉树查找值为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;
}
递归图解
根据先序遍历结果构建二叉树
例如如下的先序遍历字符串: 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题推荐
- 单值二叉树。965. 单值二叉树 - 力扣(LeetCode)
- 检查两颗树是否相同。100. 相同的树 - 力扣(LeetCode)
- 对称二叉树。101. 对称二叉树 - 力扣(LeetCode)
- 二叉树的前序遍历。144. 二叉树的前序遍历 - 力扣(LeetCode)
- 二叉树中序遍历 。94. 二叉树的中序遍历 - 力扣(LeetCode)
- 二叉树的后序遍历 。145. 二叉树的后序遍历 - 力扣(LeetCode)
- 另一颗树的子树。572. 另一棵树的子树 - 力扣(LeetCode)
- 二叉树遍历和构建二叉树遍历牛客题霸牛客网 (nowcoder.com)
以上就是本文全部内容,感谢观看,你的支持就是对我最大的鼓励~