【C++】非遞迴的三種二叉樹遍歷
二叉樹的遍歷有三種方式,如下:
(1)前序遍歷(DLR),首先訪問根結點,然後遍歷左子樹,最後遍歷右子樹。簡記根-左-右。
(2)中序遍歷(LDR),首先遍歷左子樹,然後訪問根結點,最後遍歷右子樹。簡記左-根-右。
(3)後序遍歷(LRD),首先遍歷左子樹,然後遍歷右子樹,最後訪問根結點。簡記左-右-根。
前序:根A,A的左子樹B,B的左子樹沒有,看右子樹,為D,所以A-B-D。再來看A的右子樹,根C,左子樹E,E的左子樹F,E的右子樹G,G的左子樹為H,沒有了結束。連起來為C-E-F-G-H,最後結果為ABDCEFGH
中序:先訪問根的左子樹,B沒有左子樹,其有右子樹D,D無左子樹,下面訪問樹的根A,連起來是BDA。
再訪問根的右子樹,C的左子樹的左子樹是F,F的根E,E的右子樹有左子樹是H,再從H出發找到G,到此C的左子樹結束,找到根C,無右子樹,結束。連起來是FEHGC, 中序結果連起來是BDAFEHGC
後序:B無左子樹,有右子樹D,再到根B。再看右子樹,最下面的左子樹是F,其根的右子樹的左子樹是H,再到H的根G,再到G的根E,E的根C無右子樹了,直接到C,這時再和B找它們其有的根A,所以連起來是DBFHGECA
程式碼實現:#include <stack> #include <queue> #include <iostream> using namespace std; struct Tree{ Tree* lchild; Tree* rchild; char* data; }; /*************************** * 二叉樹:前序遍歷(非遞迴) ****************************/ void preTraverse(Tree* t) { if(t==NULL) return ; stack<Tree *> s ; //遍歷至最左葉級節點 while(t!=NULL) { s.push(t); cout<<(t->data); t= t->lchild; } //遍歷所有的節點用來查詢其右節點 while(!s.empty()) { //獲取最左葉級節點的右節點 Tree * temp = s.top()->rchild; s.pop(); //遍歷至該右節點的最左葉級節點 while(temp!=NULL) { s.push(temp); cout<<(temp->data); temp= temp->lchild; } } } /*************************** * 二叉樹:中序遍歷(非遞迴) ****************************/ void inOrderTraverse(Tree * t) { if(t==NULL) return; stack<Tree *> s; s.push(t); Tree *curr = t->lchild; //遍歷所有的節點用來查詢其右節點 while(curr!=NULL || !s.empty()) { //遍歷並push所有左節點 while(curr!=NULL) { s.push(curr); curr = curr->lchild; } curr = s.top(); s.pop(); cout<<(curr->data); //取右節點 curr = curr->rchild; } } /*************************** * 二叉樹:後序遍歷(非遞迴) ****************************/ void postOrderTraverse(Tree * t) { if(t == NULL) return; stack<Tree *> s; s.push(t); Tree* curr = t->lchild; Tree* tmpr = NULL; while(!s.empty()) { //先push完左節點 while(curr != NULL) { s.push(curr); curr = curr -> lchild; } curr = s.top(); //取最左葉級節點 //如果左葉級節點沒有有右節點,那麼其為葉級節點,列印該節點,並繼續迴圈跳至其上級節點,遍歷其上級節點的右節點子樹.... if(curr->rchild == NULL || curr->rchild == tmpr) { cout<<(curr->data); tmpr = curr; //忽略已經遍歷過的右節點 s.pop(); curr = NULL; //忽略已經遍歷過的左節點 } else { curr = curr->rchild; //如果左葉級節點有右節點,則走右節點,並再次走while(!s.empty()){},查詢該右節點的的左節點子樹(此處類似遞迴) } } } /************************************************************************/ /* 廣度優先 */ /************************************************************************/ void scope_priority(Tree* t) { queue<Tree*> q; q.push(t); Tree* curr=NULL; while (!q.empty()) { curr=q.front(); cout<<curr->data; q.pop(); if (curr->lchild!=NULL) { q.push(curr->lchild); } if (curr->rchild!=NULL) { q.push(curr->rchild); } } } /************************************************************************/ /* CREATE A TEST TREE */ /************************************************************************/ Tree* create_tree() { Tree* A=(Tree*)malloc(sizeof(Tree)); Tree* B=(Tree*)malloc(sizeof(Tree)); Tree* C=(Tree*)malloc(sizeof(Tree)); Tree* D=(Tree*)malloc(sizeof(Tree)); Tree* E=(Tree*)malloc(sizeof(Tree)); Tree* F=(Tree*)malloc(sizeof(Tree)); Tree* G=(Tree*)malloc(sizeof(Tree)); Tree* H=(Tree*)malloc(sizeof(Tree)); A->data="A"; B->data="B"; C->data="C"; D->data="D"; E->data="E"; F->data="F"; G->data="G"; H->data="H"; A->lchild=B; A->rchild=C; B->rchild=D; C->lchild=E; E->lchild=F; E->rchild=G; G->lchild=H; C->rchild=B->lchild=D->lchild=D->rchild=F->lchild=F->rchild=G->rchild=H->lchild=H->rchild=NULL; return A; } /************************************************************************/ /* DESTORY TEST TREE */ /************************************************************************/ void destory(Tree* t) { if (t==NULL) { return; } destory(t->lchild); destory(t->rchild); free(t); } /***************************************************************************************** * 個人總結:無論哪種遍歷方式,都是從根節點開始遍歷並push到最左節點, * 然後,逐一出棧,取其右節點,當做根節點(視作一顆子樹),並再次遍歷並push到其最左節點 * 再出棧,在取右節點...... *****************************************************************************************/ int main(void) { Tree* tree=create_tree(); preTraverse(tree); cout<<endl; inOrderTraverse(tree); cout<<endl; postOrderTraverse(tree); cout<<endl; getchar(); destory(tree); return 0; }
二、引用
廣度優先遍歷二叉樹。
廣度優先周遊二叉樹(層序遍歷)是用佇列來實現的,從二叉樹的第一層(根結點)開始,自上至下逐層遍歷;在同一層中,按照從左到右的順序對結點逐一訪問。
按照從根結點至葉結點、從左子樹至右子樹的次序訪問二叉樹的結點。演算法:
1初始化一個佇列,並把根結點入列隊;
2當佇列為非空時,迴圈執行步驟3到步驟5,否則執行6;
3出佇列取得一個結點,訪問該結點;
4若該結點的左子樹為非空,則將該結點的左子樹入佇列;
5若該結點的右子樹為非空,則將該結點的右子樹入佇列;
6結束。
非遞迴深度優先遍歷二叉樹。
棧是實現遞迴的最常用的結構,利用一個棧來記下尚待遍歷的結點或子樹,以備以後訪問,可以將遞迴的深度優先遍歷改為非遞迴的演算法。
1. 非遞迴前序遍歷:遇到一個結點,就訪問該結點,並把此結點推入棧中,然後下降去遍歷它的左子樹。遍歷完它的左子樹後,從棧頂托出這個結點,並按照它的右連結指示的地址再去遍歷該結點的右子樹結構。
2. 非遞迴中序遍歷:遇到一個結點,就把它推入棧中,並去遍歷它的左子樹。遍歷完左子樹後,從棧頂托出這個結點並訪問之,然後按照它的右連結指示的地址再去遍歷該結點的右子樹。
3. 非遞迴後序遍歷:遇到一個結點,把它推入棧中,遍歷它的左子樹。遍歷結束後,還不能馬上訪問處於棧頂的該結點,而是要再按照它的右連結結構指示的地址去遍歷該結點的右子樹。遍歷遍右子樹後才能從棧頂托出該結點並訪問之。另外,需要給棧中的每個元素加上一個特徵位,以便當從棧頂托出一個結點時區別是從棧頂元素左邊回來的(則要繼續遍歷右子樹),還是從右邊回來的(該結點的左、右子樹均已周遊)。特徵為Left表示已進入該結點的左子樹,將從左邊回來;特徵為Right表示已進入該結點的右子樹,將從右邊回來。
4. 簡潔的非遞迴前序遍歷:遇到一個結點,就訪問該結點,並把此結點的非空右結點推入棧中,然後下降去遍歷它的左子樹。遍歷完左子樹後,從棧頂托出一個結點,並按照它的右連結指示的地址再去遍歷該結點的右子樹結構。
----------------------------------------------------------------------
圖的深度優先搜尋法是樹的先根遍歷的推廣,它的基本思想是:從圖G的某個頂點v0出發,訪問v0,然後選擇一個與v0相鄰且沒被訪問過的頂點vi訪問,再從vi出發選擇一個與vi相鄰且未被訪問的頂點vj進行訪問,依次繼續。如果當前被訪問過的頂點的所有鄰接頂點都已被訪問,則退回到已被訪問的頂點序列中最後一個擁有未被訪問的相鄰頂點的頂點w,從w出發按同樣的方法向前遍歷,直到圖中所有頂點都被訪問。
圖的廣度優先搜尋是樹的按層次遍歷的推廣,它的基本思想是:首先訪問初始點vi,並將其標記為已訪問過,接著訪問vi的所有未被訪問過的鄰接點vi1,vi2, …, vi t,並均標記已訪問過,然後再按照vi1,vi2, …, vi t的次序,訪問每一個頂點的所有未被訪問過的鄰接點,並均標記為已訪問過,依次類推,直到圖中所有和初始點vi有路徑相通的頂點都被訪問過為止。
三、附:斐波那契數列的兩種計算方法
#include <iostream>
#include <stack>
using namespace std;
/************************************************************************/
/* 遞迴法 */
/************************************************************************/
int count_fibo(int n)
{
if (n<=2)
{
return 1;
}
else
{
return count_fibo(n-1)+count_fibo(n-2);
}
}
/************************************************************************/
/* 迭代法 */
/************************************************************************/
int count_fibo_it(int n)
{
if (n<=0)
{
throw "Invalid argument(s)";
}
if (n==1||n==2)
{
return 1;
}
int fn1=1;
int fn2=1;
for (int i=3;i<=n;i++)
{
if (i%2==0)
{
fn2=fn1+fn2;
}
else
{
fn1=fn1+fn2;
}
}
if (n%2==0)
{
return fn2;
}
else
{
return fn1;
}
}
/************************************************************************/
/* 計算斐波那契數列,1 1 2 3 5 8 13 21 ... */
/************************************************************************/
int main(void)
{
cout<<count_fibo(15)<<endl;
cout<<count_fibo_it(15)<<endl;
getchar();
return 0;
}