二叉樹-遍歷終極版

tielimu 7年前發布 | 30K 次閱讀 二叉樹 B樹 算法

對于二叉樹的遍歷,最熟悉的就是遞歸遍歷了,對二叉樹的非遞歸遍歷大致知道一些,但是不太熟悉,尤其是后續非遞歸遍歷的實現,一直比較懵逼,于是上網查詢了一下,果然大神無處不在,那個后序遍歷的雙棧法,簡直讓人拍案叫絕,下面總結下。

1、先序遍歷

先序遍歷即順序為:根節點->左節點->右節點

遞歸版:

void PreOrderTraverse(BTree T)
{  
    if (T != NULL)  
    {  
        cout << T->val;  
        PreOrderTraverse(T->left);   
        PreOrderTraverse(T->right);  
    }
}

非遞歸版:

根據前序遍歷訪問的順序,優先訪問根結點,然后再分別訪問左孩子和右孩子。即對于任一結點,其可看做是根結點,因此可以直接訪問,訪問完之后,若其左孩子不為空,按相同規則訪問它的左子樹;當訪問完其左子樹后,再訪問它的右子樹。因此其處理過程如下:

對于任一結點P:

1)訪問結點P,并將結點P入棧;

2)判斷結點P的左孩子是否為空,若不為空,則將P的左孩子置為當前的結點P;若為空,則取棧頂結點并進行出棧操作,并將棧頂結點的右孩子置為當前的結點P,循環至1)

3)直到P為NULL并且棧為空,則遍歷結束。

代碼如下:

void PreOrderTraverse(BTree T)
{
    std::stack<BTree> S;
    while(T||!S.empty())
    {
        while(T)
        {
            S.push(T);
            cout<<T->val;
            T=T->left;
        }
        if(!S.empty())
        {
            T=S.top();
            S.pop();
            T=T->right;
        }       
    }
}

上面那個遞歸版是常規版,這個我覺得更好,思路是這樣的,節點的右左節點分別入棧,根絕棧的“先進后出”特性,那么先輸出的必然是左節點。這個方法代碼簡潔,思路獨特。

void PreOrderTraverse(BTree T)     //先序遍歷的非遞歸      
{    
    if(!T)      
        return ;      

    stack<BiTree> s;    
    s.push(T);    

    while(!s.empty())    
    {    
        BiTree temp = s.top();    
        cout<<temp->data<<" ";    
        s.pop();    
        if(temp->rchild)    
            s.push(temp->rchild);    
        if(temp->lchild)    
            s.push(temp->lchild);    
    }    
}

2、中序遍歷

中序遍歷的順序是:左節點、根節點、右節點

遞歸版:

void MidOrderTraaerse(BTree T)  
{
    if(T!=NULL)
    {
        MidOrderTraverse(T->left);
        cout<<T->val;
        MideOrderTraverse(T->right);
    }
}

非遞歸版:

根據中序遍歷的順序,對于任一結點,優先訪問其左孩子,而左孩子結點又可以看做一根結點,然后繼續訪問其左孩子結點,直到遇到左孩子結點為空的結點輸出節點值,按相同的規則訪問其右子樹。

因此其處理過程如下:

對于任一結點P,

1)若節點p不為空,則將P入棧并將P的左孩子置為當前的P,然后對P再進行相同的處理;

2)若訪問到左孩子為空后,則取棧頂元素并進行出棧操作,輸出該棧頂結點值,然后將當前的P置為棧頂結點的右孩子;

3)直到P為NULL并且棧為空則遍歷結束

代碼如下:

void MidOrderTraverse(BTree T)  
{
    std::stack<BTree> S;
    while(T||!S.empty())
    {
        while(T)
        {
            S.push(T);
            T=T->eft;
        }
        T=S.top();
        cout<<T->val;
        S.pop();
        T=T->ight;
    }
}

3、后序遍歷

后序遍歷的順序是:左節點、右節點、根節點

遞歸版:

void BackOrderTraverse(BTree T)
{
    if(T!=NULL)
    {
        BackOrderTraverse(T->left);
        BackOrderTraverse(T->right);
        cout<<T->val;
    }
}

非遞歸版:

1)版本1:

void PostOrder_Nonrecursive1(BTree 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)版本2:

采用雙棧法,這個真是神來之筆,對于想出這個辦法的人,真是佩服的五體投地。為了解釋原理,的看個實際例子,如下。

對于上面上面的二叉樹,對樹先左后右的將各個節點逐個放到棧1中,然后將棧1元素放到棧2中,過程如下:

棧1:A

棧1:空 | 棧2: A

棧1:B C -> B | 棧2:A C

棧1:B F G ->B F | 棧2:A C G

棧1:B F -> B | 棧2:A C G F

棧1:B -> D E | 棧2:A C G F B

棧1:D E -> D | 棧2:A C G F B E

棧1:空 | 棧2:A C G F B E D

最后輸出棧2,就是D E B F G C A,你說6不6.

void PostOrder_Nonrecursive(BTree 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();    
    }    
}

4、深度優先遍歷

深度優先遍歷,也就深入的遍歷,沿著每一個分支直到走到最后,然后才返回來遍歷剩余的節點。二叉樹不同于圖,圖需要標記節點是否已經訪問過,因為可能會存在環,而二叉樹不會出現環,所以不需要標記。那么,我們只需要一個棧空間,來壓棧就好了。因為深度優先遍歷,遍歷了根節點后,就開始遍歷左子樹,所以右子樹肯定最后遍歷。我們利用棧的性質,先將右子樹壓棧,然后在對左子樹壓棧。此時,左子樹節點是在top上的,所以可以先去遍歷左子樹。

void DepthFirstTravel(BTree T)  
{  
    stack<BTree> s;  
    s.push(T);  
    while(!s.empty())  
    {  
        T = s.top();  
        cout << T->data << " ";  
        s.pop();  
        if(T->rchild != NULL)  
        {  
            s.push(T->rchild);  
        }  
        if(T->lchild != NULL)  
        {  
            s.push(T->lchild);  
        }  

    }  
}

5、廣度優先遍歷

對于廣度優先遍歷二叉樹,也就是按層次的去遍歷。依次遍歷根節點,然后是左孩子和右孩子。所以要遍歷完當前節點的所有孩子,這樣才是層次遍歷嘛。此時我們就不能用棧這個數據結構了,因為棧只能在棧頂操作。在這里,我們需要根據左右孩子的順序來輸出,所以就是先進先出的原則,那么我們當然就想到了隊列這個數據結構。可以在rear依次插入左右孩子,在front依次讀取并刪除左右孩子,這樣就保證了層次的輸出。

void BreadthFirstTravel(BTree T)  
{  
    queue<BTree> q;  
    q.push(T);  
    while(!q.empty())  
    {  
        T = q.front();  
        cout << T->data << " ";  
        q.pop();  
        if(T->lchild != NULL)  
        {  
            q.push(T->lchild);  
        }  
        if(T->rchild != NULL)  
        {  
            q.push(T->rchild);  
        }  
    }  
}

參考

【1】 遍歷二叉樹的各種操作~~(非遞歸遍歷)

 

來自:http://blog.csdn.net/yixianfeng41/article/details/55228458

 

 本文由用戶 tielimu 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!