现在的位置: 首页 > 综合 > 正文

二叉树递归与非递归遍历的方法

2013年12月01日 ⁄ 综合 ⁄ 共 2032字 ⁄ 字号 评论关闭

二叉树的遍历是对二叉树的一个基本的操作,最近看书也看到了相关的内容,就来总结一下遍历二叉树的方法。

一般的,我们有三种常用的遍历方法,分别是前序、中序和后序,这里的前中后可以理解为将访问根节点的操作放在访问左右节点操作的前面、中间、还是后面。当然,还有分层遍历,这个就更好理解啦,从根节点逐层访问。每一种顺序的遍历都有它特有的实际应用,首先应该知道如何实现这几种遍历。分为两种实现方法:递归实现和使用栈实现。

一、递归实现树的遍历

以前序遍历为例,前序遍历的顺序为首先访问根节点A,然后访问根节点的左子树和右子树,左右子树同样按照前序遍历的方法,直到到达叶子节点。具体过程如下图所示:

结合递归的思想,给出前序遍历的代码

void PreTraverseV1(Node *pHead)
{
    if(NULL == pHead)return;
    //访问pHead
    visit(pHead);
    PreTraverseV1(pHead->lhs);
    PreTraverseV1(pHead->rhs);
}

同理,如果我们将访问pHead的部分放在递归左、右子树的中间,则完成的是中序遍历;如果放在递归左右子树的后面,则完成的是后续遍历。

二、利用栈实现遍历

非递归实现,在思路上就没有递归实现那么直观和容易理解了,甚至在三种遍历方法理解的难度上都有区别。

还是以前序遍历为例,它在实现思路上也是最简单,它始终在进行根、左、右的顺序,利用栈的特点(先进先出),我们先将根节点压入栈,然后进入循环,每弹出一个节点时,将它的右、左节点(如果不为空)分别在压入到栈中,这样下一次循环的时候,我们会首先弹出根节点的左节点,同时,将左节点的右左节点(如果不为空)再次压入栈。当栈为空的时候,循环结束。按照这样的逻辑,便可以完成对二叉树的前序遍历。

下面给出实现的代码:

void PreTraverseV2(Node *pHead)
{
    if (NULL == pHead)return;
    STACK.init(MAX_SIZE);
    STACK.push(pHead);
    while(!STACK.empty)
    {
        //访问该节点
        visit(pHead = STACK.pop);
        if(NULL != pHead->rhs)STACK.push(pHead->rhs);
        if(NULL != pHead->lhs)STACK.push(pHead->lhs);
    }
}

对于中序遍历,考虑到中序遍历的特点,首先从根节点一直寻找左节点,直到到达最左节点为止,此时,将最左节点输出,如果它还有右节点,则从它的右节点开始按照刚才的逻辑寻找,直到无右节点,则输出,按照刚才的顺序依次再向上走,如果上面的节点还有右节点,再去寻找右节点的左节点,直到将整个树遍历完。按照这样的逻辑,我们可以将寻找左节点的操作看成不断地将左节点压入栈中,当到没有左节点,此时对栈顶元素访问并弹出。新的栈顶元素也会被访问并弹出,同时检测新的栈顶元素有没有右节点代码如下:

void MidTraverse(Node *pHead)
{
    if (NULL == pHead)return;
    while(pHead || !STACK.empty)
    {
        while(pHead)
        {
            STACK.push(pHead);
            pHead = pHead->lhs;
        }
        //访问此时的节点
        visit(pHead = STACK.pop()) 
        pHead = pHead->rhs;
    }
}

对于后续遍历,思路要复杂一些,先一直寻找左节点,直到左叶子节点,如果它还有右节点,那么从其右节点开始,按照前面的逻辑寻找左节点,直到找到了没有右节点的左节点(即叶子节点),输出它并往它的父节点走,父节点如果还有右节点,仍然继续前面的寻找,如果没有,则输出父节点。知道遍历完整个树。按照这样的逻辑,我们将寻找左节点的操作看成是将左节点不断压入栈中,然后看栈顶元素是否有右节点,如果有,则让右节点入栈,并继续不断压入它的左节点。如果栈顶元组没有右节点,那么我们就访问它并将它弹出,同时将检测新的栈顶元素是否有右节点。直到栈变为空。代码如下:

void PostTraverse(Node *pHead)
{
    if (NULL == pHead)return;
    while(pHead || !STACK.empty)
    {
        while(pHead)
        {
            STACK.push(pHead);
            pHead = pHead->lhs;
        }
        pHead = STACK.top();
        if (NULL != pHead->rhs)
            pHead = pHead->rhs;
        else
        {
            visit(pHead);
            STACK.pop();
            pHead = pHead->rhs;
        }
    }
}

如果说使用递归的方法实现遍历,从宏观上看三种实现方法,还无法深刻体会到它们之间微妙的差别的话。当我们使用栈来实现时,就需要深刻考虑到每一种遍历方法细微的差别了,这些差别与思路要在循环语句的先后顺序上体现出来。LZ在经过一番思考在实现非递归方法后,对三种遍历有了深入的理解。





























抱歉!评论已关闭.