1. 程式人生 > >二叉樹的遞迴和非遞迴方式的三種遍歷

二叉樹的遞迴和非遞迴方式的三種遍歷

二叉樹的三種遍歷方式,前序遍歷中序遍歷後序遍歷,中的前中後都是指的是根節點的訪問順序,這三種遍歷方式的概念在這裡就不多說了,太普遍了!

二叉樹的建立

我們這裡以前序遍歷為例:
我們先定義好結構體

struct Tree{
    Tree* lson;
    Tree* rson;
    int data;
};
Tree* T;

下面是前序建立二叉樹:

void createBtiTree(Tree* &tree)
{
    int data;
    cin>>data;
    if(data==-1) {
        tree=
NULL; }else { tree = new Tree(); tree->data = data; createBtiTree(tree->lson); createBtiTree(tree->rson); } }

二叉樹的遞迴方式遍歷

當我們覺得構造遞迴困難的時候(不知道在這裡用構造這個詞對不對),我們就可以把遞迴想象成為一棵樹,這樣就容易構造一些!

遞迴的三種遍歷方式比較基礎,在這裡就不過多進行贅述,直接給出程式碼!如果讀者有困難的話,可以試著在紙上描述一下,畢竟遞迴是要深入的東西!

前序遍歷

void preOrderRecur(Tree* tree)
{
    if(tree==NULL) return ;
    cout<<tree->data<<" ";
    preOrderRecur(tree->lson);
    preOrderRecur(tree->rson);
}

中序遍歷

void inOrderRecur(Tree* tree)
{
    if(tree==NULL) return ;
    inOrderRecur(tree->lson);
    cout<<
tree->data<<" "; inOrderRecur(tree->rson); }

後序遍歷

void posOrderRecur(Tree* tree)
{
    if(tree==NULL) return ;
    posOrderRecur(tree->lson);
    posOrderRecur(tree->rson);
    cout<<tree->data<<" ";
}

非遞迴形式的二叉樹的三種遍歷方式

用遞迴解決的問題都能用非遞迴的方式解決。因為遞迴是使用函式棧來儲存資訊的,如果自己用自己申請的資料結構來代替函式棧就能實現相同的功能!

前序遍歷

前序遍歷也是最簡單的一種,分為下面三個步驟:
1. 申請一個棧,將頭結點壓入棧
2. 彈出棧頂指標,記作:cur,如果這個這個指標有右孩子,將右孩子入棧,如果有左孩子,將左孩子入棧;
3. 不斷重複過程2,直到棧為空,結束程式!

實現程式碼

void PreOrderRecur(Tree* tree)
{
    while(!sta.empty()) sta.pop();
    sta.push(tree);
    while(!sta.empty()) {
        Tree* cur  = sta.top();
        sta.pop();
        cout<<cur->data<<" ";
        if(cur->rson!=NULL) sta.push(cur->rson);
        if(cur->lson!=NULL) sta.push(cur->lson);
    }
}

中序遍歷

  1. 申請一個棧,頭結點為開始節點(當前節點)
  2. 如果當前節點不為空,那麼將左節點壓棧,即做tree=tree->lson操作,如果當前節點為空的時候列印棧頂元素,並且出棧,將 當前節點變為棧頂元素的右節點也就是做tree = cur->rson(中序遍歷中,棧主要儲存的是父節點元素)
  3. 不斷重複步驟2直到棧空,結束程式!

實現程式碼

void InOrderRecur(Tree* tree)
{
    while(!sta.empty()) sta.pop();
    while(!sta.empty() || tree!=NULL) {
        if(tree==NULL)  {
            Tree* cur = sta.top();
            sta.pop();
            cout<<cur->data<<" ";
            tree=cur->rson;
        } else {
            sta.push(tree);
            tree=tree->lson;
        }
    }
}

後序遍歷

後序遍歷用棧實現起來相對前面兩種遍歷是難實現一些,這這裡給出兩個方法:第一種方法是用兩個棧來實現的(比較好理解),第二種是用一個棧來實現的!

兩個棧的方法實現

先說兩個棧來實現的方法,第一個棧儲存的是根節點元素,第二棧是儲存輸出的元素!過程如下:

  1. 申請一個棧,將根節點入棧
  2. 如果棧不為空,彈出第一個棧的棧頂元素記做cur,將第一個棧頂元素出棧,然後將cur壓入第二個棧。如果cur有左孩子將左孩子加入第一個棧,如果有右孩子將右孩子加入第一個棧
  3. 不斷的重複步驟2,直到第一個棧為空,列印第二個棧,結束程式!

實現程式碼

void PosOrderRecur(Tree* tree)
{
    while(!sta.empty()) sta.pop();
    while(!Sta.empty()) Sta.pop();
    sta.push(tree);
    while(!sta.empty()) {
        Tree* cur = sta.top();
        sta.pop();Sta.push(cur);
        if(cur->lson!=NULL) sta.push(cur->lson);
        if(cur->rson!=NULL) sta.push(cur->rson);
    }
    while(!Sta.empty()) {
        cout<<Sta.top()->data<<" ";
        Sta.pop();
    }
}

一個棧的實現

我們用cur表示棧頂元素,h表示的是最近棧的元素,初始化時h為頭結點。演算法流程如下:
1. 申請一個棧 ,將頭結點壓棧,初始化h變數,
2. 如果棧不為空,cur賦為棧頂元素!
—- 1.如果cur的左孩子不為NULL並且h不等於cur的左孩子也不等於cur的右孩子那麼就將左孩子入棧。(如果最近h等於當前節點的左孩子,就說明左子樹已經列印完了,否則就代表還沒有列印過,就應該將左孩子或者右孩子入棧)
—- 2.在1條件不成立的條件下,並且cur的右孩子不等於h並且不為空,就說明右子樹還沒有處理過,這個時候就應該將cur的右孩子入棧!
—- 3.如果前倆個條件都不成立,就說明cur的左子樹和右子樹已經列印完畢了,或者當前節點為葉子節點,此時就應該將棧頂元素出棧了,並且令h=cur
3. 一直重複步驟2直到棧為空,結束程式

實現程式碼

void TPosOrderRecur(Tree* tree)
{
    while(!sta.empty()) sta.pop();
    sta.push(tree);
    Tree* c=tree;
    while(!sta.empty()) {
        Tree* cur = sta.top();
        if(cur->lson!=NULL && cur->lson!=c && cur->rson!=c){
            sta.push(cur->lson);
        } else if(cur->rson!=NULL && c!=cur->rson) {
            sta.push(cur->rson);
        } else{
            sta.pop();
            c=cur;
            cout<<cur->data<<" ";
        }
    }
}