1、二叉树遍历分析
二叉树的遍历有四种,先序、中序、后序和层序,其中,中序遍历配合其中任何一种遍历的结果都可以重建二叉树,先序和后序配合却无法重建二叉树。本人觉得编程之美书上代码清单3-12给出的代码不一定是最好的,代码冗长,复杂。在此给出自己的尝试和结果。
2、先序+中序重建二叉树
先给出一个子函数,检验字符search_char是否在字符串*s的区间[pbeg, pend],也是是否能构建二叉树的判断依据。代码如下:
bool charisexist(char search_char,char *s,int pbeg,int pend,int &loc)
{
string temp=s;
loc=temp.find(search_char);
if (loc>pend||loc<pbeg)
return false;
else
return true;
}
2.1、方案一
以先序遍历的结果为顺序一个个字符轮流搜索,重建树,beg,end是所找节点子树在中序遍历的范围。若beg>end,说明为空,返回。
DLR为先序遍历结果,LDR为中序遍历结果,offset为对DLR检测的下标。[beg, end]为LDR的有效区间,root为根节点,flag判断是否建树成功。flag的初始值为ture,执行完下面的程序后,若flag的值仍为ture,则说明建树成功,否则建树失败,说明所给的结果格式不合法。
int num=0;
void searchbuild(char *DLR,int &offset,char *LDR,int beg,int end,node *&root,bool &flag)//方案一,以DLR一个个字符轮流进行
{
int loc=0;
num++;//统计递归进入次数
if (beg>end)//递归退出条件
{
root=NULL;//-----------------------此处是关键,注意啊
return;
}
if (charisexist(*(DLR+offset),LDR,beg,end,loc))//根节点的location已存入loc中了 每次比会更新loc的值
{
node *temp=new node;
temp->value=*(DLR+offset++);
root=temp;
}
else
{
flag=faulse;
return;
}
searchbuild(DLR,offset,LDR,beg,loc-1,(root->pleft));//递归过程及其回溯过程
searchbuild(DLR,offset,LDR,loc+1,end,(root->pright));
}
运行结果为:
2.2、方案一的优化
当beg==end时,beg所指的节点已经是叶节点,此处将其左右指针赋NULL后return,否则程序将进行下一层遍历后return,增加了遍历次数,增加了时间。
void searchbuild(char *DLR,int &offset,char *LDR,int beg,int end,node *&rootd,bool &flag)//方案一,以DLR字符的轮流进行
{
int loc=0;
num++;//统计递归进入次数
if (num==0)//格式初步检查,其实可以不要的啦
{
if (sizeof(DLR)!=sizeof(LDR)||!DLR||!LDR)
{
flag=faulse;
return;
}
}
if (beg>end)//递归退出条件
{
root=NULL;//-----------------------此处是关键,注意啊
return;
}
if (charisexist(*(DLR+offset),LDR,beg,end,loc))//根节点的location已存入loc中了 每次比会更新loc的值
{
node *temp=new node;
temp->value=*(DLR+offset++);
root=temp;
if (beg==end)//递归退出条件
{
root->pleft=NULL;//-----------------------此处是关键,注意啊
root->pright=NULL;
return;
}
}
else
{
flag=faulse;
return;
}
searchbuild(DLR,offset,LDR,beg,loc-1,(root->pleft));//递归过程及其回溯过程,loc在此会有反应
searchbuild(DLR,offset,LDR,loc+1,end,(root->pright));
}
优化后,递归函数重入次数变为15次,重入次数明显减少,效率提高,运行结果为:
2.3、方案二
采用分治法,每次递归将子树分长左右两部分,直到节点所在子树长度为0时,返回。
DLR-s为先序遍历结果,LDR-s为中序遍历结果,lengh。[beg, end]为LDR的有效区间,root为根节点,flag判断是否建树成功。flag的初始值为ture,执行完下面的程序后,若flag的值仍为ture,则说明建树成功,否则建树失败,说明所给的结果格式不合法。
void searchbuild2(char *DLR_s,char *LDR_s,int lenght,node *&root,bool &flag)
{
int loc,leftlen,rightlen;
num++;//统计递归进入次数
if (lenght==0)
{
root=NULL;
return;
}
if (charisexist(*(DLR_s),LDR_s,0,lenght-1,loc))
{
node *temp=new node;
temp->value=*DLR_s;
root=temp;
}
else
{
flag=faulse;
return;
}
leftlen=loc;
rightlen=lenght-loc-1;
searchbuild2(DLR_s+1,LDR_s,leftlen,root->pleft);
searchbuild2(DLR_s+1+leftlen,LDR_s+leftlen+1,rightlen,root->pright);//注意要越过根节点//LDR_s+leftlen+1
}
运行结果:
2.4、方案二的优化
当节点所在的子树长度length==1时,说明该节点为叶子节点,此处将其左右指针赋NULL后return,否则程序将进行下一层遍历后return,增加了遍历次数,增加了时间。优化后为:
void searchbuild2(char *DLR_s,char *LDR_s,int lenght,node *&root,bool &flag)
{
int loc,leftlen,rightlen;
num++;//统计递归进入次数
if (num==0)//格式初步检查,其实可以不要的啦
{
if (sizeof(DLR_s)!=sizeof(LDR_s)||!DLR_s||!LDR_s)
{
flag=faulse;
}
}
if (lenght==0)
{
root=NULL;
return;
}
if (charisexist(*(DLR_s),LDR_s,0,lenght-1,loc))
{
node *temp=new node;
temp->value=*DLR_s;
root=temp;
if (lenght==1)
{
root->pleft=NULL;
root->pright=NULL;
return;
}
}
else
{
flag=faulse;
return;
}
leftlen=loc;
rightlen=lenght-loc-1;
searchbuild2(DLR_s+1,LDR_s,leftlen,root->pleft);
searchbuild2(DLR_s+1+leftlen,LDR_s+leftlen+1,rightlen,root->pright);//注意要越过根节点//LDR_s+leftlen+1
}
运行结果:
2.5、方案三
方案三是对方案二的改进,将每一节点的左右指针都先赋NULL,若节点所在的子树长度大于1,则在下次递归中更改其指针的值。直到节点所在的子树为1则return,说明此节点为叶子节点。若某一节点的左子树或右子树为空,则递归的if条件将会判断是否进入递归。这样改进后,递归重入次数再次减少,效率提高。
void searchbuild4(char *DLR_s,char *LDR_s,int lenght,node *&root,bool &flag)//pleft,pright,默认为空lenght==1退出
{
int loc,leftlen,rightlen;
num++;
if (num==0)//格式初步检查,其实可以不要的啦
{
if (sizeof(DLR_s)!=sizeof(LDR_s)||!DLR_s||!LDR_s)
{
flag=false;
return ;
}
}
if (charisexist(*(DLR_s),LDR_s,0,lenght-1,loc))
{
node *temp=new node;
temp->value=*DLR_s;
temp->pleft=NULL;
temp->pright=NULL;
root=temp;
if (lenght==1)//-----------lengh==1直接返回,因为,默认的每一节点的左右指针都赋NULL,若其为非空,则下次递归时改为子树的指针
{
return;
}
}
else
{
root=NULL;
flag=false;
return ;
}
leftlen=loc;
rightlen=lenght-loc-1;
if (leftlen>0)
{
searchbuild4(DLR_s+1,LDR_s,leftlen,root->pleft,flag);//此处不可以return;,否则,下面的将不会递归执行
}
if (rightlen>0)
{
searchbuild4(DLR_s+1+leftlen,LDR_s+leftlen+1,rightlen,root->pright,flag);//注意要越过根节点//LDR_s+leftlen+1
}
}
可以发现,方案三的递归重入次数最少,效率最高。程序运行结果:
3.后序+中序重建二叉树
思路:先将后序遍历的LRD的结果反转,反转后的首个字符就是根节点,然后依次是根节点的右子树,左子树。
反转的代码为:
void exchangechar(char char1[])
{
char temp;
int n=strlen(char1);
for (int i=0,j=n-1;i<j;i++,j--)
{
temp=char1[i];
char1[i]=char1[j];
char1[j]=temp;
}
}
可采用类似于上述的算法进行重建,现将方案一稍加改进后得代码如下:
void searchbuild3(char *LRD,int &offset,char *LDR,int beg,int end,node *&rootf,bool &flag)//递归边界条件,递归回溯过程
{
int loc;
if (num==0)
{
if (sizeof(LDR)!=sizeof(LRD)||!LDR||!LRD)
{
flag=faulse;
return;
}
}
num++;
if (beg>end)
{
root=NULL;
return;
}
if(charisexist(*(LRD+offset),LDR,beg,end,loc))//offset无需限定,若offset越界,chariexist返回fause;
{
node *temp=new node;
temp->value=*(LRD+offset++);
root=temp;
if (beg==end)
{
root->pleft=NULL;
root->pright=NULL;
return;
}
}
else
{
flag=faulse;
root=NULL;
return;
}
searchbuild3(LRD,offset,LDR,loc+1,end,root->pright);//注意此处,先建右子树,再建左子树!!!
searchbuild3(LRD,offset,LDR,beg,loc-1,root->pleft);
}
运行结果为:
欢迎各位交流批评指正^_^····