理解的精髓在於用"棧"來取代遞歸,出棧的操作其實就相當於某層遞歸的出口
【前序遍歷】
用棧來替代遞歸的過程(因為遞歸歸根到底也是用棧來實現的)
考慮遞歸的時候 每進入一個遞歸都會往左子樹試探,因此一直往左子樹走到頭,遇到一個節點就訪問它
然后壓入棧中 訪問完左子樹之后再回過頭繼續對每個節點的右子樹進行"遞歸操作"
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 }