一、实验作业相关要求
实验七 树
1、实验目的与要求
1)巩固对树和二叉树基本知识的理解;
2)掌握树和二叉树的基本操作实现。
2、实验内容
1.根据两种遍历顺序确定一颗树的结构。
给出一棵二叉树的中序与后序排列。求出它的先序排列。(约定树结点用不同的大写字母表示,长度≤8)。
输入格式:第一行为二叉树的中序序列,第二行为二叉树的后序序列
输出格式:一行,为二叉树的先序序列
样例输入:
BADC
BDCA
样例输出:
ABCD
2.LCA(Lowest Common Ancestor )问题。
给定一棵树,同时给出树中的两个结点(n1和n2),求它们的最低公共祖先。
3、实验结果
1)请将调试通过的主要源代码、输出结果粘贴在下面
2)简述算法步骤,格式如下:
S1:
S2:
S3:
3)请分析算法的时间复杂度。
二、源代码
项目组成
树结点类
//class TreeNode
typedef string T;
class TreeNode{
public:
T val;//value
TreeNode* left;//leftnode ptr
TreeNode* right;//rightnode ptr
TreeNode():left(NULL),right(NULL){}
TreeNode(T val):val(val),left(NULL),right(NULL){}
TreeNode(T val,TreeNode* left,TreeNode* right):val(val),left(left),right(right){}
};
二叉树类
#include<iostream>
#include<string>
#include<vector>
#include<queue>
using namespace std;
//class BinaryTree
typedef string T;
class BinaryTree{
public:
// initialization
BinaryTree(){}
BinaryTree(T ref):refval(ref){}
BinaryTree(TreeNode* root):root(root){}
BinaryTree(T ref,TreeNode* root):refval(ref),root(root){}
BinaryTree(BinaryTree* ano):refval(ano->refval),root(ano->root){}
// set the signal string for skipping a node
void setSkipwords(T& words){skipwords=words;}
//input a tree by Breadth order
void inputTree();
// get over signal
T getRefval(){return refval;}
// get rootnode
TreeNode*& getRoot(){return root;}
private:
T refval="#";// a signal string to end reading
T skipwords="skip";// a signal string to skip reading a node
TreeNode* root;// the ptr of rootnode
};
二叉树的输入函数
void BinaryTree::inputTree(){
vector<TreeNode*> bfs;
bool fg=true;
root=new TreeNode("#");
bfs.push_back(root);
int bptr=0;
while(fg){
T temp;
getline(cin,temp);
vector<TreeNode*> ano;
T part;
for(int i=0;i<temp.size();i++){
if(bptr==bfs.size()){
if(ano.empty()){
fg=false;
}else{
bfs=ano;
bptr=0;
}
break;
}
if(temp[i]==' '||i==temp.size()-1){
if(temp[i]!=' '&&i==temp.size()-1){
part+=temp[i];
}
if(!part.empty()){
if(part==refval){
fg=false;
break;
}else if(part==skipwords&&bptr!=bfs.size()){
bptr++;
if(i==temp.size()-1){
if(!ano.empty()){
bfs=ano;
bptr=0;
}else{
fg=false;
}
break;
}
}else{
bfs[bptr]->val=part;
bfs[bptr]->left=new TreeNode("#");
bfs[bptr]->right=new TreeNode("#");
ano.push_back(bfs[bptr]->left);
ano.push_back(bfs[bptr]->right);
bptr++;
if(i==temp.size()-1){
if(!ano.empty()){
bfs=ano;
bptr=0;
}else{
fg=false;
}
break;
}
}
}
part="";
}else{
part+=temp[i];
}
}
}
}
方法类
prettyPrintTree from LeetCode’s funclib
// algorithm to show a pretty binary tree vividly from LeetCode
void printTree(TreeNode* node , string prefix = "", bool isLeft = true){
if (node==nullptr||node->val=="#") {
cout << "Empty tree";
return;
}
if(node->right&&node->right->val!="#") {
printTree(node->right, prefix + (isLeft ? "│ " : " "), false);
}
cout << prefix + (isLeft ? "└── " : "┌── ") + node->val + "\n";
if (node->left&&node->left->val!="#") {
printTree(node->left, prefix + (isLeft ? " " : "│ "), true);
}
}
由中后序获得前序
// Problem 1 algorithm: get preorder from inoder and postorder of a tree
vector<T> getPreorder(vector<T>& inorder, vector<T>& postorder) {
vector<T> preorder;
if(postorder.size()==0){
return preorder;
}
int ibegin=0,iend=inorder.size()-1,pbegin=0,pend=postorder.size()-1;
stack<pair<int,int> > iposstore,pposstore;
while(true){
preorder.push_back(postorder[pend]);
if(pbegin!=pend){
int pos=ibegin;
for(;pos<=iend;pos++){
if(inorder[pos]==postorder[pend]){
break;
}
}
if(pos==ibegin){
ibegin++;
pend--;
}else if(pos==iend){
iend--;
pend--;
}else{
iposstore.push(make_pair(pos+1,iend));
pposstore.push(make_pair(pbegin+pos-ibegin,pend-1));
iend=pos-1;
pend=pbegin+pos-ibegin-1;
}
}else{
if(iposstore.empty()){
break;
}else{
ibegin=iposstore.top().first;
iend=iposstore.top().second;
iposstore.pop();
pbegin=pposstore.top().first;
pend=pposstore.top().second;
pposstore.pop();
}
}
}
return preorder;
}
两结点的最低公共祖先
//Problem 2 algorithm: get lowestCommonAncestor of two given nodes
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
if(!root||root->val=="#"){
return NULL;
}
if(root->val==p->val||root->val==q->val){
return root;
}
TreeNode* left=lowestCommonAncestor(root->left,p,q);
TreeNode* right=lowestCommonAncestor(root->right,p,q);
if(left&&left->val!="#"&&right&&right->val!="#"){
return root;
}else if(bool(left&&left->val!="#")^bool(right&&right->val!="#")){
return bool(left&&left->val!="#")?left:right;
}else{
return NULL;
}
主函数
#include <iostream>
#include"TreeNode.h"
#include"BinaryTree.h"
#include"Solution.h"
using namespace std;
typedef string T;
int main(int argc, char** argv) {
//Problem 1
int size;
cout<<"1、请输入树的结点个数:"<<endl;
cin>>size;
vector<T> inorder(size);
vector<T> postorder(size);
cout<<"\n请输入树的中序序列:"<<endl;
for(int i=0;i<size;i++){
cin>>inorder[i];
}
cout<<"\n请输入树的后序序列:"<<endl;
for(int j=0;j<size;j++){
cin>>postorder[j];
}
vector<T> preorder=Solution().getPreorder(inorder,postorder);
cout<<"\n树的前序序列如下:"<<endl;
for(int k=0;k<preorder.size();k++){
cout<<preorder[k]<<" ";
}
cout<<endl;
system("pause");
//Problem 2
cout<<"\n2、请按层序输入一棵树(终止符为 # ,需要跳过结点则输入 skip , 同一层不同结点用空格(不可Tab缩进代替空格)隔开):"<<endl;
BinaryTree bt;
bt.inputTree();
cout<<"\n所得树的图形如下所示:"<<endl;
Solution().printTree(bt.getRoot());
T pnode,qnode;
cout<<"\n请输入两个结点的值以得到其最近公共祖先结点的值:"<<endl;
cin>>pnode>>qnode;
cout<<"\n所得最近公共祖先为:"<<Solution().lowestCommonAncestor(bt.getRoot(),new TreeNode(pnode),new TreeNode(qnode))->val<<endl;
return 0;
}
执行效果
三、作业提交的文档内容
1)请将调试通过的主要源代码粘贴在下面
如上
2)简述算法步骤:
第一题
S1:用栈实现先序遍历,以中左右的顺序将序列重排列,对先序而言,每次遍历先实例化中结点,再将可能存在的右结点代表的子树存进栈中,先处理左子树,进入左子树后,重复上述步骤;当遇到叶子结点后,从栈中逐个取出右子树按先序方式进行实例化。
S2:中序是左中右,后序是左右中,所以每次实例化当前结点必然从后序的尾部取得,此时,遍历中序,寻找此结点在中序的位置以确定左子树部分的长度,从而能将后序继续分成左右子树两半,把右子树放进栈中,然后继续在下一次遍历左部分;直到不存在左子树时,从栈逐个取出右子树进行遍历,需要注意的是,类似一个递归的过程,每次遍历子树时,依照其全新的中、后序进行处理、拆分。
S3:为了迭代实现中序、后序转前序的算法,对于每段中、后序序列各需要两个指针,共计四个指针,分别记录每次遍历时,中序、后序序列里指针约束的左、右子树的下标范围,每次遍历,取后序的尾结点实例化先序当前的新结点,再判断左右指针是否重合,若重合,说明左子树已遍历至尽头,该从栈中取出新的左右指针覆盖原来的,以实现在中、后序列上跳跃读数;若不重合,寻找中序里对应后序尾结点的结点位置,从而将中、后序都拆成左、右子树部分,右子树的左右约束指针存入栈中,下次遍历先处理左子树部分,如此往复循环。
第二题
S1:递归,深度优先搜索过程中,每次遍历结点都分类讨论:
当所给两结点有其一为当前遍历结点时或者当两结点分别处于当前左、右子树部分时,毫无疑问当前遍历的结点就是最低公共祖先;
当两结点同时在一颗子树部分时,假设调用本函数能得到最低公共祖先,递归调用本函数且以该子树作参数,必然会得到最低公共祖先。
S2:深搜判断两节点相对于当前根结点的相对位置,是利用判空来实现的,因为我们在递归中预设:
若能在树中找到两结点之一,则返回该结点;
若能在树中找到两结点,则返回两结点的最低公共祖先;
若都找不到,则返回nullptr。
那么每次遍历,分别将左、右子树作参数带入本函数递归后取得两返回值,根据以上所预设的情况,对此二返回值判空分析,便能得到最低公共祖先。
S3:对两返回值判断:
若两返回值都非空,说明左、右子树各有两结点之一,显然,最低公共祖先恰是当前根结点,返回根结点;
若返回值一空一非空,说明最低公共祖先在更上层的树结点中,且此时必然是处于被递归调用的函数内,故返回该非空值以便于更上一级的递归函数处理;
若返回值全为空,说明最低公共祖先在更上层的树结点中,且此时必然是处于被递归调用的函数内,故返回该nullptr以提供给更上一级递归函数信息:此方向遍历不存在所需查找的两给定结点中任何一个。
3)请分析算法的时间复杂度。
第一题
每得到一个按先序的结点,可能得在中序序列内查找一回,最坏情况下每次查找整个中序序列,次数为(n + n-1 + n-2 + … + 1),即时间复杂度为 O(n^2) 级别的算法。(其实有 O(n) 算法,没想写了)
第二题
显然,利用深度优先搜索、递归,最多将树中每个节点都遍历一回,便可得到最低公共祖先,故时间复杂度为 O(n)