您的位置:首页 > 其它

二叉树非递归遍历和层序遍历(各种版本)

2014-08-31 17:12 671 查看
先说下递归遍历吧,特别简单,交换三者的顺序,就能得到三种递归方式。

以先序遍历为例:

void preOrder1(BinTree *root)     //递归前序遍历
{
if(root!=NULL)
{
cout<<root->data<<" ";
preOrder1(root->lchild);
preOrder1(root->rchild);
}
}


一:先序遍历非递归方式-----其实也可以看做深度优先算法

1、用栈实现

要设置一个栈,来模拟向上回溯的方式。

(1)如果左节点不空,就一直进行“访问该结点,入栈,p指向左子树”

(2)当p指向的结点为空的时候,就开始出栈了,出栈的是p的父节点pp,那么将p = pp->right,使得p指向其兄弟结点,再返回(1)进行递归访问。

(3)每次出栈的结点的右结点肯定没有被访问过,因此让p指向其右节点就行了。

void preOrder2(BinTree *root)     //非递归前序遍历
{
stack<BinTree*> s;
BinTree *p=root;
while(p!=NULL||!s.empty())
{
while(p!=NULL)
{
cout<<p->data<<" ";
s.push(p);
p=p->lchild;
}
if(!s.empty())
{
p=s.top();
s.pop();
p=p->rchild;
}
}
}


2、用栈实现

改进一:在方法一中,我们让根结点入栈,是为了方便找到根结点的右节点。这里我们可以直接让右节点入栈。

改进二:为了省去上面的不规则代码,先while循环,再if判断。我们可以直接先让右结点入栈,再让左结点入栈,这样的话,下次循环开始,就直接出栈就行了。相当于没入栈的时候,让p = p->left。

代码如下:

void preOrder(Node *p) //非递归
{
if(!p) return;
stack<Node*> s;
Node *t;
s.push(p);

while(!s.empty()){
t=s.top();
cout << t.data << " ";
s.pop();

if(t->right) s.push(t->right);
if(t->left) s.push(t->left);
}
}


3、不用栈,但需要额外添加父节点指针和访问标记

先判断左孩子的访问标记,再判断右孩子的访问标记。如果左右孩子都被访问过,就让root指向root->parent。

// 先序遍历伪代码:非递归版本,不用栈,增加指向父节点的指针
void preOrder3(TNode* root)
{
while ( root != NULL ){ // 回溯到根节点时为NULL,退出
if( !root->bVisited ){   // 判定是否已被访问
Visit(root);
root->bVisited = true;
}
if ( root->left != NULL && !root->left->bVisited ){      // 访问左子树
root = root->left;
}
else if( root->right != NULL && !root->right->bVisited ){ // 访问右子树
root = root->right;
}
else{   // 回溯
root = root->parent;
}
}
}


这个方法不太好,不能直接找到要被访问的右结点,需要一层一层向上走。也需要了额外的空间来存储父节点指针和访问标记。但有点是没有用到栈。

二:中序遍历非递归方式

1、用栈实现

参照先序遍历的方案一,我们直接将访问的方式,放到出栈的时候,因为这个时候出栈的是父节点。利用这个代码最简洁。

void InOrderTraverse1(BiTree T)   // 中序遍历的非递归
{
if(!T)
return ;
BiTree curr = T;    // 指向当前要检查的节点
stack<BiTree> s;
while(curr != NULL || !s.empty())
{
while(curr != NULL)
{
s.push(curr);
curr = curr->lchild;
}//while
if(!s.empty())
{
curr = s.top();
s.pop();
cout<<curr->data<<"  ";
curr = curr->rchild;
}
}
}


2、用栈实现

如果参照先序遍历的方案二的话,就不能实现了,因为那里的栈中只保存了左右子树,没有跟结点信息。导致了没有办法实现中序遍历。

因此三个节点都要入栈,而且入栈的先后顺序必须为:右节点,根节点,左节点。但是,当入栈以后,根节点与其左右子树的节点就分不清楚了。因此必须引入一个标志位,表示 是否已经将该节点的左右子树入栈了。每次入栈时,根节点标志位为true,左右子树标志位为false。

下面用c++来实现,就用到pair类型。

