二叉樹的遍歷(基於棧的非遞歸方式實現)


在寫二叉樹的時候如果用遞歸實現二叉樹的遍歷很簡單,但是用非遞歸來實現二叉樹的遍歷就不那么簡單了需要一些技巧。

那為什么還要非遞歸實現呢?個人理解:如果樹的高度很大,超過了允許遞歸的次數,那么就會出錯,比如我記得python只允許遞歸100次(不知道記錯沒)

這時候用迭代就要保險的多,不會出錯。

下面先來做基本的准備說明:

 1 #include<iostream>
 2 #include<stack>
 3 
 4 #define null NULL
 5 
 6 template<typename Entry>
 7 class binary_node
 8 {
 9     public:
10         friend class binary_tree;
11         Entry data;
12         binary_node* lchild;
13         binary_node* rchild;
14         binary_node():lchild(null),rchild(null){}
15         ~binary_node(){}
16 };
17 
18 template<typename Entry>
19 class binary_tree
20 {
21     binary_node* root;
22     
23     public:
24         binary_tree():root(null){}
25         ~binary_tree(){}
26         void preordertraversal(binary_node*,void (*visit)(Entry& bt));
27         void ineordertraversal(binary_node*,void (*visit)(Entry& bt));
28         void posteordertraversal(binary_node*,void (*visit)(Entry& bt));
29         
30 };

先來看看前序遍歷:

 1 //遞歸實現先序遍歷
 2 template <typename Entry>
 3 void binary_tree<Entry>::preordertraversal(binary_node* root,void (*visit)(Entry& bt))
 4 {
 5     if(root != null)
 6     {
 7         visit(root->data);
 8         preorder(root->lchild,visit);
 9         preorder(root->rchild,visit);
10     }
11 }

棧實現前序遍歷較簡單,由於每次先輸出根節點,再輸出左節點隨后是右節點。

算法是:

1、若棧非空輸出根節點,並出棧
2、將右節點壓棧(如果存在)
3、將左節點壓棧(如果存在)
4、重復第1步直到棧空
注意:之所以先壓右節點是考慮了棧的特性,這樣在迭代過程中可以先拿到左節點處理。(棧的先入后出)

 1 template <typename Entry>
 2 void binary_tree<Entry>::preordertraversal(binary_node* root,void (*visit)(Entry& bt))
 3 {
 4     if(root == null) return;
 5     binary_node* cur = root;
 6     stack<binary_node*> s;
 7     s.push(cur);
 8     while(!s.empty())
 9     {
10         cur = s.top();
11         visit(cur->data);
12         s.pop();
13         if(cur->rchild != null) s.push(cur->rchild);
14         if(cur->lchild != null) s.push(cur->lchild);
15     }
16 }

 

再來看看中序遍歷:

 1 //遞歸實現的中序遍歷
 2 template <typename Entry>
 3 void binary_tree<Entry>::inordertraversal(binary_node* root,void (*visit)(Entry& bt))
 4 {
 5     if(root != null)
 6     {
 7         preorder(root->lchild,visit);
 8         visit(root->data);
 9         preorder(root->rchild,visit);
10     }
11 }

棧的中序遍歷需要套兩層循環,由於需要先輸出左節點,因此必須向下查找直到左節點為空才能輸出。處理邏輯如下:

1、如果棧頂元素非空且左節點存在,將其入棧,重復該過程。若不存在則進入第2步
2、若棧非空,輸出棧頂元素並出棧。判斷剛出棧的元素的右節點是否存在,不存在重復第2步,存在則將右節點入棧,跳至第1步

 1 template <typename Entry>
 2 void binary_tree<Entry>::inordertraversal(binary_node* root,void (*visit)(Entry& bt))
 3 {
 4     if(root == null) return;
 5     binary_node* cur = root;
 6     stack<binary_node*> s;
 7     s.push(cur);
 8     while(!s.empty())
 9     {
10         while(s.top()->lchild!=null)
11             s.push(s.top()->lchild);
12         while(!s.empty())
13         {
14             binary_node* cur = s.top();
15             visit(cur->data);
16             s.pop();
17             if(cur->rchild != null)
18             {
19                 s.push(cur->rchild);
20                 break;
21             }
22         }
23     
24     }
25     
26 }

 

再來看看后序遍歷:

 1 //遞歸實現的后序遍歷
 2 template <typename Entry>
 3 void binary_tree<Entry>::postordertraversal(binary_node* root,void (*visit)(Entry& bt))
 4 {
 5     if(root != null)
 6     {
 7         preorder(root->lchild,visit);
 8         preorder(root->rchild,visit);
 9         visit(root->data);
10     }
11 }

后序遍歷在中序的雙層循環的基礎上需要加入一個記錄,專門記錄上一次出棧的節點。步驟如下:

1、如果棧頂元素非空且左節點存在,將其入棧,重復該過程。若不存在則進入第2步(該過程和中序遍歷一致)
2、判斷上一次出棧節點是否當前節點的右節點,或者當前節點是否存在右節點,滿足任一條件,將當前節點輸出,並出棧。否則將右節點壓棧。跳至第1步

 1 template <typename Entry>
 2 void binary_tree<Entry>::postordertraversal(binary_node* root,void (*visit)(Entry& bt))
 3 {
 4     if(root == null) return;
 5     stack<binary_node*> s;
 6     s.push(root);
 7     binary_node* lastpop = null;
 8     while(!s.empty())
 9     {
10         while(s.top()->lchild != null)
11             s.push(s.top()->lchild);
12         while(!s.empty())
13         {
14             if(lastpop == s.top()->rchild || s.top()->rchild == null)
15             {
16                 visit(s.top()->data);
17                 lastpop = s.top();
18                 s.pop();
19             }
20             else if(s.top()->rchild != null)
21             {
22                 s.push(s.top()->rchild);
23                 break;
24             }
25         }
26     }
27 }

 

if(lastpop == s.top()->rchild || s.top()->rchild == null)
是判斷上次彈出的結點是不是當前結點的右結點,或者當前節點沒有右結點,因為訪問次序是“左-右-中”。

 

而二叉樹的層次遍歷可以通過隊列來實現:

 1 //用隊列來實現二叉樹的層次遍歷
 2 template <typename Entry>
 3 void binary_tree<Entry>::leveltraversal(binary_node* root,void (*visit)(Entry& bt))
 4 {
 5     if(root == null) return;
 6     queue<binary_node*> q;
 7     q.push(root);
 8     while(!q.empty())
 9     {
10         visit(q.front());
11         if(q.front()->lchild != null)
12             q.push(q.front()->lchild);
13         if(q.front()->rchild != null)
14             q.push(q.front()->rchild);
15         q.pop();
16     }
17 }

 

轉載說明:主要參考來源:https://www.jianshu.com/p/12848eef3452


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM