採用棧資料結構的二叉樹遍曆

來源:互聯網
上載者:User

標籤:des   style   blog   http   color   使用   os   資料   

  【前言】樹的遍曆,根據訪問自身和其子節點之間的循序關聯性,分為前序,後序遍曆。對於二叉樹,每個節點至多有兩個子節點(特別的稱為左,右子節點),又有中序遍曆。由於樹自身具有的遞迴性,這些遍曆函數使用遞迴函式很容易實現,代碼也非常簡潔。藉助於資料結構中的棧,可以把樹遍曆的遞迴函式改寫為非遞迴函式。

 

  在這裡我思考的問題是,很顯然,迴圈可以改寫為遞迴函式。遞迴函式是否藉助棧這種資料結構改寫為迴圈呢。因為函數調用中,call procedure stack 中儲存了流程的 context,調用和返回相當於根據調用棧中的 context 進行跳轉。而採用 stack 資料結構時,主要還是一個順序迴圈結構,主要通過 continue 實現流程式控制制。

 

  首先,給出遍曆二叉樹的序的定義:

  (1)前序走訪:當前節點,左子節點,右子節點;

  (2)中序遍曆:左子節點,當前節點,右子節點;

  (3)後序遍曆:左子節點,右子節點,當前節點。

 

  對二叉尋找樹 BST 來說,中序遍曆的輸出,是排序結果。所以這裡我以一個 BST 的中序遍曆為主要例子說明問題。一個簡單的 BST 如所示:

 

  

 

  其中序遍曆的輸出為:1,2,3,4,5,6,7,8,9;

 

  首先給出中序遍曆的遞迴函式,代碼如下:

 

 1 typedef struct tagNODE 2 { 3     int nVal; 4     int bVisited; //是否被訪問過 5     struct tagNODE *pLeft; 6     struct tagNODE *pRight; 7 } NODE, *LPNODE; 8  9 //中序遍曆二叉樹(遞迴版本)10 void Travel_Recursive(LPNODE pNode)11 {12     if(pNode != NULL)13     {14         Travel_Recursive(pNode->pLeft);15         _tprintf(_T("%ld, "), pNode->nVal);16         Travel_Recursive(pNode->pRight);17     }18 }

 

  很明顯,對應於前面給出的定義,只需要調整上述代碼中行號為 14,15,16 的順序,就可以得到相應的遍曆序。

 

  現在,引入棧資料結構,它是一個元素為節點指標的數組,將上面的遞迴函式改寫為非遞迴函式。中序遍曆的基本方法是:

 

  (1)將根節點 push 入棧;

  (2)當棧不為空白時,重複(3)到(5)的操作:

  (3)偷窺棧頂部節點,如果節點的左子節點不為 NULL,且沒有被訪問,則將其左子節點 push 入棧,並跳到(3)。

  (4)當被偷窺的節點沒有左子樹,pop 該節點出棧,並訪問它(同時標記該節點為已訪問狀態)。

  (5)當該節點的右子節點不為空白,將其右子節點 push 入棧,並跳到(3)。

 

  根據以上方法,給出非遞迴函式的中序遍曆版本代碼如下:

 

 1 typedef struct tagNODE 2 { 3     int nVal; 4     int bVisited; //是否被訪問過 5     struct tagNODE *pLeft; 6     struct tagNODE *pRight; 7 } NODE, *LPNODE; 8  9 //輔助資料結構10 LPNODE g_Stack[256];11 int g_nTop;12 13 //遍曆二叉樹,藉助於stack資料結構的非遞迴版本14 void TravelTree()15 {16     //while the stack is not empty17     while(g_nTop >= 0)18     {19         //peek the top node in stack;20         LPNODE pNode = g_Stack[g_nTop];21 22         //push left child;23         if(pNode->pLeft != NULL && !pNode->pLeft->bVisited)24         {25             ++g_nTop;26             g_Stack[g_nTop] = pNode->pLeft;27             continue;28         }29 30         //pop and visit it;31         _tprintf(_T("%ld, "), pNode->nVal);32         pNode->bVisited = 1;33         --g_nTop; 34 35         //push right child;36         if(pNode->pRight != NULL && !pNode->pRight->bVisited)37         {38             ++g_nTop;39             g_Stack[g_nTop] = pNode->pRight;40             continue;41         }       42     }43 }

 

  在上面的代碼的 while 迴圈體內,可以分為三個小的代碼塊:

  (1)pop 棧頂的節點,並訪問;

  (2)push 左子節點;

  (3)push 右子節點;

 

  只要調整 while 迴圈體中的這三個代碼塊的順序,就可以分別實現三種遍曆序。從上面的代碼中,有兩點需要說明:

  (1)最後一個代碼塊中的 continue 可以不需要寫,但為了可以調整代碼塊的順序,兩個 continue 都是需要的。

  (2)因為前序走訪的邏輯的簡潔性,不藉助於 bVisited 標記,也可以完成遍曆,但為了通用,還是需要這個節點標記。

 

  最後,補充上其他並不重要的方法,建立樹,釋放樹,main 函數的代碼如下:

 

//左右 Child 定義#define LCHILD        0#define RCHILD        1typedef struct tagNODE{    int nVal;    int bVisited; //是否被訪問過    struct tagNODE *pLeft;    struct tagNODE *pRight;} NODE, *LPNODE;LPNODE g_Stack[256];int g_nTop;LPNODE InsertNode(LPNODE pParent, int nWhichChild, int val){    LPNODE pNode = (LPNODE)malloc(sizeof(NODE));    memset(pNode, 0, sizeof(NODE));    pNode->nVal = val;    if(pParent != NULL)    {        if(nWhichChild == LCHILD)            pParent->pLeft = pNode;        else            pParent->pRight = pNode;    }    return pNode;}//遞迴釋放二叉樹的記憶體void FreeTree(LPNODE pRoot){    if(pRoot != NULL)    {        FreeTree(pRoot->pLeft);        FreeTree(pRoot->pRight);        //_tprintf(_T("freeing Node (%ld) ...\n"), pRoot->nVal);        free(pRoot);    }}int _tmain(int argc, _TCHAR* argv[]){    //索引為 0 的元素不使用。    LPNODE pNodes[10] = { 0 };    pNodes[1] = InsertNode(pNodes[0], LCHILD, 7);    pNodes[2] = InsertNode(pNodes[1], LCHILD, 4);    pNodes[3] = InsertNode(pNodes[1], RCHILD, 9);    pNodes[4] = InsertNode(pNodes[2], LCHILD, 2);    pNodes[5] = InsertNode(pNodes[2], RCHILD, 6);    pNodes[6] = InsertNode(pNodes[3], LCHILD, 8);    pNodes[7] = InsertNode(pNodes[4], LCHILD, 1);    pNodes[8] = InsertNode(pNodes[4], RCHILD, 3);    pNodes[9] = InsertNode(pNodes[5], LCHILD, 5);    //push 根節點    g_nTop = 0;    g_Stack[g_nTop] = pNodes[1];    TravelTree();    _tprintf(_T("\n"));    Travel_Recursive(pNodes[1]);    _tprintf(_T("\n"));    FreeTree(pNodes[1]);    return 0;}
View Code

 

  可以看到,釋放樹(FreeTree)這個函數,就是按照後序遍曆的順序進行釋放的。

 

  【補充】和本文相關的我寫的其他部落格文章:

  (1)採用路徑模型實現遍曆二叉樹的方法。2013-5-18;

  (2)[非原創]樹和圖的遍曆。2008-8-10;

 

  【後記】

  以上補充文章,和本文一同,獻給曾經向我請教“採用非遞迴方法遍曆樹”問題的小玉(littlehead)學妹。儘管,限於個人天賦和能力的有限,我給出的解答遲了一些。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.