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); }
运行结果为: