「遍歷」是二叉樹各類操做的基礎,能夠在遍歷過程當中對節點進行各類操做,如:求節點的雙親,求節點的孩子,判斷節點的層次。固然,還有一些更重要的操做,例如,依據遍歷序列創建二叉樹,對創建的二叉樹進行線索化,等等。html
二叉樹的各類遍歷操做必須瞭然於心,不管是遞歸的,仍是非遞歸的。遞歸算法的優勢是形式簡單,固然,正如一句話所說「迭代是人,遞歸是神」。遞歸算法的整個詳細過程仍是很燒腦的,咱們只有徹底一遍調試下來才能懂它具體在幹嗎。遞歸每一步都把未知看成已知,每一步又在把未知變爲已知。想不到描述起來又用上了遞歸的概念,生活中其實有不少遞歸的現象,我印象最深的就是兩面面對面放置的鏡子,你永遠也找不到最深處的本身(固然咱們假設你的視力無限好同時光在反射過程當中沒有亮度損失),也就是說這個遞歸過程沒有邊界條件,會無限遞歸,永遠也不會返回,這在程序中可就是死循環呀,至關可怕,CPU很快就會被掏空。因此每次設計遞歸算法時,首先要考慮到的就是邊界返回條件。還有一個我印象很深的電影,《盜夢空間》,三層夢境,從現實中進入第一層,再從第一層夢境進入第二層,最後再從第二層進入第三層。固然電影裏也說了能夠無限往下遞歸,進入一個叫迷失域的地方,不過好在電影的設定是不管你掉入了多深的夢,你只要在夢裏死了,就能返回上層夢境,也就是說,每層夢境都有一個返回條件,因此原則上是不會死循環的。。。算法
哎呀,扯太遠了,其實我一直想寫一篇關於遞歸的哲學文章,闡述程序和咱們現實的聯繫,算了,不扯了。數據結構
遞歸形式上很簡單,代碼很簡潔,而後時間複雜度卻很大,同時還得花費必定的空間複雜度(遞歸棧深度),好比經典的Hanoi問題,時間複雜度是O(2^n),n爲圓盤的個數,時間複雜度膨脹的很厲害。因此咱們在不少時候不能依賴於遞歸算法,而須要去尋找相應的非遞歸算法,固然並不是全部問題都能找到非遞歸算法,即使能,那估計也複雜到沒人能讀懂。而非遞歸算法形式上就比較複雜,不過它其實更接近人的思惟過程,雖然過程也不簡單,但畢竟代碼不涉及遞歸過程的一層一層的概念,不存在利用未知信息求解的過程。因此只要把代碼表面的邏輯弄懂了就是懂了。post
在二叉樹的問題上,因爲樹的結構頗有規律,因此用遞歸算法形式上會比非遞歸簡單不少,也不用去考慮不少細節問題,只要把邊界條件想好就行。spa
下面我把前序,中序,後序遍歷的遞歸算法和非遞歸算法代碼給出,其實都不長,要常常複習,思考這幾個過程,以達到熟練於心的程度。設計
1 //如下三種遞歸算法均對每一個節點只訪問一次,故時間複雜度都是O(n) 2 //遞歸工做棧的深度剛好是樹的深度,因此最壞的狀況下空間複雜度爲O(n) 3 4 //遞歸前序遍歷二叉樹 5 int PreOrderTraverse_Recursive(BiPtr T) 6 { 7 if (T) 8 { 9 cout << T->data << " "; 10 PreOrderTraverse_Recursive(T->lchild); 11 PreOrderTraverse_Recursive(T->rchild); 12 } 13 return 0; 14 } 15 16 //遞歸中序遍歷二叉樹 17 int InOrderTraverse_Recursive(BiPtr T) 18 { 19 if (T) 20 { 21 InOrderTraverse_Recursive(T->lchild); 22 cout << T->data << " "; 23 InOrderTraverse_Recursive(T->rchild); 24 } 25 return 0; 26 } 27 28 //遞歸後序遍歷二叉樹 29 int PostOrderTraverse_Recursive(BiPtr T) 30 { 31 if (T) 32 { 33 PostOrderTraverse_Recursive(T->lchild); 34 PostOrderTraverse_Recursive(T->rchild); 35 cout << T->data << " "; 36 } 37 return 0; 38 }
從以上咱們能夠看出遞歸算法的簡潔,並且咱們發現三種順序的遍歷方式隻影響了其中兩行代碼的順序而已,三種遞歸算法形式上很是的一致和統一,這正是遞歸的魅力所在。指針
咱們再看看非遞歸算法調試
1 //非遞歸前序遍歷二叉樹 2 int PreOrderTraverse_NonRecursive(BiPtr T) 3 { 4 BiPtr p = T; 5 SqStack S; 6 InitStack(S); 7 8 while (p || !EmptyStack(S)) 9 { 10 while (p) 11 { 12 cout << p->data << " "; 13 Push(S, p); 14 p = p->lchild; 15 } 16 if (!EmptyStack(S)) 17 { 18 Pop(S, p);//剛剛訪問過的根節點 19 p = p->rchild;//訪問該根節點的右子樹,並對右子樹重複上述過程 20 } 21 } 22 return 0; 23 }
1 //非遞歸中序遍歷二叉樹 方法1 2 int InOrderTraverse_NonRecursive_1(BiPtr T) 3 { 4 BiPtr p = T; 5 SqStack S; 6 InitStack(S); 7 8 Push(S, T); 9 while (!EmptyStack(S)) 10 { 11 while (GetTop(S)) 12 { 13 Push(S, p->lchild); 14 p = p->lchild;//不停地「向左下走」 15 } 16 Pop(S, p);//前面會「走過頭」,也就是說最後棧頂會多出一個空指針,因此彈出多入棧的空指針 17 18 if (!EmptyStack(S)) 19 { 20 Pop(S, p); 21 cout << p->data << " "; 22 Push(S, p->rchild); 23 p = p->rchild; 24 } 25 } 26 return 0; 27 } 28 29 //非遞歸中序遍歷二叉樹 方法2 30 int InOrderTraverse_NonRecursive_2(BiPtr T) 31 { 32 BiPtr p = T;//從樹根開始 33 SqStack S; 34 InitStack(S); 35 36 while (p || !EmptyStack(S))//還有未訪問的結點 37 { 38 if (p)//p向左下走到底並記錄下沿途的節點 39 { 40 Push(S, p); 41 p = p->lchild; 42 } 43 else//p走到了底,再依次彈出剛纔路過卻沒有訪問的節點,訪問之,而後p向右走 44 { 45 Pop(S, p); 46 cout << p->data << " "; 47 p = p->rchild; 48 } 49 } 50 return 0; 51 }
1 //非遞歸後序遍歷二叉樹 2 /* 3 後序遍歷是先訪問左子樹再訪問右子樹,最後訪問根節點 4 因爲返回根節點時有可能時從左子樹返回的,也多是從右子樹返回的, 5 要區分這兩種狀況需藉助輔助指針r,其指向最近訪問過的節點 6 */ 7 int PostOrderTraverse_NonRecursive(BiPtr T) 8 { 9 BiPtr p = T,r = nullptr;//從樹根開始,r用於指向最近訪問過的節點 10 SqStack S; 11 InitStack(S); 12 13 while (p || !EmptyStack(S)) 14 { 15 if (p) 16 { 17 Push(S, p); 18 p = p->lchild;//走到最左下方 19 } 20 else//說明已經走到最左下了,要返回到剛剛通過的根節點,即從null的左孩子返回到它的雙親節點 21 { 22 p = GetTop(S); 23 if (p->rchild && p->rchild != r)//右孩子還沒有訪問,且右孩子不爲空,因而,訪問右孩子 24 { 25 p = p->rchild; 26 Push(S, p); 27 p = p->lchild;//走到右孩子的最左下方,即重複最開始的過程 28 } 29 else//右孩子爲空,則直接輸出該節點,或者右孩子剛剛被訪問過了,一樣直接輸出該節點 30 { 31 Pop(S, p); 32 cout << p->data << " "; 33 r = p;//r指向剛剛訪問過的節點 34 p = nullptr;//左右子樹都已經處理完,根節點也剛處理完,返回到根節點的雙親,即棧頂元素,下次從新取棧頂元素分析 35 } 36 } 37 } 38 return 0; 39 }
從以上看出,三種順序的非遞歸算法基本徹底不一樣,由於非遞歸算法其實本質上是把咱們人類的思惟過程用一些循環判讀語句表達出來了而已,人類的思惟天然沒有神那麼高大上,因此天然得一步一步,正如咱們本身在紙上思考的那樣,還得考慮每個細節。code
下面我再把其餘一些非關鍵代碼也貼出來(其實我以前發的文章中已經有了,這篇文章知識作一個總結,由於最近在複習數據結構,方便之後本身忘了複習)htm
創建二叉樹
1 //默認按前序遍歷的順序輸入,尾結點用#表示 2 int Create_BiTree(BiPtr& T) 3 { 4 ElemType c; 5 //cout << "請輸入當前節點元素值:" << endl; 6 cin >> c; 7 if (c == '#') T = NULL; 8 else 9 { 10 T = new BiNode;//新建一個節點 11 T->data = c; 12 Create_BiTree(T->lchild); 13 Create_BiTree(T->rchild); 14 } 15 return 0; 16 }
棧結構定義
1 //--------------------------------棧結構---------------------------------- 2 int InitStack(SqStack &S) 3 { 4 S.base = (BiPtr *)malloc(STACK_INIT_SIZE * sizeof(BiPtr)); 5 if (!S.base) 6 { 7 cout << "分配空間失敗!"; 8 exit(-1); 9 } 10 S.top = S.base; 11 S.stacksize = STACK_INIT_SIZE; 12 return 0; 13 } 14 15 int Push(SqStack &S, BiPtr e) 16 { 17 if ((S.top - S.base) >= STACK_INIT_SIZE) 18 { 19 //空間不足時從新分配空間,並將原來的元素拷貝到新分配的空間中去 20 S.base = (BiPtr *)realloc(S.base, (STACK_INIT_SIZE + STACKINCREASE) * sizeof(BiPtr)); 21 if (!S.base) 22 { 23 cout << "分配空間失敗!"; 24 exit(-1); 25 } 26 S.top = S.base + STACK_INIT_SIZE; 27 S.stacksize = STACK_INIT_SIZE + STACKINCREASE; 28 } 29 *(S.top) = e;//結構體 30 S.top++;//先插入元素,再後移指針,始終保證top指向棧頂元素下一個位置 31 return 0; 32 } 33 34 int Pop(SqStack &S, BiPtr &e) 35 { 36 if (S.base == S.top) 37 { 38 cout << "棧爲空!"; 39 exit(0); 40 } 41 S.top--; 42 e = *(S.top); 43 return 0; 44 } 45 46 BiPtr GetTop(SqStack &S) 47 { 48 if (S.base == S.top) 49 { 50 cout << "棧爲空!"; 51 return 0; 52 } 53 else return *(S.top - 1);//返回一個指針 54 } 55 56 57 int EmptyStack(SqStack &S) 58 { 59 if (S.base == S.top) return 1;//stack is empty! 60 else return 0;//stack is not empty! 61 }
結構體定義
1 #define STACK_INIT_SIZE 100 2 #define STACKINCREASE 10 3 4 typedef char ElemType; 5 typedef struct BiNode 6 { 7 ElemType data; 8 struct BiNode *lchild, *rchild; 9 }BiNode, *BiPtr; 10 11 typedef struct 12 { 13 BiPtr *base;//始終處於棧底位置 14 BiPtr *top;//始終處於棧定元素的下一個位置,當top == base時,棧爲空;當base == NULL時,棧不存在 15 int stacksize;//棧當前可以使用的最大容量 16 }SqStack;