目录
前言
数据结构算法的两大难关之一,一个是二叉树,另一个就是图论。在这之前,我经过了四天的刷题,累计在LeetCode攻克了将近100道二叉树的题目,即使目前自感本人水平依然有限,但也希望能在本文中尽力去通俗易懂的带有体系的讲解给大家。这中间自然少不了借鉴LeetCode一些大佬的归纳总结以及模板,希望我们一起努力,攻克这座大山!
二叉树的遍历系列
二叉树的遍历总共有四种:前,中,后,层。大家一定要非常熟练的掌握这四种遍历方式,同时也要深刻的理解它的特性(注意,你目前所以为的理解到位往往还只处在表面阶段),因为这是我们后面打开二叉树的其他类型算法大门的钥匙,在这里我也会给出递归和迭代两种方法,大家看到递归往往就开始头疼了,但是在这里我介绍一种递归三部曲的方式,它在本章中会大放光彩的。
第一曲:函数返回值以及参数(这个大部分时候不能一开始就确定,需要逐步确定)
第二曲:终止条件
第三区:单层递归逻辑
在后面涉及到的递归算法我都会按照这三步来说明。
除此之外,说明一下代码里面构造二叉树的数据输入:均为按照先序输入一棵二叉树,其中空节点用 -1 表示。
如图的输入方式:3 5 6 -1 -1 2 7 -1 -1 4 -1 -1 1 0 -1 -1 8 -1 -1
二叉树的前序遍历
第一曲:确定函数返回值以及参数
遍历过程我们只需要做的事就是将遍历到的节点纳入结果数组即可,所以返回值为void,而参数只要一颗二叉树就行了
第二曲:终止条件
遍历到空节点的时候返回就行了,没有继续遍历下去的必要了
第三曲:单层递归逻辑
前序遍历的过程就是我们的单层递归逻辑:先访问根节点,再访问左子树,再访问右子树
递归
#include<bits/stdc++.h>
using namespace std;
struct TreeNode{
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int x):val(x),left(NULL),right(NULL){}; //节点构造函数
};
vector<int>result;
void preorderTraversal(TreeNode* root){ //前序遍历二叉树--递归版
//终止条件
if(!root) return; //空节点返回
//单层递归逻辑
result.push_back(root->val); //访问根节点
preorderTraversal(root->left); //访问左子树
preorderTraversal(root->right); //访问右子树
}
void BuildTree(TreeNode* &root){ //前序输入构建一颗二叉树
int x;
scanf("%d",&x);
if(x==-1) {
root=NULL;
return; //空节点直接返回
}
root=new TreeNode(x); //构造根节点
BuildTree(root->left); //构造左子树
BuildTree(root->right); //构造右子树
}
int main(){
TreeNode* root;
BuildTree(root); //构造二叉树
preorderTraversal(root); //前序遍历
for(int i=0;i<result.size();i++) //输出结果序列
printf("%d ",result[i]);
return 0;
}
迭代
由于遍历二叉树的过程是一个递归的过程,在递归过程中我们调用的其实是系统栈,所以系统栈能做到的事,我们同样可以靠自己定义一个栈来解决,那么如何做呢?我们结合前序遍历和栈的特点,以下图为例,先入栈根节点,然后出栈访问它,那么这时候我们入栈的顺序必须是先右子树,再左子树,因为栈是先进后出的,但我们要先访问左子树,所以左子树要比右子树后入栈。
代码:
#include<bits/stdc++.h>
using namespace std;
struct TreeNode{
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int x):val(x),left(NULL),right(NULL){}; //节点构造函数
};
vector<int>result;
void preorderTraversal(TreeNode* root){ //前序遍历二叉树--迭代版
if(!root) return; //空节点直接返回
stack<TreeNode*>st;
st.push(root); //根节点入栈
while(!st.empty()){ //栈空结束
TreeNode* cur=st.top();st.pop(); //根
result.push_back(cur->val);
if(cur->right) st.push(cur->right); //右
if(cur->left) st.push(cur->left); //左
}
}
void BuildTree(TreeNode* &root){ //前序输入构建一颗二叉树
int x;
scanf("%d",&x);
if(x==-1) {
root=NULL;
return; //空节点直接返回
}
root=new TreeNode(x); //构造根节点
BuildTree(root->left); //构造左子树
BuildTree(root->right); //构造右子树
}
int main(){
TreeNode* root;
BuildTree(root); //构造二叉树
preorderTraversal(root); //前序遍历
for(int i=0;i<result.size();i++) //输出结果序列
printf("%d ",result[i]);
return 0;
}
二叉树的中序遍历
递归
第一曲:确定函数返回值以及参数
遍历过程我们只需要访问节点并纳入结果数组即可,无需返回值,参数只需要一颗树即可
第二曲:终止条件
遍历到空节点直接返回
第三曲:单层递归逻辑
中序遍历的过程就是单层递归逻辑:先访问左子树,再访问根节点,再访问右子树
代码:
#include<bits/stdc++.h>
using namespace std;
struct TreeNode{
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int x):val(x),left(NULL),right(NULL){}; //节点构造函数
};
vector<int>result;
void inorderTraversal(TreeNode* root){ //中序遍历二叉树--递归版
//终止条件
if(!root) return; //空节点直接返回
//单层递归逻辑
inorderTraversal(root->left); //访问左子树
result.push_back(root->val); //访问根节点
inorderTraversal(root->right); //访问右子树
}
void BuildTree(TreeNode* &root){ //前序输入构建一颗二叉树
int x;
scanf("%d",&x);
if(x==-1) {
root=NULL;
return; //空节点直接返回
}
root=new TreeNode(x); //构造根节点
BuildTree(root->left); //构造左子树
BuildTree(root->right); //构造右子树
}
int main(){
TreeNode* root;
BuildTree(root); //构造二叉树
inorderTraversal(root); //中序遍历
for(int i=0;i<result.size();i++) //输出结果序列
printf("%d ",result[i]);
return 0;
}
迭代
中序遍历就没有前序遍历那么简洁了,因为前序遍历中遍历节点的序和处理节点的顺序是一致的,而中序遍历并非如此,它处理节点的顺序是落后于遍历节点的顺序,我们要先沿着左子树将路径上的结点全部压入栈中,然后在之后的出栈操作中再逐一处理结点。
,
代码:
#include<bits/stdc++.h>
using namespace std;
struct TreeNode{
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int x):val(x),left(NULL),right(NULL){}; //节点构造函数
};
vector<int>result;
void inorderTraversal(TreeNode* root){ //中序遍历二叉树--迭代版
if(!root) return; //空节点直接返回
stack<TreeNode*>st;
TreeNode* cur=root;
while(cur||!st.empty()){ //栈空结束
if(cur){ //左
st.push(cur); //沿着左子树一路压入栈中,直到遇到空节点
cur=cur->left;
}
else{
cur=st.top();st.pop(); //根
result.push_back(cur->val);
cur=cur->right; //右
}
}
}
void BuildTree(TreeNode* &root){ //前序输入构建一颗二叉树
int x;
scanf("%d",&x);
if(x==-1) {
root=NULL;
return; //空节点直接返回
}
root=new TreeNode(x); //构造根节点
BuildTree(root->left); //构造左子树
BuildTree(root->right); //构造右子树
}
int main(){
TreeNode* root;
BuildTree(root); //构造二叉树
inorderTraversal(root); //中序遍历
for(int i=0;i<result.size();i++) //输出结果序列
printf("%d ",result[i]);
return 0;
}
二叉树的后序遍历
递归
第一曲:确定函数返回值以及参数
遍历过程我们只需要访问节点并纳入结果数组即可,无需返回值,参数只需要一颗树即可
第二曲:终止条件
遍历到空节点直接返回
第三曲:单层递归逻辑
中序遍历的过程就是单层递归逻辑:先访问左子树,再访问右子树,再访问根节点
代码:
#include<bits/stdc++.h>
using namespace std;
struct TreeNode{
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int x):val(x),left(NULL),right(NULL){}; //节点构造函数
};
vector<int>result;
void postorderTraversal(TreeNode* root){ //后序遍历二叉树--递归版
//终止条件
if(!root) return; //空节点直接返回
//单层递归逻辑
postorderTraversal(root->left); //访问左子树
postorderTraversal(root->right); //访问右子树
result.push_back(root->val); //访问根节点
}
void BuildTree(TreeNode* &root){ //前序输入构建一颗二叉树
int x;
scanf("%d",&x);
if(x==-1) {
root=NULL;
return; //空节点直接返回
}
root=new TreeNode(x); //构造根节点
BuildTree(root->left); //构造左子树
BuildTree(root->right); //构造右子树
}
int main(){
TreeNode* root;
BuildTree(root); //构造二叉树
postorderTraversal(root); //后序遍历
for(int i=0;i<result.size();i++) //输出结果序列
printf("%d ",result[i]);
return 0;
}
迭代
后序遍历相比于前两种更为复杂一点,因为这里多了个难点,即我们在访问根节点时,如何判断根节点的右子树已经被访问了?
事实上,这里有两种处理方式,一种是在中序遍历的基础上进行修改,一种是按照后序遍历的逻辑
法一:
我们要用到一个指针pre,它指向上一个被访问的节点,那么我们我们只需要判断当前访问节点的右子树是不是等于pre即可。其余的逻辑就跟中序遍历的迭代差不多了
代码:
#include<bits/stdc++.h>
using namespace std;
struct TreeNode{
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int x):val(x),left(NULL),right(NULL){}; //节点构造函数
};
vector<int>result;
void postorderTraversal(TreeNode* root){ //后序遍历二叉树--迭代版 法一
if(!root) return; //空节点直接返回
TreeNode* cur=root;
TreeNode* pre=NULL;
stack<TreeNode*>st;
while(cur||!st.empty()){
if(cur){ //左
st.push(cur); //先沿着左子树一路压入栈中,知道遇到空节点
cur=cur->left;
}
else{
cur=st.top();
if(!cur->right||pre==cur->right){ //当前根节点的右子树为空或者右子树刚被访问过
st.pop(); // pre指向当前节点的右子树才能出栈
result.push_back(cur->val);
pre=cur;
cur=NULL; //此时cur要赋值为空,否则的话我们会继续访问左子树
}
else cur=cur->right; //否则的话先访问右子树
}
}
}
void BuildTree(TreeNode* &root){ //前序输入构建一颗二叉树
int x;
scanf("%d",&x);
if(x==-1) {
root=NULL;
return; //空节点直接返回
}
root=new TreeNode(x); //构造根节点
BuildTree(root->left); //构造左子树
BuildTree(root->right); //构造右子树
}
int main(){
TreeNode* root;
BuildTree(root); //构造二叉树
postorderTraversal(root); //后序遍历
for(int i=0;i<result.size();i++) //输出结果序列
printf("%d ",result[i]);
return 0;
}
法二:
我们按照后序遍历的逻辑,即先左再右后根,先一路沿着左子树将节点压入栈中,当左子树为空时,我们转向沿着右子树一路压入栈中,当来到最先要处理的节点时,将它出栈后(注意,已经出栈),判断它是否为栈顶元素的左节点,如果是,我们则继续处理当前栈顶元素的右节点;如果不是,则证明我们刚刚出栈的肯定是当前栈顶元素的右节点,这时候继续处理当前栈顶元素即可(这里想清楚,因为我们是按照后序遍历逻辑压栈处理的)
代码:
#include<bits/stdc++.h>
using namespace std;
struct TreeNode{
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int x):val(x),left(NULL),right(NULL){}; //节点构造函数
};
vector<int>result;
void postorderTraversal(TreeNode* root){ //后序遍历二叉树--迭代版 法二
if(!root) return; //空节点直接返回
TreeNode* cur=root;
stack<TreeNode*>st;
while(cur||!st.empty()){
if(cur){
st.push(cur);
if(cur->left) cur=cur->left; //先沿着左子树找
else cur=cur->right; //再沿着右子树找
}
else{
cur=st.top();st.pop(); //出栈并进行判断
result.push_back(cur->val);
if(!st.empty()&&st.top()->left==cur){ //如果栈不空且刚刚出栈的节点是当前栈顶元素的左节点
cur=st.top()->right; //则继续处理右节点
}
else cur=NULL; //否则的话证明刚刚处理的节点是栈顶元素的右节点,继续出栈栈顶元素
}
}
}
void BuildTree(TreeNode* &root){ //前序输入构建一颗二叉树
int x;
scanf("%d",&x);
if(x==-1) {
root=NULL;
return; //空节点直接返回
}
root=new TreeNode(x); //构造根节点
BuildTree(root->left); //构造左子树
BuildTree(root->right); //构造右子树
}
int main(){
TreeNode* root;
BuildTree(root); //构造二叉树
postorderTraversal(root); //后序遍历
for(int i=0;i<result.size();i++) //输出结果序列
printf("%d ",result[i]);
return 0;
}
二叉树的层序遍历
层序遍历的应用很广泛,而且写法很容易记住,基本可以当做模板来用,学会之后可以吊打LeetCode里下面十个题目,后面会逐一在二叉树算法的分类中运用层序遍历over它们中的一部分
以下代码和书上的有点区别,我将遍历到的节点按层存储进结果数组。
代码:
#include<bits/stdc++.h>
using namespace std;
struct TreeNode{
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int x):val(x),left(NULL),right(NULL){}; //节点构造函数
};
vector<vector<int>>result;
void levelOrder(TreeNode* root){ //层序遍历
if(!root) return; //空节点直接返回
queue<TreeNode*>Que;
Que.push(root); //根节点入队
while(!Que.empty()){ //队列空结束
int leversize=Que.size(); //获得当前树层的宽度
vector<int>temp; //存储每一层的临时数组
for(int i=0;i<leversize;i++){ //遍历当前树层
TreeNode* cur=Que.front(); Que.pop(); //出队
temp.push_back(cur->val);
//同时把下一层的节点入队
if(cur->left) Que.push(cur->left);
if(cur->right) Que.push(cur->right);
}
result.push_back(temp);
}
}
void BuildTree(TreeNode* &root){ //前序输入构建一颗二叉树
int x;
scanf("%d",&x);
if(x==-1) {
root=NULL;
return; //空节点直接返回
}
root=new TreeNode(x); //构造根节点
BuildTree(root->left); //构造左子树
BuildTree(root->right); //构造右子树
}
int main(){
TreeNode* root;
BuildTree(root); //构造二叉树
levelOrder(root); //后序遍历
for(int i=0;i<result.size();i++){ //输出结果序列
for(int j=0;j<result[i].size();j++) //输出每一层
printf("%d ",result[i][j]);
printf("\n");
}
return 0;
}
小结
不知道大家有没有发现,前序遍历属于“自上而下”的遍历过程,后序遍历属于“自下而上”的遍历过程,层次遍历属于“自左往右”的遍历过程,好好理解下它们各自的特点,这为我们后面在遇到算法题挑选合适的遍历指明了方向。
二叉树的属性系列
对称二叉树PIPIOJ
给你一个二叉树的根节点 root
, 检查它是否轴对称,如下所示为一颗对称二叉树
输入:1 2 3 -1 -1 4 -1 -1 2 4 -1 -1 3 -1 -1
输入:输入一行,按照先序输入一棵二叉树,其中空节点用 -1 表示。
输出:若该二叉树是对称二叉树,输出 "yes", 否则输出"no"。
思路:要判断一颗二叉树是否为对称二叉树,关键在于它的左右子树的节点是否对称。这道题可以用递归和迭代两种方式来解决。
递归
递归三部曲登场!!!
第一曲:函数返回值以及参数
题目要求我们输出是或不是,不难知道,返回值定义为bool类型,参数为两个节点指针(简称为左指针和右指针),分别为左子树当前遍历的结点和右子树当前遍历的结点
第二曲:终止条件
1.如果左指针或右指针其中之一为空,另一个不空,则必然不对称,返回false;
2.当遇到空节点时我们同样结束遍历,由于空节点必然相同,则返回true;
3.当左指针和右指针所指节点的数据不相同的时,则这颗二叉树必然不对称,返回false;注意,这个判断写在最后,我们应该先判空,再判节点数据。
第三曲:单层递归逻辑
如果当前左指针和右指针所指节点相同,则递归判断左指针的左节点和右指针的右节点,以及左指针的右节点和右指针的左节点是否相同。
确定了递归三部曲,代码的全貌基本就出来了
代码:
#include<bits/stdc++.h>
using namespace std;
struct TreeNode{
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int x):val(x),left(NULL),right(NULL){}; //节点构造函数
};
bool compare(TreeNode* Left,TreeNode* Right){ //对称二叉树--递归
//终止条件
if(Left==NULL&&Right==NULL) return true; //左右均空
else if(Left==NULL||Right==NULL) return false; //左右之一为空
else if(Left->val!=Right->val) return false; //左右不对称
//单层递归逻辑
else return compare(Left->left,Right->right)&&compare(Left->right,Right->left);
}
void BuildTree(TreeNode* &root){ //前序输入构建一颗二叉树
int x;
scanf("%d",&x);
if(x==-1) {
root=NULL;
return; //空节点直接返回
}
root=new TreeNode(x); //构造根节点
BuildTree(root->left); //构造左子树
BuildTree(root->right); //构造右子树
}
int main(){
TreeNode* root;
BuildTree(root); //构造二叉树
if(!root) printf("yes\n"); //空树直接返回yes
else if (compare(root->left,root->right)) //判断对称树
printf("yes\n");
else printf("no\n");
return 0;
}
迭代
不难发现,对称树的比较过程就是在每一层里面进行,所以这道题也可以用层序遍历来解决
代码:
#include<bits/stdc++.h>
using namespace std;
struct TreeNode{
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int x):val(x),left(NULL),right(NULL){}; //节点构造函数
};
bool compare(TreeNode* root){ //对称二叉树--迭代
if(!root) return true; //空节点直接返回
queue<TreeNode*>Que;
Que.push(root->left); //左右节点入队
Que.push(root->right);
while(!Que.empty()){
TreeNode* Left=Que.front(); Que.pop(); //取出左右节点判断
TreeNode* Right=Que.front(); Que.pop();
if(Left==NULL&&Right==NULL) continue; //左右均空则继续判断
//左右之一为空或者节点不对称
if(Left==NULL||Right==NULL||(Left->val!=Right->val)) return false;
Que.push(Left->left);Que.push(Right->right);
Que.push(Left->right);Que.push(Right->left);
}
return true;
}
void BuildTree(TreeNode* &root){ //前序输入构建一颗二叉树
int x;
scanf("%d",&x);
if(x==-1) {
root=NULL;
return; //空节点直接返回
}
root=new TreeNode(x); //构造根节点
BuildTree(root->left); //构造左子树
BuildTree(root->right); //构造右子树
}
int main(){
TreeNode* root;
BuildTree(root); //构造二叉树
if (compare(root)) //判断对称树
printf("yes\n");
else printf("no\n");
return 0;
}
二叉树的最大深度PIPIOJ
题目描述
给定一个二叉树,找出其最大深度。
最大深度是从根节点到最远叶子节点的最长路径上的节点数量。
输入:3 5 6 -1 -1 2 7 -1 -1 4 -1 -1 1 0 -1 -1 8 -1 -1 最大深度:4
输入:输入一行,按照先序输入一棵二叉树,其中空节点用 -1 表示。
输出:输出一行代表二叉树的最大深度。
思路:这道题既可以用递归来做,也可以用迭代来做。
递归
这道题做对很容易,但是有一点大家并没有弄清楚,那就是高度和深度的区别,比如我们要知道一栋楼的高度,那么自然是从底端开始往上数,二叉树也同样如此,根据后序遍历“自下而上”的特点可以知道,求高度用的是后序遍历。而深度呢,我们要知道一片湖的深度,肯定是要从上面往下测量才能知道,根据前序遍历“自上而下”的特点可以知道,求深度用的是前序遍历。
所以按理来说,本道题应该用前序遍历来做,但是由于根节点的深度就等于整棵树的高度,所以这道题也可以用后序遍历来做,并且相比于前序遍历更为简洁,大部分做对的同学用的也是后序遍历,但是却忽略了这个概念。
递归三部曲
第一曲:确定函数返回值和参数
我们要知道左子树的高度和右子树的深度再进行比较,所以返回值应该是int。参数容易确定,即左右子树
第二曲:终止条件
遍历到空节点结束
第三曲:单层递归逻辑
获取左右子树的深度,再比较一下获得最大的深度。
代码:
#include<bits/stdc++.h>
using namespace std;
struct TreeNode{
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int x):val(x),left(NULL),right(NULL){}; //节点构造函数
};
int maxDepth(TreeNode* root){ //二叉树的最大深度--递归
if(!root) return 0;
int leftdepth=maxDepth(root->left); //左子树高度
int rightdepth=maxDepth(root->right); //右子树高度
return leftdepth>rightdepth?leftdepth+1:rightdepth+1; //返回最大的那一个并加1
}
void BuildTree(TreeNode* &root){ //前序输入构建一颗二叉树
int x;
scanf("%d",&x);
if(x==-1) {
root=NULL;
return; //空节点直接返回
}
root=new TreeNode(x); //构造根节点
BuildTree(root->left); //构造左子树
BuildTree(root->right); //构造右子树
}
int main(){
TreeNode* root;
BuildTree(root); //构造二叉树
printf("%d\n",maxDepth(root));
return 0;
}
迭代
我们要数出树的高度,也就相当于我们要知道这颗二叉树有几层,所以自然而然想到层序遍历,套用之前的层序遍历模板。
#include<bits/stdc++.h>
using namespace std;
struct TreeNode{
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int x):val(x),left(NULL),right(NULL){}; //节点构造函数
};
int maxDepth(TreeNode* root){ //二叉树的最大深度--迭代
if(!root) return 0;
queue<TreeNode*> Que;
Que.push(root);
int depth=0;
while(!Que.empty()){ //队空结束
depth++; //层数+1
int leversize=Que.size(); //获取当前层宽度
for(int i=0;i<leversize;i++){
TreeNode* cur=Que.front();Que.pop();
if(cur->left) Que.push(cur->left);
if(cur->right) Que.push(cur->right);
}
}
return height;
}
void BuildTree(TreeNode* &root){ //前序输入构建一颗二叉树
int x;
scanf("%d",&x);
if(x==-1) {
root=NULL;
return; //空节点直接返回
}
root=new TreeNode(x); //构造根节点
BuildTree(root->left); //构造左子树
BuildTree(root->right); //构造右子树
}
int main(){
TreeNode* root;
BuildTree(root); //构造二叉树
printf("%d\n",maxDepth(root));
return 0;
}
二叉树的最小深度
给定一个二叉树,找出其最小深度。
最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
最小深度:2
思路:有的同学估计会以为这跟上一题的最大深度差不多,如果套用上一题的递归模板,将处理中间节点的逻辑简单的改为返回左右子树深度的最小值,你会发现,去掉样例中的9,返回值会变成1,但显然最小深度不是1。具体该怎么做呢,递归三部曲里见!
递归
递归三部曲
第一曲:确定函数返回值和参数
这两个比较好确定,我们要返回的肯定是最小深度,参数也是左右子树
第二曲:终止条件
遇到空节点返回
第三曲:单层递归逻辑
这就是与上道题不一样的地方了,这里要仔细理解题意。
1.如果根节点的左右子树都不空,那么我们求的便是两者中的最小深度
2.如果根节点的左右子树之一为空,那么我们求的便是不空的子树最小深度
3.如果左右子树均空,也就是叶子节点,那么最小深度就是1
代码:
#include<bits/stdc++.h>
using namespace std;
struct TreeNode{
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int x):val(x),left(NULL),right(NULL){}; //节点构造函数
};
int minDepth(TreeNode* root){ //二叉树的最小深度--递归
//终止条件
if(!root) return 0; //空节点返回
//单层递归逻辑
int leftdepth=minDepth(root->left);
int rightdepth=minDepth(root->right);
if(root->left&&root->right) //左右均不空
return 1+min(leftdepth,rightdepth);
if(!root->left&&!root->right) //左右均空
return 1;
//左右之一为空
return 1+leftdepth+rightdepth; //空树深度必然为0
}
void BuildTree(TreeNode* &root){ //前序输入构建一颗二叉树
int x;
scanf("%d",&x);
if(x==-1) {
root=NULL;
return; //空节点直接返回
}
root=new TreeNode(x); //构造根节点
BuildTree(root->left); //构造左子树
BuildTree(root->right); //构造右子树
}
int main(){
TreeNode* root;
BuildTree(root); //构造二叉树
printf("%d\n",minDepth(root));
return 0;
}
迭代
同样的,这道题如果用迭代来做的话逻辑会简单很多,依然是层序遍历,我们只要找到第一个左右均空的叶子节点即可
代码:
#include<bits/stdc++.h>
using namespace std;
struct TreeNode{
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int x):val(x),left(NULL),right(NULL){}; //节点构造函数
};
int minDepth(TreeNode* root){ //二叉树的最小深度--迭代
if(!root) return 0;
queue<TreeNode*>Que;
Que.push(root);
int depth=0;
while(!Que.empty()){ //队空结束
depth++;
int leversize=Que.size(); //获取每一层的宽度
for(int i=0;i<leversize;i++){
TreeNode* cur=Que.front(); Que.pop(); //取出队头节点
if(!cur->left&&!cur->right) return depth; //遇到第一个叶子节点返回
if(cur->left) Que.push(cur->left);
if(cur->right) Que.push(cur->right);
}
}
return depth;
}
void BuildTree(TreeNode* &root){ //前序输入构建一颗二叉树
int x;
scanf("%d",&x);
if(x==-1) {
root=NULL;
return; //空节点直接返回
}
root=new TreeNode(x); //构造根节点
BuildTree(root->left); //构造左子树
BuildTree(root->right); //构造右子树
}
int main(){
TreeNode* root;
BuildTree(root); //构造二叉树
printf("%d\n",minDepth(root));
return 0;
}
在每个树行中找最大值PIPIOJ
题目描述
在二叉树的每一行中找到最大的值。
输入:3 5 6 -1 -1 2 7 -1 -1 4 -1 -1 1 0 -1 -1 8 -1 -1 输出:3,5,8,7
输入:输入一行,按照先序输入一棵二叉树,其中空节点用 -1 表示。
输出:从根节点开始往下输出树的每一行的最大值。
思路:这道题就是赤裸裸的层序遍历题了,套用模板秒了
代码:
#include<bits/stdc++.h>
using namespace std;
struct TreeNode{
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int x):val(x),left(NULL),right(NULL){}; //节点构造函数
};
void levermax(TreeNode* root,vector<int>& res){ //获取每层最大值
if(!root) return;
queue<TreeNode*>Que;
Que.push(root);
while(!Que.empty()){ //队空结束
int leversize=Que.size(); //获取每一层的宽度
int max=-1; //本题-1表示空节点
for(int i=0;i<leversize;i++){ //按层遍历
TreeNode* cur=Que.front(); Que.pop(); //取出队头
if(max<cur->val) max=cur->val; //更新最大值
if(cur->left) Que.push(cur->left);
if(cur->right) Que.push(cur->right);
}
res.push_back(max); //获取每一层的最大值
}
}
void BuildTree(TreeNode* &root){ //前序输入构建一颗二叉树
int x;
scanf("%d",&x);
if(x==-1) {
root=NULL;
return; //空节点直接返回
}
root=new TreeNode(x); //构造根节点
BuildTree(root->left); //构造左子树
BuildTree(root->right); //构造右子树
}
int main(){
TreeNode* root;
BuildTree(root); //构造二叉树
vector<int>res;
levermax(root,res);
for(int i=0;i<res.size();i++)
printf("%d ",res[i]);
return 0;
}
高度平衡的二叉树PIPIOJ
题目描述
给定一个二叉树,判断它是否是高度平衡的二叉树。
一棵高度平衡二叉树定义为:一个二叉树每个节点的左右两个子树的高度差的绝对值不超过1。
输入:3 5 6 -1 -1 2 7 -1 -1 4 -1 -1 1 0 -1 -1 8 -1 -1 输出:YES
输入:输入一行,按照先序输入一棵二叉树,其中空节点用 -1 表示。
输出:若是则输出YES,否则输出NO。
思路:这道题和求二叉树高度的那两道题大同小异,我们只要获取每个根节点的左右子树的最大高度,相减进行判断即可
递归三部曲
第一曲:确定函数返回值和参数
返回值为int类型,因为我们递归要获取的是树的高度,参数为节点指针
第二曲:终止条件
1.遇到空节点,
2.已经出现不平衡的子树,结束
第三曲:单层递归逻辑
获取每个根节点的左右子树的最大高度,相减进行判断
代码:
#include<bits/stdc++.h>
using namespace std;
struct TreeNode{
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int x):val(x),left(NULL),right(NULL){}; //节点构造函数
};
bool flag=1;
int getHeight(TreeNode* root){
//终止条件
if(!root) return 0; //空节点返回
if(!flag) return 0;
//单层递归逻辑
int leftheight=getHeight(root->left);
int rightheight=getHeight(root->right);
if(abs(leftheight-rightheight)>1){ //如果已经出现不平衡子树
flag=0;
return 0;
}
else return 1+max(leftheight,rightheight); //否则继续求子树高度
}
void BuildTree(TreeNode* &root){ //前序输入构建一颗二叉树
int x;
scanf("%d",&x);
if(x==-1) {
root=NULL;
return; //空节点直接返回
}
root=new TreeNode(x); //构造根节点
BuildTree(root->left); //构造左子树
BuildTree(root->right); //构造右子树
}
int main(){
TreeNode* root;
BuildTree(root); //构造二叉树
getHeight(root);
if(flag) printf("YES\n");
else printf("NO\n");
return 0;
}
二叉树的直径PIPIOJ
题目描述
二叉树的直径是指二叉树中相距最远的两个点的距离。如下图所示二叉树,最远的两个点是7和13或者 4和13,故二叉树直径为6.
输入:8 3 1 -1 -1 6 4 -1 -1 7 -1 -1 10 -1 14 13 -1 -1 -1 输出:6
输入:输入一行,按照先序输入一棵二叉树,其中空节点用 -1 表示。
输出:输出一行代表二叉树的直径。
思路:透过本质看内容,我们发现,这道题事实上求的是根节点的左子树最大高度与右子树最大高度之和,但稍微有些区别,因为此处中的根节点可以是任意子树,并不是单纯指整棵树的根节点。但万变不离其宗,这道题依然采用的是后序遍历,我们要“自下而上”的进行处理,一定要深刻理解“自下而上”,因为代码的逻辑就是按照自下而上考虑的(即从最底层开始考虑)
递归三部曲
第一曲:确定函数返回值和参数
返回值为int型,参数为节点指针
第二曲:终止条件
遇到空节点结束
第三曲:单层递归逻辑
获取左子树的最大高度和右子树的最大高度,更新一个全局变量ans(表示任意一个根节点的最大直径),返回左右子树高度中最大的那个
代码:
#include<bits/stdc++.h>
using namespace std;
struct TreeNode{
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int x):val(x),left(NULL),right(NULL){}; //节点构造函数
};
int ans=-1;
int diameterOfBinaryTree(TreeNode* root){
if(!root) return 0;
int leftheight=diameterOfBinaryTree(root->left); //左子树最大高度
int rightheight=diameterOfBinaryTree(root->right); //右子树最大高度
ans=max(ans,leftheight+rightheight); //更新最大值
return 1+max(leftheight,rightheight);
}
void BuildTree(TreeNode* &root){ //前序输入构建一颗二叉树
int x;
scanf("%d",&x);
if(x==-1) {
root=NULL;
return; //空节点直接返回
}
root=new TreeNode(x); //构造根节点
BuildTree(root->left); //构造左子树
BuildTree(root->right); //构造右子树
}
int main(){
TreeNode* root;
BuildTree(root); //构造二叉树
diameterOfBinaryTree(root);
printf("%d",ans);
return 0;
}
层数最深叶子节点的和PIPIOJ
题目描述
给你一棵二叉树,请你返回层数最深的叶子节点的和。
输入:8 3 1 -1 -1 6 4 -1 -1 7 -1 -1 10 -1 14 13 -1 -1 -1 输出:24
输入:输入一行,按照先序输入一棵二叉树,其中空节点用 -1 表示。
输出:输出层数最深的叶子节点的和。
思路:这里简单讲一下递归的思路(迭代的相信很好理解),这道题主要用前序遍历的思路来做,自上而下,我们记录目前所遍历的最深层数,用一个sum记录递归遍历到属于最深层的那些节点之和
递归
递归三部曲
第一曲:函数返回值和参数
返回值为void型,参数为节点指针以及下一轮递归所遍历节点的层数
第二曲:终止条件
遇到空节点时结束
第三曲:单层递归逻辑
如果当前遍历节点和最深层数相等,则将其数值累加
如果当前遍历节点的层数大于最深层,则说明之前遍历的所有结点都不属于最深层的节点,重新开始累加
代码:
#include<bits/stdc++.h>
using namespace std;
struct TreeNode{
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int x):val(x),left(NULL),right(NULL){}; //节点构造函数
};
int sum=0;
int maxdepth=0;
void deepestLeavesSum(TreeNode* root,int depth){
//终止条件
if(!root) return;
//单层递归逻辑
if(depth==maxdepth) //如果该节点属于目前已知的最深层
sum+=root->val;
else if(depth>maxdepth){ //如果该节点的层数大于目前已知的最深层
maxdepth=depth; //更新最深层
sum=root->val; //重新计数
}
deepestLeavesSum(root->left,depth+1); //继续往下层遍历
deepestLeavesSum(root->right,depth+1);
}
void BuildTree(TreeNode* &root){ //前序输入构建一颗二叉树
int x;
scanf("%d",&x);
if(x==-1) {
root=NULL;
return; //空节点直接返回
}
root=new TreeNode(x); //构造根节点
BuildTree(root->left); //构造左子树
BuildTree(root->right); //构造右子树
}
int main(){
TreeNode* root;
BuildTree(root); //构造二叉树
deepestLeavesSum(root,0);
printf("%d",sum);
return 0;
}
迭代
很明显这是一道标准的层序遍历模板题,我们只要在遍历每一层的时候同时更新sum即可
代码:
#include<bits/stdc++.h>
using namespace std;
struct TreeNode{
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int x):val(x),left(NULL),right(NULL){}; //节点构造函数
};
int sum=0;
void deepestLeavesSum(TreeNode* root){
if(!root) return; //空树直接返回
queue<TreeNode*>Que;
Que.push(root);
while(!Que.empty()){ //队空结束
int leversize=Que.size(); //获取每一层的宽度
sum=0; //每一层都初始化一次sum
for(int i=0;i<leversize;i++){
TreeNode* cur=Que.front();Que.pop(); //取出队头节点
sum+=cur->val; //累加每一层的所有结点
if(cur->left) Que.push(cur->left);
if(cur->right) Que.push(cur->right);
}
}
}
void BuildTree(TreeNode* &root){ //前序输入构建一颗二叉树
int x;
scanf("%d",&x);
if(x==-1) {
root=NULL;
return; //空节点直接返回
}
root=new TreeNode(x); //构造根节点
BuildTree(root->left); //构造左子树
BuildTree(root->right); //构造右子树
}
int main(){
TreeNode* root;
BuildTree(root); //构造二叉树
deepestLeavesSum(root);
printf("%d",sum);
return 0;
}
求根到叶子结点数字之和PIPIOJ
题目描述
给定一个二叉树,它的每个结点都存放一个1-9的数字,每条从根到叶子节点的路径都代表一个数字。
例如,从根到叶子节点路径 1->2->3 代表数字 123。
计算从根到叶子节点生成的所有数字之和,答案保证在int范围。
输入:8 3 1 -1 -1 6 4 -1 -1 7 -1 -1 10 -1 14 13 -1 -1 -1
输出:26715
输入:输入一行,按照先序输入一棵二叉树,其中空节点用 -1 表示。
输出:输出根到叶子节点生成的所有数字之和。
思路:很明显,这道题的特点是一个自上而下的过程,用一个roadsum累计路径上的数字和,直到遇到叶节点,将其累加到结果sum中,所以我们选择前序遍历。
递归三部曲
第一曲:确定函数返回值和参数
由于我们需要遍历所有的节点,最终的结果累加到一个全局变量sum里面,所以返回值为void,参数为一个节点指针和一个累计路径上数字和的roadsum
第二曲:终止条件
遇到叶子节点结束遍历,将roadsum累加到结果sum中
第三曲:单层递归逻辑
将roadsum位数进一并加上当前的节点数字
代码:
#include<bits/stdc++.h>
using namespace std;
struct TreeNode{
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int x):val(x),left(NULL),right(NULL){}; //节点构造函数
};
int sum=0;
int roadsum=0;
void AllRoadSum(TreeNode* root,int roadsum){
//终止条件
if(!root->left&&!root->right){
sum+=roadsum*10+root->val; //遇到叶子节点将和累加到结果当中
return;
}
//单层递归逻辑
roadsum=roadsum*10+root->val;
if(root->left) AllRoadSum(root->left,roadsum);
if(root->right) AllRoadSum(root->right,roadsum);
}
void BuildTree(TreeNode* &root){ //前序输入构建一颗二叉树
int x;
scanf("%d",&x);
if(x==-1) {
root=NULL;
return; //空节点直接返回
}
root=new TreeNode(x); //构造根节点
BuildTree(root->left); //构造左子树
BuildTree(root->right); //构造右子树
}
int main(){
TreeNode* root;
BuildTree(root); //构造二叉树
AllRoadSum(root,0);
printf("%d",sum);
return 0;
}
找树左下角的值PIPIOJ
题目描述
给定一个二叉树,在树的最后一行找到最左边的值。
输入:8 3 1 -1 -1 6 4 -1 -1 7 -1 -1 10 -1 14 13 -1 -1 -1 输出:4
输入:输入一行,按照先序输入一棵二叉树,其中空节点用 -1 表示。
输出:输出树的最后一行最左边的值。
思路:这道题其实和求“层数最深的叶子节点数字和”那道题类似,典型的层序遍历,但同时也可以用递归来做
递归
递归三部曲
第一曲:函数返回值和参数
返回值为void型,参数为节点指针以及下一轮递归所遍历节点的层数
第二曲:终止条件
遇到空节点时结束
第三曲:单层递归逻辑
如果当前遍历节点的层数大于最深层,则说明之前遍历的所有结点都不属于最深层的节点,重新记录最左下角的值,由于我们采用的是前序遍历,所以遍历到的第一个层数最深的节点一定是最左下角的节点
代码:
#include<bits/stdc++.h>
using namespace std;
struct TreeNode{
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int x):val(x),left(NULL),right(NULL){}; //节点构造函数
};
int ans=0;
int maxdepth=-1;
void Getleft_bottom(TreeNode* root,int depth){
//终止条件
if(!root) return;
//单层递归逻辑
if(depth>maxdepth){
maxdepth=depth;
ans=root->val;
}
Getleft_bottom(root->left,depth+1);
Getleft_bottom(root->right,depth+1);
}
void BuildTree(TreeNode* &root){ //前序输入构建一颗二叉树
int x;
scanf("%d",&x);
if(x==-1) {
root=NULL;
return; //空节点直接返回
}
root=new TreeNode(x); //构造根节点
BuildTree(root->left); //构造左子树
BuildTree(root->right); //构造右子树
}
int main(){
TreeNode* root;
BuildTree(root); //构造二叉树
Getleft_bottom(root,0);
printf("%d",ans);
return 0;
}
迭代
标准的层序遍历模板,找到第一个最深层的第一个节点即可
代码:
#include<bits/stdc++.h>
using namespace std;
struct TreeNode{
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int x):val(x),left(NULL),right(NULL){}; //节点构造函数
};
int Getleft_bottom(TreeNode* root){
if(!root) return 0; //空节点直接返回
queue<TreeNode*>Que;
Que.push(root);
int ans;
while(!Que.empty()){ //队空结束
int leversize=Que.size(); //当前层的宽度
for(int i=0;i<leversize;i++){
TreeNode* cur=Que.front(); Que.pop(); //取出队头
if(i==0) ans=cur->val; //只记录每层第一个节点
if(cur->left) Que.push(cur->left);
if(cur->right) Que.push(cur->right);
}
}
return ans;
}
void BuildTree(TreeNode* &root){ //前序输入构建一颗二叉树
int x;
scanf("%d",&x);
if(x==-1) {
root=NULL;
return; //空节点直接返回
}
root=new TreeNode(x); //构造根节点
BuildTree(root->left); //构造左子树
BuildTree(root->right); //构造右子树
}
int main(){
TreeNode* root;
BuildTree(root); //构造二叉树
printf("%d",Getleft_bottom(root));
return 0;
}
判断树的子树PIPIOJ
题目描述
给定两棵非空二叉树 T1 和 T2 ,现在PIPI想让你判断T2 是否是T1的子树。 T1的子树包含T1的一个结点以及该结点的所有子孙结点(包括空节点)
root:3 4 1 -1 -1 2 -1 -1 5 -1 -1 subroot:4 1 -1 -1 2 -1 -1
输出:yes
输入
第一行按照先序输入s,其中空节点用 -1 表示。
第二行按照先序输入t,其中空节点用 -1 表示。
输出:若t是s的子树 ,输出"yes" , 否则输出 "no".
思路:判断A树是否是B树的子树,无非就是在B树的任意子树里面寻找可能匹配的情况,显然,这是一道“自上而下”的匹配过程,所以我们采用前序遍历解决。除此之外,我们需要调用一个辅助函数 IsSametree(),当我们找到了可能匹配的子树时,要对其进行判断,同样也是利用前序遍历完成
递归三部曲
第一曲:确定函数返回值和参数
返回值为bool类型,参数为指向两棵树的节点指针
第二曲:终止条件
1.一棵树空,另一棵树不空则不匹配,结束遍历
2.用IsSametree()函数匹配成功则结束遍历
第三曲:单层递归逻辑
判断A树是否为B树的左子树的子树,A树是否为B树右子树的子树
代码:
#include<bits/stdc++.h>
using namespace std;
struct TreeNode{
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int x):val(x),left(NULL),right(NULL){}; //节点构造函数
};
bool IsSametree(TreeNode* root1,TreeNode* root2){ //判断是否为相同的树
//终止条件
if(!root1&&!root2) return true; //两个空节点必然相同
else if(!root1||!root2) return false; //其中之一为空则不相同
else if(root1->val!=root2->val) return false; //根节点不相同
//单层递归
bool flag1=IsSametree(root1->left,root2->left); //判断左子树是否相同
bool flag2=IsSametree(root1->right,root2->right); //判断右子树是否相同
return flag1&&flag2;
}
bool isSubtree(TreeNode* root,TreeNode* subroot){ //判断子树
//终止条件
if(!root||!subroot) return false; //两树之一为空
if(IsSametree(root,subroot)) return true; //找到相同的子树
//单层递归
bool flagleft=isSubtree(root->left,subroot); //在root的左子树中寻找相同的子树
bool flagright=isSubtree(root->right,subroot); //在root的右子树中寻找相同的子树
return flagleft||flagright; //只要寻找到即可
}
void BuildTree(TreeNode* &root){ //前序输入构建一颗二叉树
int x;
scanf("%d",&x);
if(x==-1) {
root=NULL;
return; //空节点直接返回
}
root=new TreeNode(x); //构造根节点
BuildTree(root->left); //构造左子树
BuildTree(root->right); //构造右子树
}
int main(){
TreeNode* root,* subroot;
BuildTree(root); //构造二叉树
BuildTree(subroot);
if(isSubtree(root,subroot)) printf("yes\n");
else printf("no\n");
return 0;
}
验证二叉搜索树PIPIOJ
题目描述
给定一个二叉树,判断其是否是一棵二叉搜索树。
二叉搜索树具有如下特征:
1.节点的左子树只包含小于当前节点的数。
2.节点的右子树只包含大于当前节点的数。
3.所有左子树和右子树自身必须也是二叉搜索树。
输入:2 1 -1 -1 3 -1 -1 输出:true
输入:输入一行。 按照先序输入一棵二叉树,其中空节点用 -1 表示。
输出:若该二叉树为BST,输出"true" ,否则输出"false"(不带引号).
思路:实际上题意已经告诉我们要做什么了,采用中序遍历是验证二叉搜索树的最好办法,否则的话采用前序遍历仅仅判断当前节点左孩子是否比自己小,当前节点右孩子是否比自己大,会遇到一种特殊情况:即以上图为例,当1的右孩子出现了4,同样不是一颗二叉搜索树。
在中序遍历下,遍历节点顺序一定是按照从小到大顺序排列的,所以我们只要增加一个节点指针pre指向当前遍历节点的前一个节点即可
递归三部曲
第一曲:确定函数返回值和参数
返回值为bool类型,参数为节点指针
第二曲:终止条件
遇到空节点结束,且空树为二叉搜索树
第三曲:单层递归逻辑
判断当前节点值是否严格大于前一个节点值
代码:
#include<bits/stdc++.h>
using namespace std;
struct TreeNode{
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int x):val(x),left(NULL),right(NULL){}; //节点构造函数
};
TreeNode* pre=NULL; //指向前一个节点的指针
bool isValidBST(TreeNode* root){ //验证二叉搜索树
//终止条件
if(!root) return true;
//单层递归逻辑
bool leftIsValid=isValidBST(root->left); //验证左子树
if(pre&&root->val<=pre->val) return false; //非法情况
else pre=root;
bool rightIsValid=isValidBST(root->right); //验证右子树
return leftIsValid&&rightIsValid;
}
void BuildTree(TreeNode* &root){ //前序输入构建一颗二叉树
int x;
scanf("%d",&x);
if(x==-1) {
root=NULL;
return; //空节点直接返回
}
root=new TreeNode(x); //构造根节点
BuildTree(root->left); //构造左子树
BuildTree(root->right); //构造右子树
}
int main(){
TreeNode* root;
BuildTree(root); //构造二叉树
if(isValidBST(root)) printf("true\n");
else printf("false\n");
return 0;
}
同构二叉树PIPIOJ
题目描述
给定两颗树T1和T2,如果T1可以通过若干次左右孩子互换变成T2,则我们称两棵树是“同构”的。
例如图1给出的两棵树就是同构的,因为我们把其中一棵树的结点A、B、G的左右孩子互换后,就得到另外一棵树。而图2就不是同构的。
输入
第一行按照先序输入T1,其中空节点用 -1 表示。
第二行按照先序输入T2,其中空节点用 -1 表示。
输出:如果两棵树是同构的,输出"YES"。否则输出"NO".
思路:看起来很复杂的样子,好像得去修改二叉树,实际上并不用,本质思想和前面的一些题类似,即判断相同节点的左右孩子是否相同(不用在乎顺序),所以这道题依然是一个“自上而下”的判断过程,采用前序遍历
递归三部曲
第一曲:确定函数返回值和参数
返回值为bool类型,参数为节点指针
第二曲:终止条件
1.左右均空结束
2.左右之一为空结束
3.遍历到不相同的节点结束
第三曲:单层递归逻辑
判断两个相同节点的同构类型
1.左左同构,右右同构
2.左右同构,右左同构
如果均不满足,则不同构
代码:
#include<bits/stdc++.h>
using namespace std;
struct TreeNode{
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int x):val(x),left(NULL),right(NULL){}; //节点构造函数
};
bool IsHomogeneoustree(TreeNode* root1,TreeNode* root2){ //判断同构二叉树
//终止条件
if(!root1&&!root2) return true;
else if(!root1||!root2) return false; //其中之一为空则非同构
else if(root1->val!=root2->val) return false;
//单层递归逻辑
bool leftflag1=IsHomogeneoustree(root1->left,root2->left);
bool rightflag1=IsHomogeneoustree(root1->right,root2->right);
if(leftflag1&&rightflag1) return true; //左左同构,右右同构
bool leftflag2=IsHomogeneoustree(root1->left,root2->right);
bool rightflag2=IsHomogeneoustree(root1->right,root2->left);
if(leftflag2&&rightflag2) return true; //左右同构,右左同构
return false; //否则不同构
}
void BuildTree(TreeNode* &root){ //前序输入构建一颗二叉树
int x;
scanf("%d",&x);
if(x==-1) {
root=NULL;
return; //空节点直接返回
}
root=new TreeNode(x); //构造根节点
BuildTree(root->left); //构造左子树
BuildTree(root->right); //构造右子树
}
int main(){
TreeNode* root1,* root2;
BuildTree(root1); //构造二叉树
BuildTree(root2);
if(IsHomogeneoustree(root1,root2)) printf("YES\n");
else printf("NO\n");
return 0;
}
二叉树的构造系列
关于这类题,画图是最好的方式。
思路不难,但有很多同学在这里的代码有两点容易混淆,
1.每次递归都不知道怎么划分出正确的区间,事实上,对于这类区间划分的题目,我们要在代码的贯穿始终坚持一个相同的原则,即左闭(开)右闭(开)原则,以下我选择坚持“左闭右闭”原则
2.终止条件的判断中不知道取等号还是不等号,这要根据你坚持的原则,寻找临界情况进行判断
前序和中序构造二叉树PIPIOJ
题目描述
PIPI现在有两个序列,分别为二叉树的先序序列和二叉树的中序序列,他想由这两个序列还原二叉树,你能帮PIPI还原吗?
输入
第一行输入序列的长度n (0<n<100).
第二行输入二叉树的先序序列。
第三行输入二叉树的中序序列。
输出:输出二叉树的后序遍历序列。
思路:首先建树一定是一个“自上而下”的过程,所以我们这里采用前序遍历的方式
按照我们手工模拟的思路分为以下几步
第一步:选取前序序列的第一个数为根节点
第二步:在中序序列中寻找到根节点位置middle,将中序序列划分为左子树区间和右子树区间的部分,获取两个区间的长度
第三步:递归的构造出左子树和右子树即可
代码:
代码看起来有点繁琐,实际上结合图形理解非常轻松且逻辑清晰
#include<bits/stdc++.h>
using namespace std;
struct TreeNode{
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int x):val(x),left(NULL),right(NULL){}; //节点构造函数
};
TreeNode* PreandIn_BuildTree(vector<int>preorder,int preleft,int preright,vector<int>inorder,int inleft,int inright){ //前序+中序构建二叉树
//终止条件
if(preleft>preright) return NULL;
//单层递归
int k=preorder[preleft];
TreeNode* root=new TreeNode(k); //选取前序序列的第一个数为根节点
int middle;
for(middle=inleft;middle<inright;middle++)
if(inorder[middle]==k) break; //在中序序列中寻找到根节点
int leftlength=middle-inleft; //获取左子树区间长度
int rightlength=inright-middle; //获取右子树区间长度
root->left=PreandIn_BuildTree(preorder,preleft+1,preleft+leftlength,inorder,inleft,middle-1); //递归的构造左子树,坚持左闭右闭原则
root->right=PreandIn_BuildTree(preorder,preright-rightlength+1,preright,inorder,middle+1,inright); //递归的构造右子树,坚持左闭右闭原则
return root;
}
void postPrint(TreeNode* root){ //后序输出
//终止条件
if(!root) return; //空节点不打印
//单层递归逻辑
postPrint(root->left);
postPrint(root->right);
printf("%d ",root->val);
}
int main(){
int n;
scanf("%d",&n); //输入序列长度
vector<int>preorder(n);
vector<int>inorder(n);
int x;
for(int i=0;i<n;i++){ //输入先序序列
scanf("%d",&x);
preorder[i]=x;
}
for(int i=0;i<n;i++){ //输入中序序列
scanf("%d",&x);
inorder[i]=x;
}
TreeNode* root=PreandIn_BuildTree(preorder,0,preorder.size()-1,inorder,0,inorder.size()-1); //左闭右闭
postPrint(root); //后序输出
return 0;
}
中序和后序构建二叉树PIPIOJ
题目描述
PIPI现在有两个序列,分别为二叉树的中序序列和二叉树的后序序列,他想由这两个序列还原二叉树,你能帮PIPI还原吗?
输入
第一行输入序列的长度n (0<n<100).
第二行输入二叉树的中序序列。
第三行输入二叉树的后序序列。
输出
输出二叉树的先序遍历序列。
思路:依然采用前序遍历,“自上而下”的建树
按照我们的手工模拟,分为以下几步
第一步:选取后序的最后一个数字为根节点
第二步:在中序序列中寻找到根节点位置middle,将中序序列划分为左子树区间和右子树区间的部分,获取两个区间的长度
第三步:递归的构造出左子树和右子树即可
代码:
#include<bits/stdc++.h>
using namespace std;
struct TreeNode{
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int x):val(x),left(NULL),right(NULL){}; //节点构造函数
};
TreeNode* InndPost_BuildTree(vector<int>inorder,int inleft,int inright,vector<int>postorder,int postleft,int postright){ //中序和后序构建二叉树
//终止条件
if(postleft>postright) return NULL;
//单层递归
int k=postorder[postright];
TreeNode* root=new TreeNode(k); //选取后序序列的最后一个数为根节点
int middle;
for(middle=inleft;middle<inright;middle++)
if(inorder[middle]==k) break; //在中序序列中寻找到根节点
int leftlength=middle-inleft; //获取左子树区间长度
int rightlength=inright-middle; //获取右子树区间长度
root->left=InndPost_BuildTree(inorder,inleft,middle-1,postorder,postleft,postleft+leftlength-1); //递归的构造左子树,坚持左闭右闭原则
root->right=InndPost_BuildTree(inorder,middle+1,inright,postorder,postright-rightlength,postright-1); //递归的构造右子树,坚持左闭右闭原则
return root;
}
void prePrint(TreeNode* root){ //先序输出
//终止条件
if(!root) return; //空节点不打印
//单层递归逻辑
printf("%d ",root->val);
prePrint(root->left);
prePrint(root->right);
}
int main(){
int n;
scanf("%d",&n); //输入序列长度
vector<int>inorder(n);
vector<int>postorder(n);
int x;
for(int i=0;i<n;i++){ //输入中序序列
scanf("%d",&x);
inorder[i]=x;
}
for(int i=0;i<n;i++){ //输入后序序列
scanf("%d",&x);
postorder[i]=x;
}
TreeNode* root=InndPost_BuildTree(inorder,0,inorder.size()-1,postorder,0,postorder.size()-1); //左闭右闭
prePrint(root); //先序输出
return 0;
}
二叉搜索树的先序序列构建二叉树PIPIOJ
题目描述
PIPI现在知道了一棵二叉搜索树的先序序列,你能帮PIPI还原这棵二叉树吗?
输入
第一行输入序列的长度n (0<n<100).
第二行输入二叉树的先序序列。
输出
输出二叉树的后序遍历序列。
思路:二叉搜索树的一个重要特性就是通过中序遍历,我们得到的为一个升序数组,于是这道题我们只要将前序序列排序一遍,再按照前序+中序的方式建立即可
代码:
#include<bits/stdc++.h>
using namespace std;
struct TreeNode{
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int x):val(x),left(NULL),right(NULL){}; //节点构造函数
};
TreeNode* PreandIn_BuildTree(vector<int>preorder,int preleft,int preright,vector<int>inorder,int inleft,int inright){ //前序+中序构建二叉树
//终止条件
if(preleft>preright) return NULL;
//单层递归
int k=preorder[preleft];
TreeNode* root=new TreeNode(k); //选取前序序列的第一个数为根节点
int middle;
for(middle=inleft;middle<inright;middle++)
if(inorder[middle]==k) break; //在中序序列中寻找到根节点
int leftlength=middle-inleft; //获取左子树区间长度
int rightlength=inright-middle; //获取右子树区间长度
root->left=PreandIn_BuildTree(preorder,preleft+1,preleft+leftlength,inorder,inleft,middle-1); //递归的构造左子树,坚持左闭右闭原则
root->right=PreandIn_BuildTree(preorder,preright-rightlength+1,preright,inorder,middle+1,inright); //递归的构造右子树,坚持左闭右闭原则
return root;
}
void postPrint(TreeNode* root){ //后序输出
//终止条件
if(!root) return; //空节点不打印
//单层递归逻辑
postPrint(root->left);
postPrint(root->right);
printf("%d ",root->val);
}
int main(){
int n;
scanf("%d",&n); //输入序列长度
vector<int>preorder(n);
vector<int>inorder(n);
int x;
for(int i=0;i<n;i++){ //输入先序序列
scanf("%d",&x);
preorder[i]=x;
inorder[i]=x;
}
sort(inorder.begin(),inorder.end()); //对先序序列进行排序得到中序序列
TreeNode* root=PreandIn_BuildTree(preorder,0,preorder.size()-1,inorder,0,inorder.size()-1); //左闭右闭
postPrint(root); //先序输出
return 0;
}
构建哈夫曼树PIPIOJ
题目描述
有一棵二叉树有n个叶子结点,每个叶子结点有一个权值,求最小带权路径长度。
输入
多组输入。
第一行输入叶子结点数n(1<=n<=1000)。
接下来一行输入n个正整数(不超过1000),代表每个叶子结点的权值。
输出
输出由这n个叶子结点构成的二叉树的最小带权路径长度。
思路:这道题如果从链式结构建树稍显复杂,因为这是一个“自下而上”的过程,但是不要忘了,树可不是只有一种表示方法,所以可以用带有孩子双亲的数组表示法来构建哈夫曼树。根据哈夫曼树的特点我们可以知道,给定n个数字,最终我们会得到一颗带有2n-1个节点的哈夫曼树,其中n个数字就是这颗哈夫曼树的n个叶子节点。所以我们不妨申请一个2n-1大小的可以表示孩子和双亲的数组,大致分为以下几步
第一步:从前n个数字里面挑选出2个最小的数字合并成第n+1个数字,并给他们的孩子和双亲赋值
第二步:从前n+1个数字里挑选出没有双亲的2个最小数组合并成第n+2个数字,并给他们的孩子和双亲赋值
第三步:重复第二步的逻辑,直到构建完n-1个节点
代码:
#include<bits/stdc++.h>
using namespace std;
struct TreeNode{ //哈夫曼树的节点类型
int data; //节点值
int parent; //双亲节点
int left,right; //左右孩子
};
void CreatHT(vector<TreeNode>& HTTree,int n){ //构建哈夫曼树
int min1,min2,leftnode,rightnode;
for(int i=0;i<2*n-1;i++) //初始化节点
HTTree[i].parent=HTTree[i].left=HTTree[i].right=-1;
for(int i=n;i<2*n-1;i++){ //一共要构建n-1个节点
min1=min2=INT_MAX; //每一轮最小值都要初始化
leftnode=rightnode=-1; //标记左右孩子节点
for(int j=0;j<i;j++){ //挑选两个最小的节点
if(HTTree[j].parent==-1){ //只挑选没有双亲的节点
if(HTTree[j].data<min1){ //保持左节点不比右节点大的原则
min2=min1;rightnode=leftnode; //将左节点赋值给右节点
min1=HTTree[j].data;leftnode=j; //更新左节点
}
else if(HTTree[j].data<min2){ // 更新右节点
min2=HTTree[j].data;rightnode=j;
}
}
}
//构建新的哈夫曼节点
HTTree[i].data=min1+min2;
HTTree[i].left=leftnode;HTTree[i].right=rightnode;
HTTree[leftnode].parent=HTTree[rightnode].parent=i; //将新的哈夫曼节点作为双亲节点
}
}
int main(){
int n;
int sum; //总的带权路径
while(cin>>n){
sum=0; //初始化
vector<TreeNode>HTTree(2*n-1);
int x;
for(int i=0;i<n;i++){ //输入n个叶子节点的权值
scanf("%d",&x);
HTTree[i].data=x; //给节点赋权值
}
CreatHT(HTTree,n);
for(int i=n;i<2*n-1;i++)
sum+=HTTree[i].data;
printf("%d\n",sum);
HTTree.clear(); //清空
}
return 0;
}
二叉树的祖先系列
节点与其祖先之间的最大差值PIPIOJ
题目描述
给定二叉树的根节点 root,找出存在于不同节点 A和 B 之间的最大值V,其中 V = |A.val - B.val|,且 A 是 B 的祖先。
输入:8 3 1 -1 -1 6 4 -1 -1 7 -1 -1 10 -1 14 13 -1 -1 -1 输出:7
输入
输入一行,按照先序输入一棵二叉树,其中空节点用 -1 表示。
输出。
输出一行代表最大差值V。
思路:要找所有子树中祖先与子孙的差值,显然是一个“自上而下”的过程,所以这道题我们采用前序遍历,那么如何寻找最大差值呢,首先可以明确,最大差值所对应的两个数字一定是一个最大的和一个最小的,同时我们也要满足最大值和最小值祖孙关系。所以代码思路就出来了,我们只需要在遍历的时候携带上同一颗子树上的最大值和最小值即可
递归三部曲
第一曲:确定函数返回值和参数
返回值为void类型,参数为一个节点指针和一个最大值,一个最小值
第二曲:终止条件
遍历到空节点时返回
第三曲:单层递归逻辑
当遇到“同一颗子树上”比最大(小)值更大(小)的节点,对最值进行更新,判断是否对最大差值(全局变量)进行更新
注意上述提到的同一颗子树,因为代码里面会有最大值和最小值的回溯过程
代码:
#include<bits/stdc++.h>
using namespace std;
struct TreeNode{
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int x):val(x),left(NULL),right(NULL){}; //节点构造函数
};
int ans=0;
void maxAncestorDiff(TreeNode* root,int max,int min){
//终止条件
if(!root) return;
//单层递归逻辑
if(root->val>max){ //同一颗子树如果找到更大的最大值
max=root->val;
ans=max-min>ans?max-min:ans; //更新最大差值
}
if(root->val<min){ //同一颗子树如果找到更小的最小值
min=root->val;
ans=max-min>ans?max-min:ans; //更新最大差值
}
maxAncestorDiff(root->left,max,min);
maxAncestorDiff(root->right,max,min);
//注意,函数返回上一层时,min和max有一步回溯的过程
}
void BuildTree(TreeNode* &root){ //前序输入构建一颗二叉树
int x;
scanf("%d",&x);
if(x==-1) {
root=NULL;
return; //空节点直接返回
}
root=new TreeNode(x); //构造根节点
BuildTree(root->left); //构造左子树
BuildTree(root->right); //构造右子树
}
int main(){
TreeNode* root;
BuildTree(root); //构造二叉树
maxAncestorDiff(root,root->val,root->val);
printf("%d",ans);
return 0;
}
树中节点的祖先PIPIOJ
题目描述
PIPI有一棵结点值均不相同的二叉树,现在他想知道某结点的祖先结点有哪些,你能帮PIPI解决这个问题吗?
输入
第一行按照先序输入一棵二叉树,其中空节点用 -1 表示。
第二行输入待查询结点的值。
输出
输出一行,代表该结点的祖先节点,从根节点开始输出。若树中无该结点,输出"No Node!"。若树中某结点无祖先,输出 "No Ancestor!"
思路:要知道一个节点的祖先节点,很明显是一个“自下而上”的过程,所以这道题可以采用后序遍历
递归三部曲
第一曲:确定函数返回值和参数
返回值为void,参数为一个节点指针和要寻找的节点数字
第二曲:终止条件
1.遇到空节点时结束
2.目标节点已找到时结束
第三曲:单层递归逻辑
当我们已经找到目标节点时,将函数返回过程中路径上的节点存入数组即可
代码:
#include<bits/stdc++.h>
using namespace std;
struct TreeNode{
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int x):val(x),left(NULL),right(NULL){}; //节点构造函数
};
vector<int>res;
int flag=0; //标记节点是否找到
void AncestorNode(TreeNode* root,int x){
//终止条件
if(!root||flag) return; //空节点或者节点已找到
if(root->val==x){
flag=1;
return;
}
//单层递归逻辑
AncestorNode(root->left,x);
AncestorNode(root->right,x);
if(flag) res.push_back(root->val); //找到节点后,将祖先逆序存入结果数组
}
void BuildTree(TreeNode* &root){ //前序输入构建一颗二叉树
int x;
scanf("%d",&x);
if(x==-1) {
root=NULL;
return; //空节点直接返回
}
root=new TreeNode(x); //构造根节点
BuildTree(root->left); //构造左子树
BuildTree(root->right); //构造右子树
}
int main(){
TreeNode* root;
BuildTree(root); //构造二叉树
int x;
scanf("%d",&x); //输入要查询的值
AncestorNode(root,x);
if(flag){ //如果找到该节点
if(res.size()==0) printf("No Ancestor!\n"); //该节点无祖先
else{
for(int i=res.size()-1;i>=0;i--)
printf("%d ",res[i]);
}
}
else printf("No Node!\n");
return 0;
}
最近公共祖先PIPIOJ
题目描述
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
5和4的公共祖先为5
输入
输入两行。
第一行按照先序输入一棵二叉树,其中空节点用 -1 表示。
第二行输入两个结点的值val1, val2 , 输出该结点的双亲节点的val.(数据保证所有节点val的值都不相同)
输出:输出一行,代表最近公共祖先的val。
思路:首先我们来探讨下p和q的公共祖先有哪几种情况?
第一种:p=7,q=4,则最近公共祖先为2,也就是说我们遍历到2时,发现它的左孩子和右孩子分别是p和q,这时候我们将2返回即可
第二种:p=5,q=4,很明显我们要返回的便是5,这时候出来一个问题,当我们遍历到5时还需要继续遍历吗?答案是不用,因为我们之后遍历3的右子树一定会发现q不存在,那么5即为最近公共祖先,所以我们遍历到5时直接返回
综上,我们遍历到p和q其中之一的时候即可停止遍历,因为在之后的遍历过程中,结果一定是上述两种情况中的一种。由上面的过程不难发现这是一个自下而上的过程,所以我们采用后序遍历
递归三部曲
第一曲:确定函数返回值和参数
返回值为节点指针,参数为一个节点指针和要寻找的节点数字p和q
第二曲:终止条件
1.遇到空节点时返回
2.遇到p或q时返回
第三曲:单层递归逻辑
在根节点的左右子树中分别想寻找p或q或他们的最近公共祖先
代码:
#include<bits/stdc++.h>
using namespace std;
struct TreeNode{
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int x):val(x),left(NULL),right(NULL){}; //节点构造函数
};
TreeNode* lowestCommonAncestor(TreeNode* root,int p,int q){
//终止条件
if(!root) return NULL; //空节点返回
if(root->val==p||root->val==q) return root; //找到p或q本身
//单层递归逻辑
TreeNode* leftnode=lowestCommonAncestor(root->left,p,q); //在左子树中找最近公共祖先
TreeNode* rightnode=lowestCommonAncestor(root->right,p,q); //在右子树中找最近公共祖先
if(leftnode&&rightnode) return root; //如果左右均不空,则当前节点为最近公共祖先
else if(!leftnode) return rightnode; //如果左空,证明右边返回的的是最近公共祖先
else return leftnode; #include<bits/stdc++.h>
using namespace std;
struct TreeNode{
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int x):val(x),left(NULL),right(NULL){}; //节点构造函数
};
TreeNode* lowestCommonAncestor(TreeNode* root,int p,int q){
//终止条件
if(!root) return NULL; //空节点返回
if(root->val==p||root->val==q) return root; //找到p或q本身
//单层递归逻辑
TreeNode* leftnode=lowestCommonAncestor(root->left,p,q); //在左子树中找p或q或最近公共祖先
TreeNode* rightnode=lowestCommonAncestor(root->right,p,q); //在右子树中找p或q或最近公共祖先
if(leftnode&&rightnode) return root; //如果左右均不空,则找到的为p和q,当前节点为最近公共祖先
else if(!leftnode) return rightnode; //如果左空,证明右边返回的的是最近公共祖先
else return leftnode; //如果右空,证明左边返回的的是最近公共祖先
}
void BuildTree(TreeNode* &root){ //前序输入构建一颗二叉树
int x;
scanf("%d",&x);
if(x==-1) {
root=NULL;
return; //空节点直接返回
}
root=new TreeNode(x); //构造根节点
BuildTree(root->left); //构造左子树
BuildTree(root->right); //构造右子树
}
int main(){
TreeNode* root;
BuildTree(root); //构造二叉树
int p,q;
scanf("%d%d",&p,&q); //输入要查询的值
printf("%d",lowestCommonAncestor(root,p,q)->val); //输出公共祖先
return 0;
}
}
void BuildTree(TreeNode* &root){ //前序输入构建一颗二叉树
int x;
scanf("%d",&x);
if(x==-1) {
root=NULL;
return; //空节点直接返回
}
root=new TreeNode(x); //构造根节点
BuildTree(root->left); //构造左子树
BuildTree(root->right); //构造右子树
}
int main(){
TreeNode* root;
BuildTree(root); //构造二叉树
int p,q;
scanf("%d%d",&p,&q); //输入要查询的值
printf("%d",lowestCommonAncestor(root,p,q)->val); //输出公共祖先
return 0;
}
祖父结点为偶数的节点PIPIOJ
题目描述
PIPI有一棵二叉树,他想知道所有祖父结点值为偶数的结点值之和。
输入:6 7 2 9 -1 -1 -1 7 1 -1 -1 4 -1 -1 8 1 -1 -1 3 -1 5 -1 -1 输出:18
解释:图中红色节点的祖父节点的值为偶数,蓝色节点为这些红色节点的祖父节点。
输入:输入包含一行,先序输入一棵二叉树,空节点用-1表示。
输出:输出为满足条件的结点值之和。
思路:遍历整棵树寻找那些偶数节点,统计这些偶数节点的子孙数字和,显然是一个“自上而下”的过程,所以我们采用前序遍历
递归三部曲
第一曲:确定函数返回值和参数
返回值为void,参数为一个节点指针
第二曲:终止条件
1.遇到空节点时返回
第三曲:单层递归逻辑
如果当前节点是偶数节点,则统计它的子孙数字和
代码:
#include<bits/stdc++.h>
using namespace std;
struct TreeNode{
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int x):val(x),left(NULL),right(NULL){}; //节点构造函数
};
int sum=0;
void sumEvenGrandparent(TreeNode* root){
//终止条件
if(!root) return;
//单层递归逻辑
if(root->val%2==0){ //当前节点为偶数,则统计子孙和
//先判断是否有孩子,再判断是否有子孙,顺序不能反
if(root->left&&root->left->left) sum+=root->left->left->val;
if(root->left&&root->left->right) sum+=root->left->right->val;
if(root->right&&root->right->left) sum+=root->right->left->val;
if(root->right&&root->right->right) sum+=root->right->right->val;
}
sumEvenGrandparent(root->left);
sumEvenGrandparent(root->right);
}
void BuildTree(TreeNode* &root){ //前序输入构建一颗二叉树
int x;
scanf("%d",&x);
if(x==-1) {
root=NULL;
return; //空节点直接返回
}
root=new TreeNode(x); //构造根节点
BuildTree(root->left); //构造左子树
BuildTree(root->right); //构造右子树
}
int main(){
TreeNode* root;
BuildTree(root); //构造二叉树
sumEvenGrandparent(root);
printf("%d",sum);
return 0;
}
PIPI的族谱PIPIOJ
题目描述
PIPI最近在看家里的族谱,发现族谱刚好组成了一棵二叉树,现在PIPI想询问族谱中的两个结点是否为兄弟或者堂兄弟。
兄弟: 深度相同, 双亲节点相同(同一个结点不能是兄弟)。
堂兄弟: 深度相同,双亲节点不同。
输入
第一行按照先序输入族谱代表的二叉树,其中空节点用 -1 表示。
第二行输入两个数字 x y,代表询问的两个结点的值。
输出
若询问的两个结点是兄弟,输出"brother" , 若询问的两个结点是堂兄弟,输出"cousin" ,否则输出"other relathionship"(relationship写错了 , 请同学们直接复制"other relathionship")
思路:显然这是一道标准的层序遍历题,直接套模板,除此之外,我们需要增加两个父节点指针来指向x和y的父节点从而判断x和y是否在同一层
代码:
#include<bits/stdc++.h>
using namespace std;
struct TreeNode{
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int x):val(x),left(NULL),right(NULL){}; //节点构造函数
};
int flagBrother=0,flagCousin=0; //标记类型
void isBrotherorCousinsor(TreeNode* root,int x,int y){
TreeNode* xparent=NULL,* yparent=NULL; //x和y的父节点
queue<TreeNode*>Que;
Que.push(root);
while(!Que.empty()){ //队空结束
int leversize=Que.size(); //获取层宽
for(int i=0;i<leversize;i++){ //遍历该层
TreeNode* cur=Que.front(); Que.pop(); //取出队头
if(cur->left){
Que.push(cur->left);
xparent=cur->left->val==x?cur:xparent; //寻找父节点
yparent=cur->left->val==y?cur:yparent;
}
if(cur->right){
Que.push(cur->right);
xparent=cur->right->val==x?cur:xparent; //寻找父节点
yparent=cur->right->val==y?cur:yparent;
}
}
if(xparent&&yparent){ //如果两个节点出现在同一层(父节点出现在同一层)
if(xparent==yparent) flagBrother=1; //如果父节点相同
else flagCousin=1; //如果父节点不同
}
if(xparent||yparent) return; //如果同一层只出现了一个节点
}
}
void BuildTree(TreeNode* &root){ //前序输入构建一颗二叉树
int x;
scanf("%d",&x);
if(x==-1) {
root=NULL;
return; //空节点直接返回
}
root=new TreeNode(x); //构造根节点
BuildTree(root->left); //构造左子树
BuildTree(root->right); //构造右子树
}
int main(){
TreeNode* root;
BuildTree(root); //构造二叉树
int x,y;
scanf("%d%d",&x,&y);
isBrotherorCousinsor(root,x,y);
if(flagBrother) printf("brother\n");
else if(flagCousin) printf("cousin\n");
else printf("other relathionship\n");
return 0;
}
二叉树的修改系列
更大和树PIPIOJ
题目描述
给出二叉搜索树的根节点,该二叉树的节点值各不相同,修改二叉树,使每个节点node 的新值等于原树中大于或等于 node.val 的值之和。
输入:4 1 0 -1 -1 2 -1 3 -1 -1 6 5 -1 -1 7 -1 8 -1 -1
输出:30 36 36 -1 -1 35 -1 33 -1 -1 21 26 -1 -1 15 -1 8 -1 -1
输入:输入一行,按照先序输入一棵二叉树,其中空节点用 -1 表示。
输出:输出更大和树的先序序列。
思路:首先解释一下题目和图的意思,由于要让每个节点都大于等于原树中的任何一个节点,又因为这是一颗二叉搜索树,所以从最右下角(最大的)数字8开始修改,逐个往上累加即可,同时在这个累加的过程中,处理的顺序是右根左(想想为什么这样可以满足题目要求)。
根据上述过程,我们只要按照右根左的遍历顺序,将节点逐渐累加到下一个遍历的节点即可
递归三部曲
第一曲:确定函数返回值和参数
返回值为void,参数为一个节点指针以及累加值
第二曲:终止条件
遇到空节点时返回
第三曲:单层递归逻辑
将当前节点的值修改为累加值和自身节点的和
代码:
#include<bits/stdc++.h>
using namespace std;
struct TreeNode{
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int x):val(x),left(NULL),right(NULL){}; //节点构造函数
};
int Allsum=0; //累加值
void convertBST(TreeNode* root){ //更大和数
//终止条件
if(!root) return;
//单层递归逻辑
convertBST(root->right);
root->val+=Allsum; //修改节点值
Allsum=root->val; //修改累加值
convertBST(root->left);
}
void BuildTree(TreeNode* &root){ //前序输入构建一颗二叉树
int x;
scanf("%d",&x);
if(x==-1) {
root=NULL;
return; //空节点直接返回
}
root=new TreeNode(x); //构造根节点
BuildTree(root->left); //构造左子树
BuildTree(root->right); //构造右子树
}
void prePrint(TreeNode* root){
//终止条件
if(!root){
printf("-1 ");
return;
}
//单层递归逻辑
printf("%d ",root->val);
prePrint(root->left);
prePrint(root->right);
}
int main(){
TreeNode* root;
BuildTree(root); //构造二叉树
convertBST(root);
prePrint(root); //先序输出
return 0;
}
翻转二叉树PIPIOJ
题目描述
给定一棵二叉树,交换二叉树的左右子树。
输入:输入一行,按照先序输入一棵二叉树,其中空节点用 -1 表示。
输出:输出交换后的二叉树的先序序列,空节点无需输出。
思路:这道题采用后序遍历逻辑更为清晰,即“自下而上”的进行翻转,实际上自上而下也可以,仅仅修改代码的位置即可
递归三部曲
第一曲:确定函数返回值和参数
返回值为void,参数为一个节点指针
第二曲:终止条件
遇到空节点时返回
第三曲:单层递归逻辑
将当前根节点的左右子树进行交换
代码:
#include<bits/stdc++.h>
using namespace std;
struct TreeNode{
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int x):val(x),left(NULL),right(NULL){}; //节点构造函数
};
void invertTree(TreeNode* root){ //翻转二叉树
//终止条件
if(!root) return;
//单层递归逻辑
invertTree(root->left);
invertTree(root->right);
swap(root->left,root->right); //交换左右子树
}
void BuildTree(TreeNode* &root){ //前序输入构建一颗二叉树
int x;
scanf("%d",&x);
if(x==-1) {
root=NULL;
return; //空节点直接返回
}
root=new TreeNode(x); //构造根节点
BuildTree(root->left); //构造左子树
BuildTree(root->right); //构造右子树
}
void prePrint(TreeNode* root){
//终止条件
if(!root) return;
//单层递归逻辑
printf("%d ",root->val);
prePrint(root->left);
prePrint(root->right);
}
int main(){
TreeNode* root;
BuildTree(root); //构造二叉树
invertTree(root);
prePrint(root);
return 0;
}
删除给定值的叶子节点PIPIOJ
题目描述
PIPI有一棵二叉树和一个整数 target ,请你删除所有值为 target 的 叶子节点 。
注意,一旦删除值为 target 的叶子节点,它的父节点就可能变成叶子节点;如果新叶子节点的值恰好也是 target ,那么这个节点也应该被删除。
输入
第一行按照先序输入树T,其中空节点用 -1 表示。
第二行输入target
输出:按照先序输出删完之后的二叉树。
思路:首先可以明确我们要遍历整棵树,其次这是一个“自下而上的”过程,即后序遍历,有了这两点,我们的思路就浮出水面了
递归三部曲
第一曲:确定函数返回值和参数
返回值为节点指针,方便在删除目标节点后让父节点的孩子指针置空
参数为一个节点指针和目标值
第二曲:终止条件
遇到空节点时返回
第三曲:单层递归逻辑
如果当前的节点是目标节点,则将其删除
代码:
#include<bits/stdc++.h>
using namespace std;
struct TreeNode{
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int x):val(x),left(NULL),right(NULL){}; //节点构造函数
};
TreeNode* DeleteTarget(TreeNode* root,int target){ //删除给定的叶子节点
//终止条件
if(!root) return NULL;
//单层递归逻辑
root->left=DeleteTarget(root->left,target);
root->right=DeleteTarget(root->right,target);
if(root->val==target&&!root->left&&!root->right){
delete(root);
return NULL;
}
return root;
}
void BuildTree(TreeNode* &root){ //前序输入构建一颗二叉树
int x;
scanf("%d",&x);
if(x==-1) {
root=NULL;
return; //空节点直接返回
}
root=new TreeNode(x); //构造根节点
BuildTree(root->left); //构造左子树
BuildTree(root->right); //构造右子树
}
void prePrint(TreeNode* root){
//终止条件
if(!root) return;
//单层递归逻辑
printf("%d ",root->val);
prePrint(root->left);
prePrint(root->right);
}
int main(){
TreeNode* root;
BuildTree(root); //构造二叉树
int target;
scanf("%d",&target);
root=DeleteTarget(root,target);
prePrint(root); //先序输出
return 0;
}
为二叉排序树排序PIPIOJ
题目描述
现在给你两棵二叉排序树,要求你对两棵二叉排序树的中的所有元素进行升序排序。
输入:输入两行,每一行按照先序输入一棵二叉排序树,其中空节点用 -1 表示(二叉树的结点不超过2000)。
输出:输出升序排序之后的结果。
思路:根据二叉排序树的特点,只要采用中序遍历,得到的一定是升序序列,分别遍历两颗二叉排序树,将最后的共同结果排序即可(实际上可以用归并排序写,这里我就不写了)
代码:
#include<bits/stdc++.h>
using namespace std;
struct TreeNode{
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int x):val(x),left(NULL),right(NULL){}; //节点构造函数
};
vector<int>res;
void SortSearchtree(TreeNode* root){
//终止条件
if(!root) return;
SortSearchtree(root->left);
res.push_back(root->val);
SortSearchtree(root->right);
}
void BuildTree(TreeNode* &root){ //前序输入构建一颗二叉树
int x;
scanf("%d",&x);
if(x==-1) {
root=NULL;
return; //空节点直接返回
}
root=new TreeNode(x); //构造根节点
BuildTree(root->left); //构造左子树
BuildTree(root->right); //构造右子树
}
void prePrint(TreeNode* root){
//终止条件
if(!root) return;
//单层递归逻辑
printf("%d ",root->val);
prePrint(root->left);
prePrint(root->right);
}
int main(){
TreeNode* root1,* root2;
BuildTree(root1); //构造二叉树
BuildTree(root2);
SortSearchtree(root1);
SortSearchtree(root2);
sort(res.begin(),res.end());
for(int i=0;i<res.size();i++)
printf("%d ",res[i]);
return 0;
}
二叉搜索树的插入
701. 二叉搜索树中的插入操作 - 力扣(LeetCode)
给定二叉搜索树(BST)的根节点 root 和要插入树中的值 value ,将值插入二叉搜索树。 返回插入后二叉搜索树的根节点。 输入数据 保证 ,新值和原始二叉搜索树中的任意节点值都不同。
注意,可能存在多种有效的插入方式,只要树在插入后仍保持为二叉搜索树即可。 你可以返回 任意有效的结果 。
输入:5(要插入的值x) 4 2 1 -1 -1 3 -1 -1 7 -1 -1 输出:1 2 3 4 5 7
输入
第一行输入要插入的值
第二行按照先序输入树T,其中空节点用 -1 表示。
输出:按照升序输出插入之后的二叉树。
思路:类比于手工插入的逻辑寻找合法的插入位置
递归三部曲
第一曲:确定函数返回值和参数
返回值为节点指针,可以利用返回值完成新加入的节点与其父节点的赋值操作
参数为节点指针和要插入的值x
第二曲:终止条件
遍历到到空节点时进行插入,结束遍历
第三曲:单层递归逻辑
1.如果当前的节点小于x,则往右子树寻找插入位置
2.如果当前的结点大于x,则往左子树寻找插入位置
代码:
#include<bits/stdc++.h>
using namespace std;
struct TreeNode{
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int x):val(x),left(NULL),right(NULL){}; //节点构造函数
};
TreeNode* insertIntoBST(TreeNode* root,int x){
//终止条件
if(!root){
TreeNode* newnode=new TreeNode(x); //建立新节点插入
return newnode;
}
//单层递归逻辑
if(root->val<x) root->right=insertIntoBST(root->right,x);
else if(root->val>x) root->left=insertIntoBST(root->left,x);
else return root;
}
void BuildTree(TreeNode* &root){ //前序输入构建一颗二叉树
int x;
scanf("%d",&x);
if(x==-1) {
root=NULL;
return; //空节点直接返回
}
root=new TreeNode(x); //构造根节点
BuildTree(root->left); //构造左子树
BuildTree(root->right); //构造右子树
}
void InorderPrint(TreeNode* root){
//终止条件
if(!root) return;
//单层递归逻辑
InorderPrint(root->left);
printf("%d ",root->val);
InorderPrint(root->right);
}
int main(){
TreeNode* root;
int x;
scanf("%d",&x); //输入要插入的值
BuildTree(root); //构造二叉树
root=insertIntoBST(root,x);
InorderPrint(root);
return 0;
}
删除二叉搜索树的节点
450. 删除二叉搜索树中的节点 - 力扣(LeetCode)
给定一个二叉搜索树的根节点 root 和一个值 key,删除二叉搜索树中的 key 对应的节点,并保证二叉搜索树的性质不变。
一般来说,删除节点可分为两个步骤:
首先找到需要删除的节点;
如果找到了,删除它。
输入:3(要删除的值) 5 3 2 -1 -1 4 -1 -1 6 -1 7 -1 -1
输出:2 4 5 6 7
输入
第一行输入要删除的值
第二行按照先序输入树T,其中空节点用 -1 表示。
输出:按照升序输出删除之后的二叉树。
思路:首先我们来讨论下有哪些删除情况以及如何处理?
1.树中不存在要删除的节点
2.删除的为叶子节点,直接删除即可
3.删除的节点仅有孩子,将孩子作为被删除节点的父节点的新孩子
4.删除的节点有两个孩子,则将左子树赋值给右子树的最左下角的节点,再将删除节点的右孩子返回即可
有了以上思路,代码逻辑就清晰了
递归三部曲
第一曲:确定函数返回值和参数
返回值为节点指针,可以利用返回值完成新加入的节点与其父节点的赋值操作
参数为节点指针和要删除的值x
第二曲:终止条件
1.遍历到空节点结束
2.遍历到要删除的目标节点,删除后结束遍历
第三曲:单层递归逻辑
1.如果当前的节点小于x,则往右子树寻找删除目标
2.如果当前的结点大于x,则往左子树寻找删除目标
代码:
#include<bits/stdc++.h>
using namespace std;
struct TreeNode{
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int x):val(x),left(NULL),right(NULL){}; //节点构造函数
};
TreeNode* deleteNode(TreeNode* root,int x){
//终止条件
if(!root) return NULL; //无删除目标
if(root->val==x){ //找到要删除的节点
if(!root->left&&!root->right){ //删除叶子节点
delete(root);
return NULL;
}
else if(!root->left||!root->right){ //删除仅有一个孩子节点的情况
TreeNode* temp=root->left==NULL?root->right:root->left; //保存孩子节点
delete(root);
return temp;
}
else{ //删除节点有两个孩子节点的情况
TreeNode* cur=root->right;
while(cur->left) cur=cur->left; //找到右子树最左下角的节点
cur->left=root->left; //将被删除节点的左子树挪过去
TreeNode* temp=root->right; //保存要返回的右子树
delete(root);
return temp;
}
}
//单层递归逻辑
if(root->val<x) root->right=deleteNode(root->right,x);
if(root->val>x) root->left=deleteNode(root->left,x);
return root;
}
void BuildTree(TreeNode* &root){ //前序输入构建一颗二叉树
int x;
scanf("%d",&x);
if(x==-1) {
root=NULL;
return; //空节点直接返回
}
root=new TreeNode(x); //构造根节点
BuildTree(root->left); //构造左子树
BuildTree(root->right); //构造右子树
}
void InorderPrint(TreeNode* root){
//终止条件
if(!root) return;
//单层递归逻辑
InorderPrint(root->left);
printf("%d ",root->val);
InorderPrint(root->right);
}
int main(){
TreeNode* root;
int x;
scanf("%d",&x); //输入要删除的值
BuildTree(root); //构造二叉树
root=deleteNode(root,x);
InorderPrint(root);
return 0;
}
修建二叉搜索树
给你二叉搜索树的根节点 root ,同时给定最小边界low 和最大边界 high。通过修剪二叉搜索树,使得所有节点的值在[low, high]中。修剪树 不应该 改变保留在树中的元素的相对结构 (即,如果没有被移除,原有的父代子代关系都应当保留)。 可以证明,存在 唯一的答案 。
所以结果应当返回修剪好的二叉搜索树的新的根节点。注意,根节点可能会根据给定的边界发生改变。
输入:1,3(要保存的区间) 3 0 -1 2 1 -1 -1 -1 4 -1 -1
输出:2 4 5 6 7
输入
第一行输入要删除的区间
第二行按照先序输入树T,其中空节点用 -1 表示。
输出:按照升序输出插入之后的二叉树。
思路:如果理解了上一道题目,再看这道题会感觉莫名的亲切对吧,但这里面还是有一些变化的,首先我们不对节点进行删除了,因为在寻找符合区间的节点时,我们一定是按树为单位的进行抛弃,要删除的话很费时,所以我们这里换一种思路,只保留符合区间的节点,也就是“修剪”。
递归三部曲
第一曲:确定函数返回值和参数
返回值为节点指针,可以利用返回值完成新加入的节点与其父节点的赋值操作
参数为节点指针和要保留的区间low和high
第二曲:终止条件
遍历到空节点结束
第三曲:单层递归逻辑
1.如果当前的节点小于low,则对右子树进行修剪,返回右子树(抛弃左子树)
2.如果当前的结点大于high,则对左子树进行修剪,返回左子树(抛弃右子树)
3.如果当前的结点在区间内,则对当前节点的左右子树进行修剪
代码:
#include<bits/stdc++.h>
using namespace std;
struct TreeNode{
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int x):val(x),left(NULL),right(NULL){}; //节点构造函数
};
TreeNode* trimBST(TreeNode* root,int low,int high){
//终止条件
if(!root) return NULL; //无删除目标
//单层递归逻辑
if(root->val<low){
TreeNode* right=trimBST(root->right,low,high); //对右子树进行修剪
return right; //返回修剪后的右子树
}
else if(root->val>high){
TreeNode* left=trimBST(root->left,low,high); //对左子树进行修剪
return left; //返回修剪后的左子树
}
else{ //当前节点在区间内
root->left=trimBST(root->left,low,high);
root->right=trimBST(root->right,low,high);
return root;
}
}
void BuildTree(TreeNode* &root){ //前序输入构建一颗二叉树
int x;
scanf("%d",&x);
if(x==-1) {
root=NULL;
return; //空节点直接返回
}
root=new TreeNode(x); //构造根节点
BuildTree(root->left); //构造左子树
BuildTree(root->right); //构造右子树
}
void InorderPrint(TreeNode* root){
//终止条件
if(!root) return;
//单层递归逻辑
InorderPrint(root->left);
printf("%d ",root->val);
InorderPrint(root->right);
}
int main(){
TreeNode* root;
int low,high;
scanf("%d%d",&low,&high); //输入要删除的区间
BuildTree(root); //构造二叉树
root=trimBST(root,low,high);
InorderPrint(root);
return 0;
}
二叉树的线索化系列
以下的代码我们均先通过前序构造一颗二叉树,再分别利用前中后序对其进行线索化,并且只给出算法,代码实现不好给出,因为我还没看见哪个网站有这类题目可以让我们提交
在这里统一给出结构体定义和递归思路
struct TreeNode{
int val;
int ltag,rtag;
TreeNode* left;
TreeNode* right;
TreeNode(int x):val(x),left(NULL),right(NULL){}; //节点构造函数
};
递归三部曲
第一曲:确定函数返回值和参数
返回值为void类型,参数为节点指针
第二曲:终止条件
遍历到空节点结束
第三曲:单层递归逻辑
1.如果当前节点的左孩子为空,则让线索化当前节点的左孩子,让其指向前驱pre(上一个遍历的节点)
2.如果pre不空的话且pre的右孩子为空,则线索化前驱节点的右孩子,让其指向后继(当前遍历的节点)
前序线索化![6618b8e214a54ddc802fb604b7d4a76f.png](https://img-blog.csdnimg.cn/6618b8e214a54ddc802fb604b7d4a76f.png)
注意,在递归三部曲的代码逻辑基础上,前序线索化在进入递归前要先判断当前节点的ltag和rtag都没有被线索化,否则会进入死循环,如图中的4
TreeNode* pre=NULL; //pre定义为全局变量
void PreThread(TreeNode* cur){ //cur意指当前遍历的节点
//终止条件
if(!cur) return;
//单层递归逻辑
if(cur->left==NULL){ //当前节点的左孩子为空
cur->left=pre;
cur->ltag=1; //标记已被线索化
}
else cur->ltag=0; //否则标记不能被线索化
if(pre){ //前驱节点不为空
if(pre->right==NULL){ //前驱节点的右孩子为空
pre->right=cur;
pre->rtag=1; //标记已被线索化
}
else pre->trag=0; //否则标记不能被线索化
}
pre=cur; //将cur作为前驱节点
if(cur->ltag!=1)PreThread(cur->left); //线索化左子树
if(cur->rtag!=1)PreThread(cur->right); //线索化右子树
}
中序线索化![c6d0d868d6f44759aa14ff172715464f.png](https://img-blog.csdnimg.cn/c6d0d868d6f44759aa14ff172715464f.png)
TreeNode* pre=NULL; //pre定义为全局变量
void InorderThread(TreeNode* cur){ //cur意指当前遍历的节点
//终止条件
if(!cur) return;
//单层递归逻辑
InorderThread(cur->left); //线索化左子树
if(cur->left==NULL){ //当前节点的左孩子为空
cur->left=pre;
cur->ltag=1; //标记已被线索化
}
else cur->ltag=0; //否则标记不能被线索化
if(pre){ //前驱节点不为空
if(pre->right==NULL){ //前驱节点的右孩子为空
pre->right=cur;
pre->rtag=1; //标记已被线索化
}
else pre->trag=0; //否则标记不能被线索化
}
pre=cur; //将cur作为前驱节点
InorderThread(cur->right); //线索化右子树
}
后序线索化![b7319d4b3d6f47d096e80e8d15b88d0f.png](https://img-blog.csdnimg.cn/b7319d4b3d6f47d096e80e8d15b88d0f.png)
同理,后序也要加上判断
TreeNode* pre=NULL; //pre定义为全局变量
void PostThread(TreeNode* cur){ //cur意指当前遍历的节点
//终止条件
if(!cur) return;
//单层递归逻辑
if(cur->ltag!=1)PostThread(cur->left); //线索化左子树
if(cur->rtag!=1)PostThread(cur->right); //线索化右子树
if(cur->left==NULL){ //当前节点的左孩子为空
cur->left=pre;
cur->ltag=1; //标记已被线索化
}
else cur->ltag=0; //否则标记不能被线索化
if(pre){ //前驱节点不为空
if(pre->right==NULL){ //前驱节点的右孩子为空
pre->right=cur;
pre->rtag=1; //标记已被线索化
}
else pre->trag=0; //否则标记不能被线索化
}
pre=cur; //将cur作为前驱节点
}
总结
1.经过以上30多道题的磨练,相信大家已经见识到了递归三部曲的威力了,能够很好地帮我们制定递归的框架,而不是递着递着及就把自己绕晕了。
2.二叉树算法的第一步就是确定采用何种遍历方式,这里我并没有按照遍历方式分类算法,就是希望大家能够按照懂得去分析题目的特点,如果这点都做不好,何谈攻克呢
3.针对于二叉搜索树的算法,一定要清晰的知道它的特点,中序遍历是按照节点大小升序遍历,所以这类题往往采用中序最为合适,否则的话跟普通二叉树有什么区别呢
4.二叉树祖先系列里,如果问的是孙子的特点往往采用前序,如果是祖先的特点往往采用后序
5.二叉树构造系列中,一定要学会画图划分区间,并要始终坚持一个原则
6.二叉树的修建系列里我们往往会采用节点指针的返回值,这样可以很好的帮我们建立起当前节点和父节点的系