目录
3.检查这棵二叉树,有没有根直接到叶子这样的路径,若有,则计算一下“目标整数值”
实验目的及内容
解题思路
需求分析:有三大需求
1.根据所给的前序和中序序列,构建二叉树
这部分最复杂,我是采用递归的方法来做的。
总体思路为:通过递归,不断压缩中序和先序序列的长度,从而确定左右子树。左右子树的根节点就是左右孩子。
首先,用两个数组preorder、inorder分别存储用户给出的前序序列和中序序列,然后,必须借助这两个数组来进行构造↓↓↓↓
如图所示
第一步.从前序序列中找根(对于每一棵树,它的根总是第一个在前序序列中出现);
第二步.从中序序列中确定左右子树(在一棵树的范围内,根节点左边就是左子树、右边就是右子树),
- 同时左子树的根节点就是根节点
root
的左孩子,右子树的根节点就是根节点root
的右孩子。
- 一直递归。。。如此,便求出了每个节点的左右孩子。
终止条件:对照根和中序序列,确定出的左/右子树只有一个节点时;
每一次递归,我们都需要确定左右子树的范围(包含哪些节点),所以,需要用四个整形变量记录前序序列和中序序列中左右子树的范围;
![](https://i-blog.csdnimg.cn/blog_migrate/5a8dffce1328fecb421da7a64cdcbdb4.png)
关键点:找出先序序列中左右子树的分界点(范围):
方法:两种序列中,左子树和右子树的大小相等,从而可以求出分界点
可知,中序序列中,左子树的范围是:il到 il+rootIndex-1;右子树的范围是:rootIndex+1到 ir;
前序序列中,左子树的范围是:pl+1到pl+1+leftsize;右子树范围是:pl+1+leftsize+1到
pr;
2.按要求格式,前序输出这棵二叉树
这部分主要是一些细节处理,有点繁杂,但是不难想,主要是多试几次。实际上就是在基本的前序输出基础上再加一个缩进的效果,其规律是:只要访问了左右孩子,就要多输出一个空格
3.检查这棵二叉树,有没有根直接到叶子这样的路径,若有,则计算一下“目标整数值”
这部分较为简单,由于是二叉树,所以只要查一下左右两棵子树是否是叶子即可,如果是叶子,那么就存在这样的路径
实验代码及注释
TIPS:为了优化时间复杂度,我额外开一个map数组,专门存放各个节点在中序序列里的位置下标,这样每次递归就不用扫描查找相应根结点的下标了(扫描查找时间复杂度为O(n));
借助map数组进行查找的时间复杂度为O(1),这样创建这课二叉树的时间复杂度为O(logn),但是由于需要输出和输入,所以整体代码的时间复杂度是O(n);
#include <iostream>
using namespace std;
struct TreeNode
{
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(NULL), right(NULL) {}//C++类的初始化函数
};
// 二叉树创建函数(递归)
TreeNode *buildTree(int*map,int *preorder, int *inorder, int pl, int pr, int iL, int iR)
{
//递归终止条件,如果算出来的左/右子树为空时,返回NULL
if (pl > pr || iL > iR)
{
return NULL;
}
//总体的根为先序中的第一个元素
int rootVal = preorder[pl];
TreeNode *root = new TreeNode(rootVal);
// 在中序序列中,借助map数组找到当前根节点的位置rootIndex
int rootIndex = map[preorder[pl]];
// 计算出左子树的长度,从而也得出了右子树的范围
int leftSize = rootIndex - iL;
//继续递归建立左右子树
root->left = buildTree(map,preorder, inorder, pl + 1, pl + leftSize, iL, rootIndex - 1);
root->right = buildTree(map,preorder, inorder, pl + leftSize + 1, pr, rootIndex + 1, iR);
return root;//左子树的根节点就是父节点的左孩子(或右子树的根节点是父节点的右孩子),故返回根节点
}
// 按要求输出前序序列
void printPreorder(TreeNode *root, int depth)
{
if (root == NULL)
{
return;
}
//用depth来标记是第几层孩子,每访问一次孩子,层数+1
for (int i = 0; i < depth; i++)
{
cout << " ";
}
cout << "|" << root->val << endl;
printPreorder(root->left, depth + 1);
printPreorder(root->right, depth + 1);
}
// 判断一个节点是否为叶子的函数
bool IsLeaf(TreeNode *tmp)
{
if(tmp==NULL)
{
return false;
}
if (tmp->left == NULL && tmp->right == NULL)
{
return true;
}
else
{
return false;
}
}
// 判断是否有根到叶子的节点函数,若有,则按要求计算目标整数值,并输出
void JudgeSumPrint(TreeNode *root)
{
TreeNode *tmp;
tmp = root;
bool L = IsLeaf(tmp->left);
bool R = IsLeaf(tmp->right);
int sumL=root->val;
int sumR=root->val;
if (tmp == NULL)
{
return ;
cout<<"根结点为空,不存在从根到叶子的路径"<<endl;
}
if(L&&R)
{
cout<<"存在两条根到叶子的路径"<<endl;
cout<<"左边这条路径的值为:";
sumL+=tmp->left->val;
cout<<sumL<<endl;
cout<<"右边这条路径的值为:";
sumR+=tmp->right->val;
cout<<sumR<<endl;
}
else if(L)
{
cout<<"存在一条根到叶子的路径"<<endl;
cout<<"左边这条路径的值为:";
sumL+=tmp->left->val;
cout<<sumL<<endl;
}
else if(R)
{
cout<<"存在一条根到叶子的路径"<<endl;
cout<<"右边这条路径的值为:";
sumR+=tmp->right->val;
cout<<sumR<<endl;
}
else
{
cout<<"不存在从根到叶子的路径"<<endl;
}
}
//************//
int main()
{
int n; // 节点个数
cout << "请输入节点个数:" << endl;
cin >> n;
int *preorder = new int[n];
int *inorder = new int[n];
int *map=new int[n];//用来记录各个节点在中序序列里的位置
cout << "请输入二叉树的前序序列:" << endl;
for (int i = 0; i < n; i++)
{
cin >> preorder[i];
}
cout << "请输入二叉树的中序序列:" << endl;
for (int i = 0; i < n; i++)
{
cin >> inorder[i];
map[inorder[i]]=i;
}
TreeNode *root = buildTree(map,preorder, inorder, 0, n - 1, 0, n - 1);
cout << "前序输出这棵二叉树:" << endl;
printPreorder(root, 0);
JudgeSumPrint(root);
delete[]map;
delete[] preorder;
delete[] inorder;//new完一定要delete!
return 0;
}
结果截图
第一棵树(存在根到叶子的路径)
前序(VLR):1 2 3 4 5 6
中序(LVR): 3 2 5 4 1 6
第二棵树(不存在这种路径)
前序(VLR):1 2 3 4 5 6
中序(LVR): 3 2 4 1 6 5
第三棵树(存在两条这样的路径)
心得体会
递归是一个树结构,每个分支都探究到最远,发现无法继续的时候才往回走,每个节点只会访问一次,因此,终止条件的判定非常重要!
同理,树的许多操作也适合用递归来写,虽然时间复杂度上没有多少优化,空间复杂度也不友好,但是其代码却很简洁好看,也许,这就是递归算法的优美之处吧~~