先说下递归遍历吧,特别简单,交换三者的顺序,就能得到三种递归方式。
以先序遍历为例:
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);
}
}
}