二叉樹非遞歸遍歷和層序遍歷(各種版本)

先說下遞歸遍歷吧,特別簡單,交換三者的順序,就能得到三種遞歸方式。

以先序遍歷爲例:

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);  
        }  
    }   
}  





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章