二叉树基本题
前序、中序、后续三种遍历方法(递归)
二叉树的前序、中序和后序遍历是常用的三种遍历方式,它们定义了节点的访问顺序。假设有一个二叉树如下:
A
/ \
B C
/ \ \
D E F
- 前序遍历(Preorder Traversal):首先访问根节点,然后按照先左后右的顺序遍历子树。对于上述二叉树,前序遍历结果为:A -> B -> D -> E -> C -> F。
- 中序遍历(Inorder Traversal):按照先左后根再右的顺序遍历子树。对于上述二叉树,中序遍历结果为:D -> B -> E -> A -> C -> F。
- 后序遍历(Postorder Traversal):按照先左后右再根的顺序遍历子树。对于上述二叉树,后序遍历结果为:D -> E -> B -> F -> C -> A。
先给出三种遍历的基本模板
void Traverse(Node* root)
{
if (root == NULL)
return;
//第一次在原函数
Traverse(root->leftchild);
//第二次在原函数,这次是经过右边的遍历之后返回来的
Traverse(root->rightchild);
//第三次在原函数,这次是分别经过左右两边遍历之后返回回来的
}
根据模板中对于三次回到原函数的时机不同,就可以衍生出三种不同的遍历方式
前序遍历就是在第一次在原函数时就对结点进行操作,然后再对左右两边进行操作
代码如下
前序遍历:
void PrevOrder(Node* root)
{
if (root == NULL)
return;
printf("%d ", root->val);
PrevOrder(root->leftchild);
PrevOrder(root->rightchild);
}
看懂了前序遍历,中序遍历和后续遍历自然也就懂了
中续遍历:
void InOrder(Node* root)
{
if (root == NULL)
return;
InOrder(root->leftchild);
printf("%d ", root->val);
InOrder(root->rightchild);
}
后续遍历
void PostOrder(Node* root)
{
if (root == NULL)
return;
PostOrder(root->leftchild);
PostOrder(root->rightchild);
printf("%d ", root->val);
}
求二叉树节点个数
思路:用后续遍历的思想(其实前序、中序都可以),遍历整个二叉树,当遇到空节点时返回0,代表0个节点,不然就返回1加上该节点下左右两个子树的节点,其含义代表的是以当前节点为父节点作为子树的所有节点数量。
代码实现:
代码实现
int TreeSize(Node* root)
{
if (root == NULL)
return 0;
return 1 + TreeSize(root->leftchild) + TreeSize(root->rightchild);
}
求二叉树叶子节点个数
思路:首先要明确什么是叶子节点,叶子节点就是左右两个节点都是空节点的节点。
其次,我们要清楚当遇到左右两个节点都是空结点的情况和遇到空结点的情况
即:
当遇到空节点时,直接返回0就可以了,但是不要忽略这种情况
代码实现
int TreeLeafSize(Node* root)
{
if (root == NULL)
return 0;
if (root->leftchild == NULL && root->rightchild == NULL)
return 1;
return TreeLeafSize(root->leftchild) + TreeLeafSize(root->rightchild);
}
求二叉树第k层节点的个数
当我们要求第k层节点数时,我们需要知道一下两点
- 每当向子树方向移动时,层数便会增加一层
- 相对于当前节点的第k层,其实就是相当于子节点的第k-1层,举个例子:二叉树的第三层,是相对于根节点的第三层,但是对于根节点的两个孩子节点来说,就是它们的第2层。
弄明白了以上两点,这个题目就已经做出了一半了
代码实现
int BinaryTreeLevelKSize(Node* root, int k)
{
//当还未达到第k层时就已经碰到了空结点,说明从该该节点下去是没有第k层的
if (root == NULL)
return 0;
//当k==1时,说明已经到了我们要找的第k层
if (k == 1)
{
if (root == NULL)
return 0;
else
return 1;
}
//当前树的第k层的节点=左子树的第k-1层节点+右子树的第k-1层节点
return BinaryTreeLevelKSize(root->left, k - 1) + BinaryTreeLevelKSize(root->right, k - 1);
}
在二叉树中查找值为x的节点
在这里主要用到了前序遍历的思想,首先判断当前节点是否时我们要找的节点,如果是的话,就直接返回,如果不是才分别向左右子树中去找
在这里需要注意的是,当我们在左子树找到时,就可以直接返回该节点,就没有必要再去右子树去找了
如果左右两边都没有找到,就返回空指针
代码实现
Node* BianryTreeFind(Node* root, DataType x)
{
//若发现当前节点为空,就没有必要再往下去找了,直接返回空
if (root == NULL)
return NULL;
//找到了就直接返回
if (root->data == x)
return root;
//先去左子树找,找到了直接返回,不用去右子树找
Node* ret = BianryTreeFind(root->left, x);
if (ret)
return ret;
ret = BianryTreeFind(root->right, x);
if (ret)
return ret;
return NULL;//两边都找不到就返回空
}
二叉树的销毁
对于二叉树的销毁,其实思想和二叉树的后续遍历很像,我们需要先完全销毁当前节点的左子树,然后销毁当前节点的右子树,最后才销毁该节点。
倘若其中顺序发生了变化,假如我们是先销毁根节点,再去销毁左右两颗子树,我们就会发现左右两棵子树找不到了
代码实现
void Destroy(Node* root)
{
if (root == NULL)
return;
Destroy(root->left);
Destroy(root->right);
free(root);
}
单值二叉树
读完题目后,我们可以知道这道题目需要利用到二叉树的遍历,通过遍历去看有没有不符合题意的节点,关于利用哪种遍历遍历方式,很显然,前序遍历是最适合这个题目的:当我们发现当前节点不满足题意时,就不用再去筛选其他节点了,直接返回false即可,
当我们一直往深处遍历遇到空时,就说明当前这条分支上的节点都是符合题意的,返回true即可
另外,当我们在左子树找到不符合题意的节点时,右子树就没有必要再去遍历了
怎么样,这道题是不是和《在二叉树中查找值为x的节点》的思考方式有异曲同工之妙?
代码实现
bool isUnivalTree(struct TreeNode* root){
//当遍历到空时,就直接返回
if(root==NULL)
return true;
//先看左孩子节点是不是符合题意
if(root->left&&root->left->val!=root->val)
return false;
//再看右孩子是不是符合题意
if(root->right&&root->right->val!=root->val)
return false;
//再去左右两边分别去找
return isUnivalTree(root->left)&&isUnivalTree(root->right);
}
需要注意的是最后一条语句,其中用到了&&操作符
回顾一下&&操作符
exp1 && exp2
当表达式一为假时,表达式二就不再执行;当表达式一为真时,表达式二才会执行
运用到这里的意思就是说当在树的左边找到不符合题意的节点返回false时,就不会再到右边去找了
检查两棵树是否相同
要比较两个树是否相同,需要两棵树按照一样的方式同时去遍历,只要其中有一个节点不一样就返回false,只有在两棵树都遍历到了空结点,就说明这两棵树的当前分支是相同的
代码实现
bool isSameTree(struct TreeNode* p, struct TreeNode* q){
//当左右两个节点都为空时,说明当前分支相同
if(p==NULL&&q==NULL)
return true;
//当走到这里时,一共有三种情况
//1、q和p都不为空
//2、q为空,p不为空
//3、q不为空,p不为空
//其中前两种情况都是不符合题意的,第三种情况还需要再判断
if(q==NULL||p==NULL)//筛选前两种情况
return false;
if(p->val!=q->val)//判断第三种情况
return false;
//当走到这里时,说明两颗树的当前节点都是符合题意的
//接着在分别往左右两边进行判断
return isSameTree(p->right,q->right)&&isSameTree(p->left,q->left);
}
对称二叉树
题目链接
这里其实可以复用《相同的树》的代码,当判断完根节点后,直接将根节点的左右孩子节点作为作为两个子树的根节点传到判断两棵树是否相同的代码中
需要注意的是这里是判断轴对称,即需要p节点的左孩子和q节点的右孩子进行比较,p节点的右孩子和q孩子的左节点进行比较
代码实现
bool isSameTree(struct TreeNode* p, struct TreeNode* q){
//两个结点都为空
if(p==NULL&&q==NULL)
return true;
//有一个结点不为空
if(p==NULL||q==NULL)
return false;
//当两个节点值不相同时
if(p->val!=q->val)
return false;
return isSameTree(p->left, q->right)&&isSameTree(p->right,q->left);
}
bool isSymmetric(struct TreeNode* root){
if(root==NULL){
return true;
}
return isSameTree(root->left,root->right);
}
另一颗树的子树
题目链接
这道题也可以复用《相同的树》的代码,当我们判断出root的某一个节点的值与subRoot的根节点的值相同时,就说明,以该节点作为根节点的子树有可能和subRoot相同,就拉出去比较,如果相同,就直接返回true,不用再继续找了;如果不同,就继续遍历
代码实现
bool isSameTree(struct TreeNode*p,struct TreeNode*q){
if(p==NULL&&q==NULL)
return true;
if(p==NULL||q==NULL)
return false;
if(p->val!=q->val)
return false;
return isSameTree(p->left,q->left)&&isSameTree(p->right,q->right);
}
bool isSubtree(struct TreeNode* root, struct TreeNode* subRoot){
//当找到空时,说明当前节点所在分支的所有子树都不符合题意,就返回false
if(root==NULL)
return false;
//当该节点与subPoot的根节点的值相同时,说明该节点可能符合题意,就进行判断
if(root->val==subRoot->val){
if(isSameTree(root,subRoot)){
return true;
}
}
//当有一边找到时返回true,根据||操作符的特点,另一边就不会再找了
return isSubtree(root->left,subRoot)||isSubtree(root->right,subRoot);//有一边找到了另一边就不需要再找了
}
层序遍历
层序遍历与前序、中序和后续遍历不同,后三者都是深度优先遍历(DFS),而层序遍历属于广度优先遍历(BFS),
A
/ \
B C
/ \ \
D E F
当前二叉树利用层序遍历的方式顺序就是A->B->C->D->E->F,可以看出来这种遍方式就不是依靠递归来做到的了
要完成层序遍历,需要借用队列这一数据结构
操作顺序如下
- 先将根节点放入到队列中(根节点不为空)
- 将队列头部的节点的左右孩子节点(空结点不入队列)分别进入队列,打印队列头部的值并将队列头部的值出队列
- 反复执行第二步,直到队列为空为止
代码实现
额,这个等博主学了C++再回来补上吧,埋个坑先
总结
这篇博客主要包含了博主再初次学习二叉树这一数据结构时,所收集整理一些基础题目的思路和解法,以后会不定期更新。