二叉树的遍历算法(先序中序后序遍历的递归算法与非递归算法、层级遍历的递归与非递归算法)

一、先序遍历

先序遍历(preOrder):若二叉树非空,则先访问根结点,再访问左子树,最后访问右子树;

1、递归算法:每次都走左分支,直到左子树为空,然后开始从最深处返回,然后开始恢复递归现场,访问右子树。

void preOrder(BinTree *root)     //先序遍历递归实现
{
    if(root!=NULL)
    {
        visit(root);
        preOrder(root->lchild);
        preOrder(root->rchild);
    }
}

2、非递归算法:考察递归算法,因此过程很简单,一直向左走,遇到一个结点便立即访问;由于一直走到最左边后,需要逐步返回到父节点并访问右结点,因此必须有一个措施对结点序列回溯,考虑到结点出现次序与恢复次序恰好是反序的,是一个先进后出结构,因此一般我们采用栈来记忆。考察下面的算法,同一层中不可能同时有两个结点压栈,因此栈的大小空间为O(h),h为二叉树高度,由于每个结点都入栈出栈一次,因此时间复杂度为O(n)。

void preOrder2(BinTree *root)     //非递归前序遍历 
{
    stack s;  //定义栈
    BinTree *p=root;    //p指向根节点
    while(p!=NULL||!s.empty())  //直到p为NULL并且栈为空,遍历结束
    {
        if(p!=NULL) //向左走到尽头
        {
            visit(p);
            s.push(p); //根指针进栈,遍历左子树
            p=p->lchild;
        }
		else   //向左走到尽头之后开始出栈
        {
            p=s.top();
            s.pop();  //弹出
            p=p->rchild; //跳转到if语句
        }
    }
}


二、中序遍历

中序遍历(inOrder):若二叉树非空,则先访问左子树,再访问根结点,最后访问右子树;

1、递归算法:类似前序遍历,只是这里对根结点的访问是出现在左子树之后的。

void inOrder(BinTree *root)      //中序遍历递归实现
{
    if(root!=NULL)
    {
        inOrder(root->lchild);
        visit(root);
        inOrder(root->rchild);
    }
} 

2、非递归算法:对比前序遍历,中序遍历仅仅需要调换一下结点访问的次序。先序是先访问再入栈,而中序则是先入栈,弹栈后再访问。其时间复杂度与空间复杂度也同先序遍历一致。

void inOrder2(BinTree *root)      //非递归中序遍历
{
    stack s;
    BinTree *p=root;
    while(p!=NULL||!s.empty())  //直到p为NULL并且栈为空,遍历结束
    {
        if(p!=NULL)    //向左走到尽头
        {
            s.push(p);   
            p=p->lchild;  //一直压栈
        }
		else  
        {
            p=s.top();
            s.pop();  
	        visit(p);  //弹栈之后访问
            p=p->rchild; //跳转到if语句
        }
    }    
} 

三、后续遍历

后序遍历(postOrder):若二叉树非空,则先访问左子树,再访问右子树,最后访问根结点;

1、递归算法:基本思想是先左再右最后才中间节点;访问左子树后,需要跳转到右子树,右子树访问完毕了再回溯至根节点并访问之。

void postOrder(BinTree *root)    //后序遍历递归实现
{
    if(root!=NULL)
    {
        postOrder(root->lchild);
        postOrder(root->rchild);
        visit(root);
    }    
} 

2、非递归算法:后序遍历的非递归算法是三种遍历方式中最难的一种,因为在后续遍历中,要保证左孩子和右孩子都被已被访问并且左孩子在右孩子之前访问才能访问根结点,这种跳跃就为流程控制带来了难题。我们可以采用以下思路:核心思想就是保证根结点要在左孩子和右孩子访问之后才能访问,对于任意结点p,先将其入栈,如果p不存在左孩子和右孩子,则可以直接访问它;或者p存在左孩子或者右孩子,但是左孩子和右孩子都被访问过了,则同样可以直接访问该结点。若非上述两种情况,则将p的右孩子和左孩子依次入栈,这就保证了每次取栈顶元素的时候,左孩子在右孩子前面被访问,左孩子和右孩子都在跟结点前面被访问。(算法思想引自博客园海子的博客)

void postOrder2(BinTree *root)     //非递归后序遍历
{
    stack s;
    BinTree *cur;                      //当前结点 
    BinTree *pre=NULL;                 //前一次访问的结点 
    s.push(root);
    while(!s.empty())
    {
        cur=s.top();
        if((cur->lchild==NULL&&cur->rchild==NULL)||
           (pre!=NULL&&(pre==cur->lchild||pre==cur->rchild)))
        {
            visit(cur);  //如果当前结点没有孩子结点或者孩子节点都已被访问过 
            s.pop();
            pre=cur; //pre一直指向上一次访问的结点
        }
        else
        {
            if(cur->rchild!=NULL)
                s.push(cur->rchild); //右孩子入栈
            if(cur->lchild!=NULL)    
                s.push(cur->lchild); //左孩子入栈
        }
    }    
}

四、层次遍历

层次遍历一般采用非递归算法,其基本思想是按自顶向下,自左向右的顺序来访问每个节点。这里需要使用队列这种数据结构,非递归实现算法如下:

void layerOrder(BinTree *root)
{
	queue q;
	BinTree *p=NULL; //当前访问的结点
	if(root)
	{
		q.push(root); //若根结点非空,则入队列
	}
	while(!q.empty()) //队列非空
	{
		p=q.front(); //队头删除
		q.pop();
		visit(p);
		if(p->lchild) //左孩子不空,入队列
		{
			q.push(p->lchild);
		}
		if(p->rchild) //右孩子不空,入队列
		{
			q.push(r->child);
		}
	}
}



你可能感兴趣的:(数据结构与算法)