二叉樹的四種遍歷方式:遞迴、非遞迴+棧、Morris(後序非遞迴還有一種單棧和雙棧的不同版本)
阿新 • • 發佈:2019-02-02
本文參考:
參考文章1
參考文章2
程式碼中加入了一些自己的理解
/* 二叉樹的四種遍歷方式
*/
#include <iostream>
#include <stack>
using namespace std;
// 二叉樹節點的定義
class TreeNode{
public:
char val;
//int val;
TreeNode *left, *right;
TreeNode(int val){
this->val = val;
this->left = this->right = NULL;
}
};
// 遞迴方式,每個結點只遍歷一次,時間複雜度O(1),遞迴最多呼叫n次,空間複雜度O(n)
// 先序:根-》遞迴左-》遞迴右
void preOrder(TreeNode* root){
if(root==NULL)
return;
cout << root->val << endl;
preOrder(root->left);
preOrder(root->right);
}
// 中序:遞迴左-》根-》遞迴右
void inOrder(TreeNode* root){
if (root==NULL)
return;
inOrder(root->left);
cout << root->val << endl;
inOrder(root->right);
}
// 後序:遞迴左-》遞迴右-》根
void postOrder(TreeNode* root){
if(root==NULL)
return;
postOrder(root->left);
postOrder(root->right);
cout << root-> val << endl;
}
// 非遞迴,使用棧,每個節點只需遍歷一次,時間複雜度O(n),使用棧,只需壓入或彈出各一次,空間複雜度O(n)
// 先序:根壓入,從棧頂彈出結點,並訪問,
// 壓入當前右孩子,壓入當前左孩子,則出棧順序與先序遍歷一致
void preOrder2(TreeNode* root){
if(root==NULL)
return;
stack<TreeNode*> stk;
stk.push(root);
while(!stk.empty()){
TreeNode* pNode = stk.top();
stk.pop();
cout << pNode->val << endl;
if(pNode->right!=NULL)
stk.push(pNode->right);
if(pNode->left!=NULL)
stk.push(pNode->left);
}
}
// 中序:初始指標pNode指向根,
// 若pNode不空,則pNode壓入,pNode指向當前左孩子,一直到最左,
// 若pNode為空,從棧頂彈出結點,並訪問,pNode當前右孩子
void inOrder2(TreeNode* root){
if(root==NULL)
return;
stack<TreeNode*> stk;
TreeNode* pNode = root;
while(pNode!=NULL || !stk.empty()){
if(pNode!=NULL){
stk.push(pNode);
pNode = pNode->left;
}
else{
pNode = stk.top();
cout << pNode->val << endl;
stk.pop();
pNode = pNode->right;
}
}
}
// 後序,使用2個棧:設定兩個棧stk, stk2;
// 將根結點壓入第一個棧stk;彈出stk棧頂的結點,並把該結點壓入第二個棧stk2;
// 將當前結點的左孩子和右孩子先後分別入棧stk;當所有元素都壓入stk2後,依次彈出stk2的棧頂結點,並訪問之。
// 第一個棧的入棧順序是:根結點,左孩子和右孩子;壓入第二個棧的順序是:根結點,右孩子和左孩子。
// 因此,第二個棧彈出的順序就是:左孩子,右孩子和根結點。
void postOrder2(TreeNode* root){
if(root==NULL)
return;
stack<TreeNode*> stk;
stack<TreeNode*> stk2;
stk.push(root);
while(!stk.empty()){
TreeNode* pNode = stk.top();
stk.pop();
stk2.push(pNode);
if(pNode->left!=NULL)
stk.push(pNode->left);
if(pNode->right!=NULL)
stk.push(pNode->right);
}
while(!stk2.empty()){
cout << stk2.top()->val << endl;
stk2.pop();
}
}
// 後序,使用1個棧:
// 每次迴圈開始時,當前結點更新為棧頂
// 首先向下遍歷,指向之前訪問的結點的指標prev為空時,或者當前訪問的是prev的孩子時,繼續向下訪問左孩子並壓入,左孩子沒有則訪問右孩子並壓入
// 之後是從左孩子向上遍歷,即當前訪問的是prev的父結點時,如果父節點有右孩子,繼續向下訪問右孩子,並壓入
// prev不為空,並且當前訪問的節點也不是prev的孩子,說明已經到達葉子節點或父結點,之後輸出當前節點,並彈出
// 每個步驟操作完成後,將prev更新為當前結點
void postOrder3(TreeNode* root){
if(root==NULL)
return;
stack<TreeNode*> stk;
stk.push(root);
TreeNode* prev = NULL;//指向上次訪問的節點,即為父節點或者葉節點
while(!stk.empty()){
TreeNode *pNode = stk.top();
if(!prev || prev->left == pNode || prev->right == pNode){//之前沒有剛問過的點,或者當前是prev的孩子,則繼續向下遍歷並壓入
if(pNode->left)
stk.push(pNode->left);
else if(pNode->right)
stk.push(pNode->right);
}
else if(pNode->left==prev){//若左孩已經訪問過
if(pNode->right)//則繼續訪問右孩,並壓入
stk.push(pNode->right);
}
else{//到達葉節點,或者父節點
cout << pNode->val << endl;
stk.pop();
}
prev = pNode;
}
}
// Morris 遍歷:線索二叉樹,在O(1)空間內,O(n)時間內完成遍歷,通過修改葉子結點的左右空指標指向前驅或後記
// 中序:
// 1. 如果當前節點的左孩子為空,則輸出當前節點並將其右孩子作為當前節點。
// 2. 如果當前節點的左孩子不為空,在當前節點的左子樹中找到當前節點在中序遍歷下的前驅節點。
// a) 如果前驅節點的右孩子為空,將它的右孩子設定為當前節點。當前節點更新為當前節點的左孩子。
// b) 如果前驅節點的右孩子為當前節點,將它的右孩子重新設為空(恢復樹的形狀)。輸出當前節點。當前節點更新為當前節點的右孩子。
// 3. 重複以上1、2直到當前節點為空。
// 使用兩個輔助指標,空間複雜度O(1),
// 時間複雜度O(n):n個結點二叉樹有n-1條邊,每條邊最多有兩次,一次用於找前驅,一次用於訪問該結點
void inOrderMorris(TreeNode* root){
TreeNode* cur = root;
TreeNode* prev = NULL;
while(cur!=NULL){
if(cur->left == NULL){ // 1. (表示左子樹已經遍歷完,訪問其前驅)
cout << cur->val << endl;
cur = cur->right;
}
else{
//找到cur的前驅prev,即為cur的左孩子的最右孩子
prev = cur->left;
while(prev->right!=NULL && prev->right!=cur){
prev = prev->right;
}
//已經找到prev
if(prev->right==NULL){ // 2.a)
prev->right = cur; // 新增線索
cur = cur->left; // 向下左遍歷繼續為每個中結點找前驅
}
else{ // 2.b)
prev->right = NULL; // 第二次遍歷到中結點,刪除線索
cout << cur->val << endl; // 列印中結點
cur = cur->right; // 訪問右子樹,右子樹不再有右子樹時,cur為空,程式終止
}
}
}
}
// 前序:與中序相似,在於輸出的順序
// 1. 如果當前節點的左孩子為空,則輸出當前節點並將其右孩子作為當前節點。
// 2. 如果當前節點的左孩子不為空,在當前節點的左子樹中找到當前節點在中序遍歷下的前驅節點。
// a) 如果前驅節點的右孩子為空,將它的右孩子設定為當前節點。輸出當前節點(在這裡輸出,這是與中序遍歷唯一一點不同)。當前節點更新為當前節點的左孩子。
// b) 如果前驅節點的右孩子為當前節點,將它的右孩子重新設為空。當前節點更新為當前節點的右孩子。
// 3. 重複以上1、2直到當前節點為空。
// 時間、空間複雜度與中序相同
void preOrderMorris(TreeNode* root){
TreeNode* cur = root;
TreeNode* prev = NULL;
while(cur!=NULL){
if(cur->left == NULL){
cout << cur->val << endl;
cur = cur->right;
}
else{
prev = cur->left;
while(prev->right!=NULL && prev->right!=cur){
prev = prev->right;
}
if(prev->right == NULL){ // 與中序唯一不同之處
cout << cur->val << endl;
prev->right = cur;
cur = cur->left;
}
else{
prev->right = NULL;
cur = cur->right;
}
}
}
}
// 後序:稍複雜,需要建立臨時結點dump,令其左孩子是root,
// 需要子過程:倒序輸出某兩個節點之間路徑上的各個節點
// 當前節點設定為臨時節點dump。
// 1. 如果當前節點的左孩子為空,則將其右孩子作為當前節點。
// 2. 如果當前節點的左孩子不為空,在當前節點的左子樹中找到當前節點在中序遍歷下的前驅節點。
// a) 如果前驅節點的右孩子為空,將它的右孩子設定為當前節點。當前節點更新為當前節點的左孩子。
// b) 如果前驅節點的右孩子為當前節點,將它的右孩子重新設為空。倒序輸出從當前節點的左孩子到該前驅節點這條路徑上的所有節點。當前節點更新為當前節點的右孩子。
// 3. 重複以上1、2直到當前節點為空。
void reverseFromTo(TreeNode* from, TreeNode* to){
if(from==to)
return;
TreeNode *x = from;//x為路徑頭的結點
TreeNode *y = from->right;//y為待插入到路徑頭的節點
TreeNode *z;//下一帶插入點
// y不斷向x頭位置前插
while(true){
z = y->right;
y->right = x;
x = y;
y = z;
if(x == to)
break;
}
}
void printReverse(TreeNode* from, TreeNode* to){
reverseFromTo(from,to);//顛倒from到to結點的路徑
TreeNode* pNode = to;
while(true){
cout << pNode->val << endl;
if(pNode==from)
break;
pNode = pNode->right;
}
reverseFromTo(to,from);//恢復樹的形狀
}
void postOrderMorris(TreeNode* root){
if(root == NULL)
return;
TreeNode* dump = new TreeNode('z');
dump->left = root;
TreeNode* pNode = dump;
TreeNode* pPrev = NULL;
while(pNode!=NULL){//當前節點不為空時
if(pNode->left==NULL)
pNode = pNode->right;
else{
//找前驅pPrev
pPrev = pNode->left;
while(pPrev->right!=NULL && pPrev->right!=pNode){
pPrev = pPrev->right;
}
//前驅pPrev已找到
if(pPrev->right==NULL){
pPrev->right = pNode;
pNode = pNode->left;
}
else{
pPrev->right = NULL;
printReverse(pNode->left,pPrev);
pNode = pNode->right;
}
}
}
}
int main()
{
TreeNode* a = new TreeNode('A');
TreeNode* b = new TreeNode('B');
TreeNode* c = new TreeNode('C');
TreeNode* d = new TreeNode('D');
TreeNode* e = new TreeNode('E');
TreeNode* f = new TreeNode('F');
TreeNode* root = a;
root->left = b;
root->right = c;
b->left = d;
b->right = e;
c->left = f;
// 前序:ABDECF
// 中序:DBEAFC
// 後序:DEBFCA
cout << "先序遍歷:遞迴"<< endl;
preOrder(root);
cout << "中序遍歷:遞迴"<< endl;
inOrder(root);
cout << "後序遍歷:遞迴"<< endl;
postOrder(root);
cout << "先序遍歷:棧"<< endl;
preOrder2(root);
cout << "中序遍歷:棧"<< endl;
inOrder2(root);
cout << "後序遍歷:雙棧"<< endl;
postOrder2(root);
cout << "後序遍歷:單棧"<< endl;
postOrder3(root);
cout << "中序遍歷:Morris"<< endl;
inOrderMorris(root);
cout << "後序遍歷:Morris"<< endl;
postOrderMorris(root);
}