1. 程式人生 > >二叉樹的四種遍歷方式:遞迴、非遞迴+棧、Morris(後序非遞迴還有一種單棧和雙棧的不同版本)

二叉樹的四種遍歷方式:遞迴、非遞迴+棧、Morris(後序非遞迴還有一種單棧和雙棧的不同版本)

本文參考:
參考文章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); }