二叉树非递归遍历和层序遍历(各种版本)
2014-08-31 17:12
671 查看
先说下递归遍历吧,特别简单,交换三者的顺序,就能得到三种递归方式。
以先序遍历为例:
一:先序遍历非递归方式-----其实也可以看做深度优先算法
1、用栈实现
要设置一个栈,来模拟向上回溯的方式。
(1)如果左节点不空,就一直进行“访问该结点,入栈,p指向左子树”
(2)当p指向的结点为空的时候,就开始出栈了,出栈的是p的父节点pp,那么将p = pp->right,使得p指向其兄弟结点,再返回(1)进行递归访问。
(3)每次出栈的结点的右结点肯定没有被访问过,因此让p指向其右节点就行了。
2、用栈实现
改进一:在方法一中,我们让根结点入栈,是为了方便找到根结点的右节点。这里我们可以直接让右节点入栈。
改进二:为了省去上面的不规则代码,先while循环,再if判断。我们可以直接先让右结点入栈,再让左结点入栈,这样的话,下次循环开始,就直接出栈就行了。相当于没入栈的时候,让p = p->left。
代码如下:
3、不用栈,但需要额外添加父节点指针和访问标记
先判断左孩子的访问标记,再判断右孩子的访问标记。如果左右孩子都被访问过,就让root指向root->parent。
这个方法不太好,不能直接找到要被访问的右结点,需要一层一层向上走。也需要了额外的空间来存储父节点指针和访问标记。但有点是没有用到栈。
二:中序遍历非递归方式
1、用栈实现
参照先序遍历的方案一,我们直接将访问的方式,放到出栈的时候,因为这个时候出栈的是父节点。利用这个代码最简洁。
2、用栈实现
如果参照先序遍历的方案二的话,就不能实现了,因为那里的栈中只保存了左右子树,没有跟结点信息。导致了没有办法实现中序遍历。
因此三个节点都要入栈,而且入栈的先后顺序必须为:右节点,根节点,左节点。但是,当入栈以后,根节点与其左右子树的节点就分不清楚了。因此必须引入一个标志位,表示 是否已经将该节点的左右子树入栈了。每次入栈时,根节点标志位为true,左右子树标志位为false。
下面用c++来实现,就用到pair类型。
3、不用栈
这个方法比较绕,参考下就行
中序遍历的第三个非递归版本:采用指向父节点的指针回溯。这个与先序遍历是非常类似的,不同之处在于,先序遍历只要一遇到节点,那么没有被访问那么立即访问,访问完毕后尝试向左走,如果左孩子补课访问,则尝试右边走,如果左右皆不可访问,则回溯;中序遍历是先尝试向左走,一直到左边不通后访问当前节点,然后尝试向右走,右边不通,则回溯。(这里不通的意思是:节点不为空,且没有被访问过)
三、后序遍历非递归方式
1、用栈
(1)
(2)
2、用栈
这个参考了中序遍历的第二种方法,入栈的顺序是,根结点、右子树、左子树。
四、层序遍历
利用队列,很简单
以先序遍历为例:
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); } } }
相关文章推荐
- 二叉树的,前/中/后序的遍历( 递归,非递归),层序遍历,以及各种应用功能
- 二叉树的建立,前中后序遍历的递归版本和非递归版本,层序遍历
- 三种遍历二叉树各种算法(非递归)
- 二叉树实现(包括遍历等各种操作,递归与非递归)
- java实现的二叉树(前序、中序、后序)递归和非递归遍历,包含层序遍历
- 二叉树先序、中序、后序、层序遍历(递归、非递归)
- 非递归遍历二叉树(先序、中序、后序、层序)
- 二叉树的遍历:前序,中序,后序,层序--包括递归和非递归实现
- <二叉树 前中后 层序 非递归遍历 c语言实现>
- 二叉树的前序中序后序遍历 - 递归&非递归版本
- C++实现二叉树 前序遍历, 后序遍历, 中序遍历, 层序遍历(不用递归)
- 二叉树的遍历:前序,中序,后序,层序--包括递归和非递归实现
- 二叉树的遍历:前序,中序,后序,层序--包括递归和非递归实现
- 非递归遍历二叉树的四种策略-先序、中序、后序和层序
- 二叉树的遍历:前序,中序,后序,层序--包括递归和非递归实现(转)
- 二叉树的建立及遍历各种递归
- 二叉树的遍历:前序,中序,后序,层序--包括递归和非递归实现
- 二叉树的各种递归和栈的遍历。
- 二叉树的各种操作的(递归非递归)的实现,如(递归非递归)先序后序中序层次遍历 二叉树的高度 叶子节点数,所有节点数
- 二叉树系列(3)层序遍历的非递归实现