【樹01】對二叉樹前序/中序/後序遍歷演算法的一些思考

山斗發表於2021-03-13

二叉樹的前序、中序、後序遍歷

  

 

 

   每個節點會被經過3次,前序、中序、後序的區別在於:在哪一次經過該節點時對其進行訪問

2. 遞迴實現

traverseRecursive(BiTrNode<T>* node):

    basecase:
        if(node == nullptr) return;

    general:
        1 print(node->data);
        2 traverseRecursive(node->lchild);
        3 traverseRecursive(node->rchild);

        前序:123
        中序:213
        後序:132

3. 非遞迴實現

  首先需要明確的是,所謂的遞迴實現其實是在模擬遞迴棧,所以使用的基本資料結構是stack

  這裡如何設計節點的入棧策略是關鍵,因為執行的時候無非就是依次從stack中取出棧頂元素進行處理,所以stack中節點的入棧順序就是訪問順序

  那既然我們已經知道了,每個節點都會被經過3次,所謂前序、中序、後序的區別無非就是第幾次經過時對其進行操作(這裡我們把對節點的操作簡化成print node->data)。

 

初步想法

  給每一個節點增加一個欄位(lookTimes)表示這是第幾次經過該節點。

  模擬一下3次經過的入棧順序:①第一次經過該節點 -> ②遍歷該節點的左孩子 -> ③回到該節點(第二次經過)-> ④遍歷該節點的右孩子 -> ⑤回到該節點(第三次經過)。

  init:所有節點的lookTimes都設為0,將根節點入棧。

  從stack中取出棧頂元素表示經過一次該節點(lookTimes++),然後看lookTimes進行相應的操作:

  • lookTimes = 1:第一次訪問該節點,接下來要訪問該節點的左子樹,然後再回到該節點,再所以應該依次:push(左孩子),push(自己)
  • lookTimes = 2:第二次訪問該節點,接下來要訪問該節點的右子樹,然後再回到該節點,所以應該依次:push(右孩子),push(自己)
  • lookTimes = 3:第三次訪問該節點,此時對該節點的3次訪問就結束了,所以就沒有什麼額外操作等著取下一個節點就行了。

  模擬好3次經過之後,要實現前序/中序/後序遍歷只需在每一次push前print(node->data)就可以了。比如,如果是前序遍歷就在p->lookTimes == 1的if語句塊的第一行加上print(node->data)就可以了。

 

 

 

  偽碼如下:

  

//version1.0
while(stack is not empty){
        p = stack.pop();
        (p->lookTimes)++;

        if(p->lookTimes == 1){
            //print(p->data); 前序遍歷
            stack.push(p);
            if(p->lchild)
                stack.push(p->lchild);
        }else if(p->lookTimes == 2){
            //print(p->data); 中序遍歷
            stack.push(p);
            if(p->lchild)
                stack.push(p->rchild);
        }else if(p->lookTimes == 3){
            //print(p->data); 後序遍歷
        }         
    }

 

優化版本

  上面是一種比較通用的想法,但是在考慮到具體的某一種遍歷方式時,比如前序遍歷:既然在lookTimes == 1時就已經對該節點進行訪問了,那麼後面的兩次回到該節點顯然是沒有必要的。

  因此根據每一種遍歷方式的特點進行優化:

  1. 前序

  入棧順序:①第一次經過該節點 -> ②遍歷該節點的左孩子 -> ③遍歷該節點的右孩子。而且這樣的話每個節點只會經過一次,lookTimes欄位也就沒有必要了。

//前序遍歷_version2.0:without lookTimes    
while(stack is not empty){
        p = stack.pop();
        print(p->data);

        if(p->lchild)
                stack.push(p->lchild);

        if(p->rchild)
                stack.push(p->rchild);         
 }        

  2. 中序

  入棧順序:①第一次經過該節點 -> ②遍歷該節點的左孩子 -> ③回到該節點(第二次經過)-> ④遍歷該節點的右孩子。

//中序遍歷_version2.0
while(stack is not empty){
        p = stack.pop();
        (p->lookTimes)++;

        if(p->lookTimes == 1){
            stack.push(p);
            if(p->lchild)
                stack.push(p->lchild);
        }else if(p->lookTimes == 2){
            print(p->data);
            if(p->lchild)
                stack.push(p->rchild);
        }   
    }

  3. 後序

  後序因為在第三次經過時才會訪問,所以不能再優化了。

 

相關文章