文章目錄
在前一篇文章二叉樹遍曆遞迴演算法對二叉樹遍曆的遞迴演算法做了總結,這篇文章就來對二叉樹遍曆的非遞迴演算法做個匯總。還是與上一篇文章一樣的順序,一一匯總先序、中序、後序以及層序遍曆的非遞迴演算法。
1、先序遍曆(非遞迴演算法)
先序遍曆非遞迴訪問,使用棧即可實現。先序遍曆的非遞迴訪問在所有的遍曆中算是最簡單的了。主要思想就是先將根結點壓入棧,然後根結點出棧並訪問根結點,而後依次將根結點的右孩子、左孩子入棧,直到棧為空白為止。代碼如下:
void preOrderIter(struct node *root){ if (root == NULL) return; stack<struct node *> s; s.push(root); while (!s.empty()) { struct node *nd = s.top(); cout << nd->data << " "; s.pop(); if (nd->right != NULL) s.push(nd->right); if (nd->left != NULL) s.push(nd->left); } cout << endl;}
先序遍曆的非遞迴演算法另一演算法,也是用的棧,只是稍微複雜點,當左子樹遍曆完後,需要回溯並遍曆右子樹。
void preOrderIter2(struct node *root){ stack<struct node *> s; while (root != NULL || !s.empty()) { if (root != NULL) { cout << root->data << " "; //訪問結點併入棧 s.push(root); root = root->left; //訪問左子樹 } else { root = s.top(); //回溯至父親結點 s.pop(); root = root->right; //訪問右子樹 } } cout << endl;}
本演算法有一個地方要注意的是,每次從棧中pop出結點時,表示該結點以及該的左子樹已經訪問完了,接下來訪問其右子樹。
2、中序遍曆(非遞迴演算法)
中序遍曆非遞迴演算法也是採用棧實現,與上面的先序遍曆演算法2類似,只是訪問根結點的時機不同。
void inOrderIter(struct node *root){ stack<struct node *> s; while (root != NULL || !s.empty()) { if (root != NULL) { s.push(root); root = root->left; } else { root = s.top(); cout << root->data << " "; //訪問完左子樹後才訪問根結點 s.pop(); root = root->right; //訪問右子樹 } } cout << endl;}
當然,中序遍曆非遞迴演算法還有很多種,比如修改二叉樹結點結構加入一個欄位來標示結點是否被訪問過等,但是都比該演算法複雜,且修改了原來的二叉樹結構,所以在這裡就不囉嗦了,有興趣的可以移步這裡二叉樹非遞迴訪問,後續如果有時間我會對二叉樹遍曆非遞迴演算法通過在結點添加一個標記的方法再做一個總結。
3、後序遍曆(非遞迴演算法)
後序遍曆的非遞迴演算法較複雜,使用一個棧可以實現,但是過程很繁瑣,這裡可以巧妙的用兩個棧來實現後序遍曆的非遞迴演算法。注意到後序遍曆可以看作是下面遍曆的逆過程:即先遍曆某個結點,然後遍曆其右孩子,然後遍曆其左孩子。這個過程逆過來就是後序遍曆。演算法步驟如下:
- Push根結點到第一個棧s中。
- 從第一個棧s中Pop出一個結點,並將其Push到第二個棧output中。
- 然後Push結點的左孩子和右孩子到第一個棧s中。
- 重複過程2和3直到棧s為空白。
- 完成後,所有結點已經Push到棧output中,且按照後序遍曆的順序存放,直接全部Pop出來即是二叉樹後序遍曆結果。
void postOrderIter(struct node *root){ if (!root) return; stack<struct node*> s, output; s.push(root); while (!s.empty()) { struct node *curr = s.top(); output.push(curr); s.pop(); if (curr->left) s.push(curr->left); if (curr->right) s.push(curr->right); } while (!output.empty()) { cout << output.top()->data << " "; output.pop(); } cout << endl;}
4、層序遍曆(非遞迴演算法)
如果不考慮分層換行列印,則用一個隊列可以很容易的通過非遞迴實現層序遍曆。但是要每列印一層換一行,就顯得稍微複雜了一點。可以有兩種方法,第一種使用兩個隊列,代碼很簡練,第二種方法則是使用一個隊列,代碼稍顯複雜。
方法一:使用兩個隊列
第一個隊列currentLevel用於儲存當前層的結點,第二個隊列nextLevel用於儲存下一層的結點。當前層currentLevel為空白時,表示這一層已經遍曆完成,可以列印分行符號了。
然後將第一個空的隊列currentLevel與隊列nextLevel交換,然後重複該過程直到結束。這個演算法比較好理解。
void levelOrderIter(struct node* root){ if (!root) return; queue<struct node *> currentLevel, nextLevel; currentLevel.push(root); while (!currentLevel.empty()) { struct node *currNode = currentLevel.front(); currentLevel.pop(); if (currNode) { cout << currNode->data << " "; nextLevel.push(currNode->left); nextLevel.push(currNode->right); } if (currentLevel.empty()) { cout << endl; swap(currentLevel, nextLevel); } }}void swap(queue<struct node *> &curr, queue<struct node*> &next){ while (!next.empty()) { struct node *nd = next.front(); next.pop(); curr.push(nd); }}
方法二:使用一個隊列
只使用一個隊列的話,需要額外的兩個變數來儲存當前層結點數目以及下一層的結點數目。
void levelOrderIter2(struct node *root){ if (!root) return; queue<struct node*> nodesQueue; int nodesInCurrentLevel = 1; int nodesInNextLevel = 0; nodesQueue.push(root); while (!nodesQueue.empty()) { struct node *currNode = nodesQueue.front(); nodesQueue.pop(); nodesInCurrentLevel--; if (currNode) { cout << currNode->data << " "; nodesQueue.push(currNode->left); nodesQueue.push(currNode->right); nodesInNextLevel += 2; } if (nodesInCurrentLevel == 0) { cout << endl; nodesInCurrentLevel = nodesInNextLevel; nodesInNextLevel = 0; } }}