二叉樹的遞歸遍歷仍是很簡單的,代碼也很好寫。可是在有的時候可能會要求用迭代法來遍歷二叉樹,或者你兩種方法都會的話確定也更好。既然能夠用遞歸實現,而遞歸的實質其實就是棧,那麼迭代法遍歷很顯然就是會用到棧。可是入棧和出棧的順序是須要考慮清楚的,感受本身常常會忘記怎麼處理這個順序,從而致使代碼又不會寫了,那麼今天就來總結一個這個入棧出棧的順序,邏輯理順了,代碼就不難寫了。數據結構
前序遍歷的遍歷順序要保證:中->左->右。 其遞歸實現以下:函數
void preOrder(TreeNode* p){
if(p == NULL)
return;
cout << p->val <<endl;
preOrder(p->left);
preOrder(p->right);
}
複製代碼
1
/ \
2 3
/ \ / \
4 5 6 7
複製代碼
考慮到如上一個簡單的二叉樹,那麼用先序遍歷的實現結果應該是1 2 4 5 3 6 7。
一開始很顯然從根節點開始沿着左子樹往下遍歷,這部分入棧也很簡單,就是一直把當前節點入棧,在把當前節點指向左子樹,直到當前節點爲NULL。對應在這個例子裏面就是入棧了1 2 4,而後當前節點已經指向了4的左子樹,它是NULL。
問題是碰到這種狀況下一步應該怎麼作才能保證按正確的順序遍歷到2的右節點5呢?
首先很明確,要想使當前節點直到節點5,那麼就必須經過指向2的右樹來得到。如今棧頂是4,那麼顯然4應該被彈出來。
那麼4又該何時被彈出來呢?
應該是遍歷完了4的左樹,且得到了4的右樹指針以後,遍歷4的右樹以前。這主要是由於前序遍歷要先遍歷完左子樹才能遍歷右子樹,在遍歷右子樹的時候,獲取其右子樹的指針前,不能把4刪掉。而一旦左子樹遍歷完了以後,得到了指針立刻要遍歷右子樹以前,應該當即把4出棧,由於遍歷其右子樹,假設其右子樹不爲空的話,那麼就還會有節點入棧,所以得在遍歷其右子樹以前對4出棧,否則後面就沒法在合適的節點出棧了。
所以,這個出棧順序就肯定下來了:當前節點的左子樹已經遍歷完,得到了其右子樹的指針以後,且遍歷右子樹以前,應該把棧頂元素彈出。學習
void preOrder(TreeNode* p){
if( p == NULL)
return;
stack<TreeNode*> s;
while(p != NULL || !s.empty()){
while(p){
s.push(p);
cout<< p->val <<endl;
p = p->left;
}
p = s.top()->right;
s.pop();
}
}
複製代碼
中序遍歷的順序:左->中->右。 中序遍歷的棧的維護包括入棧出棧操做和前序遍歷一致,不一樣的是,前序遍歷按入棧的順序輸出節點的值,中序遍歷彈出的時候輸出節點的值。這主要是由於當前節點得等其左子樹所有遍歷完了以後右子樹開始遍歷以前才能輸出,從前面的分析,那就正好是當前節點彈出的時間點。
代碼以下:ui
void inOrder(TreeNode* p){
if( p == NULL)
return;
stack<TreeNode*> s;
while(p != NULL || !s.empty()){
while(p){
s.push(p);
p = p->left;
}
p = s.top()->right;
cout << s.top()->val << endl;
s.pop();
}
}
複製代碼
後序遍歷的順序:左->右->中。
後序遍歷和前序、中序不太同樣,主要是由於每一個節點都會前後用到三次。得到左節點須要一次,得到右節點須要一次,最後還須要在輸出本身。而前序和中序輸出自身和獲取右節點是連續的,所以只須要兩次。由於只須要兩次,全部經過一次入棧和一次出棧就能夠保證順序正確。可是後序是不夠的,因此還須要一個數據結構來存儲信息,代表是第二次到仍是第三次到當前節點。spa
void afterOrder(TreeNode* root) {
if (root== NULL)
return;
stack<TreeNode*> s;
vector<TreeNode*> v;
s.push(root);
TreeNode* p = NULL;
while (!s.empty()) {
p = s.top();
bool flag1 = false;
bool flag2 = false;
if (p->left == NULL)
flag1 = true;
if (p->right == NULL)
flag2 = true;
if (!flag1) {
auto it = v.begin();
for (; it != v.end(); it++) {
if (*it == p->left)
break;
}
if (it != v.end()) {
flag1 = true;
}
}
if (!flag2) {
auto it = v.begin();
for (; it != v.end(); it++) {
if (*it == p->right)
break;
}
if (it != v.end()) {
flag2 = true;
}
}
if (flag1 == true && flag2 == true) {
v.push_back(s.top());
s.pop();
}
else if (flag1 == false) {
s.push(p->left);
}
else if (flag1 == true && flag2 == false) {
s.push(p->right);
}
}
for (auto p : v) {
cout << p->val << endl;
}
}
複製代碼
其實迭代法遍歷二叉樹不止以上介紹的方法,可是我以爲這種方法仍是很好理解的,由於這種遍歷的順序是和遞歸函數調用的順序很像的。若是感興趣的話,也可學習其餘迭代方法,進行對比。指針