1. 程式人生 > >二叉樹遍歷非遞迴寫法之大統一

二叉樹遍歷非遞迴寫法之大統一

在學習二叉樹遍歷時,大家都很容易接受遞迴寫法,好理解。對於非遞迴寫法,基本思想是用棧消除遞迴,但是教材上的前序、中序和後序基本上三個寫法,還很難理解。博主親身經歷,找工作中,考查二叉樹遍歷的非遞迴寫法還是常見的。所以決心整理出此文,方便理解和記憶二叉樹遍歷。

        來複習一下二叉樹的遞迴遍歷。

  1. struct TreeNode {  
  2.     int data;  
  3.     TreeNode * left, * right;  
  4. };  
  5. void preOrderTraverse(TreeNode * root, void (*visit)(TreeNode *)) {  
  6.     if (root) {  
  7.         visit(root);  
  8.         preOrderTraverse(root->left, visit);  
  9.         preOrderTraverse(root->right, visit);  
  10.     }  
  11. }  
  12. void inOrderTraverse(TreeNode * root, void (*visit)(TreeNode *)) {  
  13.     if (root) {          
  14.         inOrderTraverse(root->left, visit);  
  15.         visit(root);  
  16.         inOrderTraverse(root->right, visit);  
  17.     }  
  18. }  
  19. void postOrderTraverse(TreeNode * root, void (*visit)(TreeNode *)) {  
  20.     if (root) {  
  21.         postOrderTraverse(root->left, visit);  
  22.         postOrderTraverse(root->right, visit);  
  23.         visit(root);          
  24.     }  
  25. }  

         首先,博主找到了一篇類似論文,

《更簡單的非遞迴遍歷二叉樹的方法》,該文非常漂亮的將三種遍歷方法統一起來,除了有三行程式碼的順序不一樣外。該實現中,對每個節點定義了一個pair對,通過改變區域性入棧順序實現整體遍歷的不同。

        本文意圖使用常見的二叉樹遍歷的形式,簡潔的實現這三種遍歷。

void preOrderTraverseNonrecursive(TreeNode * root, void (*visit)(TreeNode *)) {
    stack<TreeNode *> s;
    TreeNode * p = root;
    while(p != NULL || !s.empty()) {
        while(p != NULL) {
           
visit(p);
s.push(p);
            p = p->left;
        }
if (!s.empty()) {
p = s.top();
p = p->right;
s.pop();
        }
    } 
}

void inOrderTraverseNonrecursive(TreeNode * root, void (*visit)(TreeNode *)) {
    stack<TreeNode *> s;
    TreeNode * p = root;
    while(p != NULL || !s.empty()) {
        while(p != NULL) {            
s.push(p);
            p = p->left;
        }
if (!s.empty()) {
p = s.top();
visit(p);
p = p->right;
s.pop();
        }
    } 
}

void postOrderTraverseNonrecursive(TreeNode * root, void (*visit)(TreeNode *)) {
    stack<TreeNode *> s;
    TreeNode * p = root, *pre = NULL;
    while(p != NULL || !s.empty()) {
        while(p != NULL) {            
s.push(p);
            p = p->left;
        }
if (!s.empty()) {
p = s.top();
if( p->right == NULL || p->right == pre) {
visit(p);

                pre = p;

               p = NULL;

s.pop();
}
else {
p = p->right;
}
        }
    } 
}

        在前序遍歷的程式碼中,最外層是while迴圈直到棧空且p為空,裡面的while迴圈,不停訪問根節點p,且迭代p=p->right,直到p為空。此時,從root開始的所有左分支的根節點均已被訪問。到if分支時,取棧頂節點,切換到其右子樹開始迭代。

        前序遍歷和中序遍歷區別僅在於visit函式的呼叫位置不同中序和後序的區別在於呼叫visit的時機不一致。教材上的實現中,後序遍歷需要一個標誌來記錄當前節點是否被訪問過。在本文的實現中,通過一個pre指標記錄被訪問的最後一個元素,通過p->right==pre的比較來確認p是否被訪問過。當然,直接訪問p的另一情形是p沒有右孩子。在這樣的實現下,中序和後序的區別儘可能小。三種實現中,程式碼結構較一致,只有較少的變動。

        希望本文可以方便那些學習非遞迴遍歷二叉樹的同學。

        歡迎大家留言討論!