二叉树的非递归遍历详解

@二叉树的常用算法

树的遍历(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;
    }
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值