【數據結構】樹的前序、中序、后續遍歷的非遞歸寫法


理解的精髓在於用"棧"來取代遞歸,出棧的操作其實就相當於某層遞歸的出口

【前序遍歷】

用棧來替代遞歸的過程(因為遞歸歸根到底也是用棧來實現的)

考慮遞歸的時候 每進入一個遞歸都會往左子樹試探,因此一直往左子樹走到頭,遇到一個節點就訪問它

然后壓入棧中 訪問完左子樹之后再回過頭繼續對每個節點的右子樹進行"遞歸操作"

 1 void PreOrder(){
 2     Node p = root;
 3     while (p || s.empty()){
 4         while (p){
 5             s.push(p);
 6             cout<<p->idx<<" ";
 7             p = p->l;
 8         }
 9         if (!s.empty()){
10             p = s.top();
11             s.pop();
12             p = p->r;
13         }
14     }
15 }

 

【中序遍歷】

和中序遍歷結構一樣,只是訪問的時刻變了,只有左子樹訪問完了才開始訪問棧中的節點(迎合中序遍歷的要求)

 1 void MiddleOrder(){
 2     Node p = root;
 3     while (p || s.empty()){
 4         while (p){
 5             s.push(p);
 6             p = p->l;
 7         }
 8         if (!s.empty()){
 9             //到這一步說明s.top()的左子樹訪問完了(或者左子樹為空)
10             p = s.top();s.pop();
11             cout<<p->idx<<" ";
12             p = p->r;
13         }
14     }
15 }

【后序遍歷】

基本的思路和中序遍歷、前序遍歷類似。

但是需要考慮一個問題,就是當到達s.top()這個點的時候,雖然s.top()的左子樹都已經訪問完了。

但是不能保證s.top()的右子樹訪問完了。所以只有當s.top()的右子樹為空,或者s.top()的右子樹的根節點

已經訪問過了(或者可以說前一個訪問的點是s.top()的右子樹的根節點,因為訪問完右子樹的根節點肯定就接着訪問的是s.top()了),

那么就可以直接訪問s.top()了,否則這個點還不到該訪問的時候,因此還得入棧,遞歸他的右子樹。

可以回憶一下遞歸的寫法,會發現某個點x要在遞歸dfs(l[x])和dfs(r[x])之后才能訪問(后續遍歷),因此可以想見這個點s.top()需要入棧兩次

之后才能保證要開始訪問s.top()了,所以另一種寫法是,記錄這個點是第幾次入棧了,如果是第二次,那么就可以等下次彈出的時候直接輸出了。

代碼中給的是,記錄上次訪問的是哪個節點,也是可以的,比較方便)

 1 void LaterOrder(){
 2     Node p = root;
 3     Node lastv = NULL;
 4     while (p || s.empty()){
 5         while (p){
 6             s.push(p);
 7             p = p->l;
 8         }
 9         if (!s.empty()){
10             p = s.top();
11             if(!p->r || p->r==lastv){//說明右子樹的節點已經都訪問過了
12                 cout<<p->idx<<" ";
13                 lastv = p;
14                 p=NULL;//方便往上"遞歸"
15             }else{
16                 p = p->r;
17             }
18         }
19     }
20 }

 


免責聲明!

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



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