二叉樹遍歷算法的改進(非遞歸實現)

二叉樹遍歷算法的改進

二叉樹的深度優先遍歷算法都是用遞歸函數實現的,這是很低效的,原因在於系統幫你調用了一個棧並做了諸如保護現場和恢復現場等複雜的操作,才使得遍歷可以用非常簡潔的代碼實現。二叉樹深度優先遍歷算法的非遞歸實現用用戶定義的棧來代替系統棧,也就是用非遞歸的方式來實現遍歷算法,可以得到不小的效率提升。

二叉樹深度優先遍歷算法的非遞歸實現

(1)先序遍歷非遞歸算法

要寫出其遍歷的非遞歸算法,其主要任務是用自己定義的棧來代替系統棧的功能。

以圖1所示的二叉樹爲例,過程爲圖二所示

二叉樹遍歷算法的改進(非遞歸實現)

二叉樹遍歷算法的改進(非遞歸實現)

初態棧空

  1. 結點1入棧。
  2. 出棧,輸出棧頂結點1,並將1的左、右孩子結點(2和4)入棧;右孩子先入棧,左孩子後入棧,因爲對左孩子的訪問要先於右孩子,後入棧的會先出棧訪問。
  3. 出棧,輸出棧頂結點2,並將2的左、右孩子結點(3和5)入棧。
  4. 出棧,輸出棧頂結點3,3爲葉子結點,無孩子,本步無結點入棧。
  5. 出棧,輸出棧頂結點5。

出棧,輸出棧頂結點4,此時棧空,進入終態。

遍歷序列爲1,2,3,5,4。

代碼如下

void preorderNonrecursion(BTNode *bt)
{
    if(bt != NULL)
    {
        BTNode *Stack[maxSize];             //定義一個棧
        int top = -1;                       //初始化棧
        BTNode *p;
        Stack[++top] = bt;                  //根結點入棧
        while(top != -1)                    //棧空循環結束
        {
            p = Stack[top--];               //出棧並輸出棧頂結點
            Visit(p);                       //Visit()爲訪問p的函數
            if(p->rchild != NULL)           //棧頂結點的右孩子存在,則右孩子入棧
                Stack[++top] = p->rchild;
            if(p->lchild != NULL)           //棧頂結點的左孩子存在,則左孩子入棧
                Stack[++top] = p->lchild;
        }
    }
}
(2)中序遍歷非遞歸算法

類似於先序遍歷,對圖1中的二叉樹進行中序遍歷,各個結點進棧、出棧過程如圖3所示。

二叉樹遍歷算法的改進(非遞歸實現)

初態棧空。

  1. 結點1入棧,1左孩子存在。
  2. 結點2入棧,2左孩子存在。
  3. 結點3入棧,3左孩子不存在。
  4. 出棧,輸出棧頂結點3,3右孩子不存在。
  5. 出棧,輸出棧頂結點2,2右孩子存在,右孩子5入棧,5左孩子不存在。
  6. 出棧,輸出棧頂結點5,5右孩子不存在。
  7. 出棧,輸出棧頂結點1,1右孩子存在,右孩子4入棧,4左孩子不存在。

出棧,輸出棧頂結點4,此時棧空,進入終態。

遍歷序列爲3,2,5,1,4。

由以上步驟可以看出,中序非遞歸遍歷過程如下:

  1. 開始根結點入棧
  2. 循環執行如下操作:如果棧頂結點左孩子存在,則左孩子進棧;如果棧頂結點左孩子不存在,則出棧並輸出棧頂結點,然後檢查其右孩子是否存在,如果存在,則右孩子入棧。
  3. 當棧空時算法結束。

代碼如下

void inorderNonrecursion(BTNode *bt)
{
    if(bt != NULL)
    {
        BTNode *Stack[maxSize];
        int  top = -1;
        BTNode *p;
        p = bt;
        /*下邊循環完成中序遍歷。注意:圖3進棧、出棧過程在7中會出現棧空狀態,但是這時遍歷還沒有結束,因根結點的右子樹還沒有遍歷,此時p非空,根據這一點來維持循環的進行*/
        while(top != -1 || p != NULL)
        {
            while(p != NULL)        //左孩子存在,則左孩子入棧
            {
                Stack[++top] = p;
                p = p->lchild;
            }
            if(top != -1)           //在棧不空的情況下出棧並輸出出棧結點
            {
                p = Stack[top--];
                Visit(p);
                p = p->rchild;
            }
        }
    }
}
(3)後序遍歷非遞歸算法

首先寫出圖1中二叉樹進行先序遍歷和後序遍歷的序列。

先序遍歷序列:1、2、3、5、4

後序遍歷序列:3、5、2、4、1

把後序遍歷序列逆序得:

逆後序遍歷序列:1、4、2、5、3

發現,逆後序遍歷序列和先序遍歷序列有一點的聯繫,逆後序遍歷序列只不過是先序遍歷過程中對左右子樹遍歷順序交換所得到的結果,如圖4所示。

因此,只需要將前面的非遞歸先序遍歷算法中對左右子樹的遍歷順序交換就可以得到逆後序遍歷序列,然後將逆後序遍歷序列逆序就得到了後序遍歷序列。因此需要兩個棧,一個棧stack1用來輔助做逆後序遍歷(將先序遍歷的左、右子樹遍歷順序交換的遍歷方式稱爲逆後序遍歷)並將遍歷結果序列壓入另一個棧stack2,然後將stack2中的元素全部出棧,所得到的序列即爲後序遍歷序列。具體過程如圖5所示。

二叉樹遍歷算法的改進(非遞歸實現)

二叉樹遍歷算法的改進(非遞歸實現)

初態棧空。

  1. 結點1如stack1。
  2. stack1元素出棧,並將出棧結點1入stack2,結點1的左、右孩子存在,左孩子結點2入stack1,右孩子結點4入stack1,這裏注意和先序遍歷進出棧過程對比,恰好是將其左、右孩子入棧順序調換,以實現訪問順序的調換。
  3. stack1元素出棧,並將出棧結點4入stack2,結點4的左、右孩子不存在。
  4. stack1元素出棧,並將出棧結點2入stack2,結點2的左、右孩子存在,左孩子結點3入stack1,右孩子結點5入stack1。
  5. stack1元素出棧,並將出棧結點5入stack2。
  6. stack1元素出棧,並將出棧結點3入stack2。

此時stack1空,stack2中元素自頂向下依次爲:3、5、2、4、1,正好爲後序遍歷序列。

代碼如下:

void postorderNonrecursion(BTNode *bt)
{
    if(bt != NULL)
    {
        /*定義兩個棧*/
        BTNode *Stack1[maxSize];int top1 = -1;
        BTNode *Stack2[maxSize];int top2 = -1;
        BTNode *p = NULL;
        Stack1[++top1] = bt;
        while(top1 != -1)
        {
            p = Stack1[top1--];
            Stack2[++top2] = p;//注意這裏和先序遍歷的區別,輸出改爲入Stack2
            /*注意下邊這兩個if語句和先序遍歷的區別,左、右孩子的入棧順序相反*/
            if(p->lchild != NULL)
                Stack1[++top1] = p->lchild;
            if(p->rchild != NULL)
                Stack1[++top1] = p->rchild;
        }
        while(top2 != -1)
        {
            /*出棧序列即爲後序遍歷序列*/
            p = Stack2[top2--];
            Visit(p);
        }
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章