void inOrder(Node *p)
{
if(!p)
return;
stack< pair<Node*,int> > s;
Node *t;
int unUsed;

s.push(make_pair(p,1));
while(!s.empty()){
t=s.top().first;
unUsed = s.top().second;
s.pop();//退出栈顶结点

if(unUsed){//如果其左右子树都没有放入栈中
if(t->right)            //按照右子树,根结点,左子树的顺序放入
s.push( make_pair(t->right,1) );
s.push( make_pair(t,0) );   //只有放入根结点的时候,要设置标记为0
if(t->left)
s.push( make_pair(t->left,1));
}
else printf("%d\n",t->data);
}
}


3、不用栈

这个方法比较绕,参考下就行

中序遍历的第三个非递归版本:采用指向父节点的指针回溯。这个与先序遍历是非常类似的,不同之处在于,先序遍历只要一遇到节点,那么没有被访问那么立即访问,访问完毕后尝试向左走,如果左孩子补课访问,则尝试右边走,如果左右皆不可访问,则回溯;中序遍历是先尝试向左走,一直到左边不通后访问当前节点,然后尝试向右走,右边不通,则回溯。(这里不通的意思是:节点不为空,且没有被访问过)

// 中序遍历伪代码:非递归版本,不用栈,增加指向父节点的指针
void InOrder3(TNode* root)
{
while ( root != NULL ) // 回溯到根节点时为NULL,退出
{
while ( root->left != NULL && !root->left->bVisited )
{                  // 沿左子树向下搜索当前子树尚未访问的最左节点
root = root->left;
}
if ( !root->bVisited )
{                  // 访问尚未访问的最左节点
Visit(root);
root->bVisited=true;
}
if ( root->right != NULL && !root->right->bVisited )
{                  // 遍历当前节点的右子树
root = root->right;
}
else
{                 // 回溯至父节点
root = root->parent;
}
}
}


三、后序遍历非递归方式

1、用栈

(1)

void PostOrder_Nonrecursive1(BiTree T)  // 后序遍历的非递归
{
stack<BiTree> S;
BiTree curr = T ;           // 指向当前要检查的节点
BiTree previsited = NULL;    // 指向前一个被访问的节点
while(curr != NULL || !S.empty())  // 栈空时结束
{
while(curr != NULL)            // 一直向左走直到为空
{
S.push(curr);
curr = curr->lchild;
}
curr = S.top();
// 当前节点的右孩子如果为空或者已经被访问,则访问当前节点
if(curr->rchild == NULL || curr->rchild == previsited)
{
cout<<curr->data<<"  ";
previsited = curr;
S.pop();
curr = NULL;
}
else
curr = curr->rchild;      // 否则访问右孩子
}
}


(2)

void PostOrder_Nonrecursive(BiTree T)  // 后序遍历的非递归     双栈法
{
stack<BiTree> s1 , s2;
BiTree curr ;           // 指向当前要检查的节点
s1.push(T);
while(!s1.empty())  // 栈空时结束
{
curr = s1.top();
s1.pop();
s2.push(curr);
if(curr->lchild)
s1.push(curr->lchild);
if(curr->rchild)
s1.push(curr->rchild);
}
while(!s2.empty())
{
printf("%c ", s2.top()->data);
s2.pop();
}
}


2、用栈

这个参考了中序遍历的第二种方法,入栈的顺序是,根结点、右子树、左子树。

void postOrder(Node *p)
{
if(!p) return;
stack<pair<Node*,int> > s;
Node *t;

int unUsed;
s.push(make_pair(p,1));
while(!s.empty()){
t=s.top().first;
unUsed=s.top().second;
s.pop();

if(unUsed){
s.push(make_pair(t,0);

if(t->right)
s.push(make_pair(t->right,1));
if(t->left)
s.push(make_pair(t->left,1));
}
else printf("%d\n",t->data);
}
}


四、层序遍历

利用队列,很简单

void levelOrderTraverse(const BiTree& T)
{
queue<BiTree> q;
BiTree p = NULL;

if(T)//若根结点非空,则入队列
{
q.push(T);
}
while(!q.empty())//队列非空
{
p = q.front();
q.pop();
cout<<p->data<<" ";
if(p->lchild)//左孩子不空,入队列
{
q.push(p->lchild);
}
if(p->rchild)//右孩子不空,入队列
{
q.push(p->rchild);
}
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