@二叉树的常用算法
树的遍历(C语言实现)
树的遍历分为前序遍历(NLR),中序遍历(LNR),后续遍历(LRN)以及层次遍历,在树的定义中用的是递归方式定义,所以考虑用递归的方式实现遍历。另外,可以用栈来实现非递归的算法,所以同时也用栈的方式实现遍历;
递归遍历
1、前序遍历
按照前序遍历的定义,先访问根节点,在访问左子树,最后访问右子树,递归需要一个结束条件,当结点为空时就结束递归
二叉树定义如下
typedef struct BiTNode{
//定义二叉树结点
ElemType data;
struct BiTNode *lchild,*rchild;
}BiTNode,*Bitree;
代码的实现
void Pre_Order(Bitree T){
//递归对树进行前序遍历
if(T!=NULL){
visit();
Pre_Order(p->lchild);
Pre_Order(p->rchild);
}
}
visit()函数自己可以定义为对结点的操作
2、中序遍历
思想和前序遍历一致,调换一下访问顺序即可
void In_Order(Bitree T){
//递归对树进行中序遍历
if(T!=NULL){
In_Order(T->lchild);
visit();
In_Order(T->rchild);
}
}
3、后序遍历
void Post_Order(Bitree T){
//递归对树进行后序遍历
if(T!=NULL){
Post_Order(T->lchild);
Post_Order(T->rchild);
visit();
}
}
非递归遍历
对于非递归遍历我们先参考一下递归遍历,在递归遍历中,除去visit()函数,三种遍历完全一致,所以访问路径也应该一致,大家可以去调试看一下工作栈的函数调用,先对路径进行分析。
可以看到三种遍历的访问路径完全一致,只是访问结点时机不同,前序遍历在第一次到结点时就进行访问,中序遍历为第一次回到结点进行访问,后序遍历为第二次回到结点进行访问。叶子结点同样对其左右孩子进行访问。
对路径进行思考
1.先访问左子树到底,直到为空
2.回到上一层,对右孩子进行访问,若右孩子为空,回到上一层(这个上一层为右孩子上一层的上一层);若右孩子不为空,采用1的方式访问。
思路完全和栈的特点吻合,1中结点不断入栈,2中回到上一层即是结点出栈的描述。
路径的代码如下
while(p!=NULL||!StackEmpty(S)){
if(p!=NULL){
Push(S,p);
p=p->lchild;
}
else{
Pop(S,p);
p=p->rchild;
}
}
结合路径和遍历时的visit()时机,我们可以写出三种遍历的非递归算法
1、先序遍历
void Pre_Order2(Bitree T){
//非递归进行前序遍历
stack s;
char ch;
s.top=-1;
Bitree* p=T;
while(p!=NULL||s.top!=-1){
if(p){
s.data[++s.top]=p;
visit();
p=p->lchild;
}
else{
p=s.data[s.top--];
p=p->rchild;
}
}
}
由于写时没有提前写栈的基本操作,直接用代码实现的基本操作,对应路径理解即可,采用的都是顺序栈。
2、中序遍历
void In_Order2(Bitree T){
//非递归进行中序遍历
stack s;
char ch;
s.top=-1;
BiTNode* p=T;
while(p!=NULL||s.top!=-1){
if(p){
s.data[++s.top]=p;
p=p->lchild;
}
else{
p=s.data[s.top--];
visit();
p=p->rchild;
}
}
}
3、后序遍历
后序遍历与前序遍历和中序遍历不完全相同,它是第二次回到结点时进行访问,我们怎么体现这个第二次?
a、加入辅助指针r,r指向刚被访问的结点,当第一次回到上一结点,刚被访问的结点为左孩子,第二次访问时刚被访问结点为右孩子。
b、用一个辅助数组记录是否被访问,数组可在函数定义,也可以直接加到树的定义中。
注意:在第一次访问时只访问,不退栈。
代码如下:(采用方法a)
void Post_Order2(Bitree T){
//非递归后序遍历
stack s;
s.top=-1;
BiTNode *p=T,*r=NULL;
while(p!=NULL||s.top!=-1){
if(p!=NULL){
s.data[++s.top]=p;
p=p->lchild;
}
else{
p=s.data[s.top];
if(p->rchild!=NULL&&r!=p->rchild)
//p的右孩子不为空或者右孩子未被访问过
p=p->rchild;
else{
p=s.data[s.top--];
visit();
r=p;
p=NULL;
}
}
}
}
层次遍历
层次遍历不同与前面三种遍历方式,它是逐层从左到右遍历,一直以一种同层次左右孩子依次排列的方式,考虑用队列进行辅助遍历。
将根结点入栈,然后出栈,将根节点的左右孩子入栈,然后依次进行类似出栈和入栈操作,当栈为空时,遍历完所有的结点。
void Level_Order(Bitree T){
//对树进行层次遍历,用队列进行辅助
BiTNode *p;
queue Q;
Q.front=Q.rear=-1;
if(T!=NULL)
Q.data[++Q.rear]=T;
while(Q.front!=Q.rear){
p=Q.data[++Q.front];
visit();
if(p->lchild)
Q.data[++Q.rear]=p->lchild;
if(p->rchild)
Q.data[++Q.rear]=p->rchild;
}
}