1. 程式人生 > >二叉樹遍歷:前序,中序,後序,層序的遞迴以及非遞迴實現

二叉樹遍歷:前序,中序,後序,層序的遞迴以及非遞迴實現

樹,是一種在實際程式設計中經常遇到的資料結構,它的邏輯很簡單:除根節點之外每個節點都有且只有一個父節點,除葉子節點之外所有節點都有一個或多個子節點。我們說的二叉樹,就是指子節點最多2個的樹。

二叉樹中,最重要的操作就是遍歷。二叉樹的遍歷分為:

1.前序遍歷:先訪問根節點,再訪問左子節點,最後訪問右子節點。

2.中序遍歷:先訪問左子節點,再訪問根節點,最後訪問右子節點。

3.後序遍歷:先訪問左子節點,再訪問右子節點,最後訪問根節點。

4.層序遍歷:也就是我們說的“廣度優先”或者“寬度優先”遍歷。先訪問第一層節點,再訪問第二層...直到最後一層。每一層的訪問順序都是從左到右。

在本文中,將寫出二叉樹的前中後序遍歷的遞迴實現以及非遞迴實現,還有層序遍歷的實現。

首先,我們給出二叉樹的定義。

typedef struct BinaryTree
{
    int _data;
    struct BinaryTree * _lchild;
    struct BinaryTree * _rchild;
}Tree,*pTree;

可以看到,一個樹中節點結構包括值域,左孩子節點和右孩子節點。葉子節點的左右孩子為空。

然後是三種遍歷的遞迴實現。

  • 前序遍歷的遞迴實現
void PreOrderRecursion(pTree node)
{
    if(node == nullptr)    return;
    cout<<node->_data<<" ";
    PreOrderRecursion(node->_lchild);
    PreOrderRecursion(node->_rchild);
}

從根節點開始,先打印出根節點的值,再遞迴左子樹,最後遞迴右子樹。

中序、後序遍歷的思想和前序一樣,只需要調整列印的順序即可,所以下面不再贅述。

void InOrderRecursion(pTree node)
{
    if(node == nullptr)    return;
    InOrderRecursion(node->_lchild);
    cout << node->_data<<" ";
    InOrderRecursion(node->_rchild);
}

void PostOrderRecursion(pTree node)
{
    if(node == nullptr)    return;
    PostOrderRecursion(node->_lchild);
    PostOrderRecursion(node->_rchild);
    cout << node->_data<<" ";
}

接下來是三種遍歷的非遞迴實現。我們說到其實遞迴的本質就是棧,既然不使用遞迴,那麼我們就要用到棧。

其中,前序遍歷和中序遍歷的思想也十分相似,只是後序遍歷有些許不同。

此處前序遍歷的非遞迴有兩種實現,思想有些許不同。

前序遍歷的非遞迴實現方法1:

思想:使用棧,首先判斷輸入合法性。隨後將頭節點首先入棧 ,保證棧中開始至少有一個元素。使用一個while迴圈,只要棧還非空就一直進行。迴圈體中首先獲取棧頂節點,將其列印後直接出棧,並將其的右孩子和左孩子依次入棧(如果存在的話)。根據棧的FILO特性,最後入棧的左孩子將在下一輪中成為棧頂元素。這樣就能滿足前序遍歷的特點。

void PreOrderIteration1(pTree node)
{
    if(node == nullptr)    return;
    stack<pTree> s;
    pTree p = node;
    s.push(p);
    
    while(!s.empty())
    {
        p = s.top();
        cout<<p->_data<<" ";
        s.pop();

        if(p->_rchild)    s.push(p->_rchild);
        if(p->_lchild)    s.push(p->_lchild);
    }
}

前序遍歷非遞迴實現方法2:

思想:迴圈開始,從棧頂節點(除第一次是根節點)開始不斷左探,入棧並列印,直到左孩子為空;然後指標指向棧頂元素的右孩子,開啟下一輪迴圈。

void PreOrderIteration2(pTree node)
{
    stack<pTree> s;
    pTree p = node;

    while(!s.empty() || p != nullptr)
    {
        while(p != nullptr)            //不斷左探的過程
        {
            cout<< p->_data << " ";
            s.push(p);
            p = p->_lchild;
        }

        if(!s.empty())                //最後訪問右孩子
        {
            p = s.top();
            s.pop();
            p = p->_rchild;
        }
    }
}

總地來說,還是第一種方法更好理解也便於記憶。

中序遍歷非遞迴實現:

思想:與前序遍歷非遞迴方法2的思想相同,只是不在不斷左探的時候列印,而是在出棧的時候列印。

void InOrderIteration(pTree node)
{
    if (node == nullptr)     return;
    pTree p = node;
    stack<pTree> s;
    while(!s.empty() || p != nullptr)
    {
        while(p != nullptr)
        {
            s.push(p);
            p = p->_lchild;
        }
        if(!s.empty())
        {
            p = s.top();
            cout << p->_data <<" ";
            s.pop();
            p = p->_rchild;
        }
    }
}

後序遍歷非遞迴實現:

思想:相對於前序和中序,後續遍歷的實現就稍顯麻煩。我們要保證一個節點要在左孩子和右孩子之後才能訪問,那麼有下面三種情況:

1.它是葉子節點,沒有左右孩子。可以直接訪問。

2.它有左右孩子,但是左右孩子都已經被訪問過,也可以直接訪問該節點。

3.有左右孩子,且左右孩子沒有訪問過。此時要先入棧右孩子,再入棧左孩子。

為了確認一個節點的左右孩子是否被訪問過,我們就要定義一個pPre指標。訪問過一個節點後,就將pPre指向它,在下一輪判斷就可以通過p->lchild || p->rchild  == pPre來作為子節點是否被訪問過的條件了。

void PostOrderIteration(pTree node)
{
    pTree pCur = node;        //用來儲存當前節點的指標
    pTree pPre = nullptr;     //儲存上一個訪問的節點的指標
    stack<pTree> s;
    s.push(pCur);
    
    while(s.!empty())
    {
        pCur = s.top();
        if((pCur->_lchild == nullptr && pCur->_rchild == nullptr) ||              //沒有左右孩子的情況
           (pPre != nullptr && (pPre == pCur->_lchild || pPre == pCur->_rchild))) //左右孩子訪問過的情況
        {
            cout << pCur->_data << " ";
            s.pop();
            pPre = pCur;
        }
        else
        {
            if(pCur->_rchild != nullptr)    s.push(pCur->_rchild);
            if(pCur->_lchild != nullptr)    s.push(pCur->_lchild);
        }
    }
}

最後是層序遍歷。和其他三種遍歷不同的是,層序遍歷使用佇列來實現。先進先出也很符合層序遍歷的特點。

void LevelOrder(pTree node)
{
    pTree p = node;
    if(node == nullptr)    return;
    queue<pTree> q;
    for(q.push(p);q.size();q.pop())        //只要佇列不空就一直出隊
    {
        p = q.front();
        cout << p->_data <<" ";
        if(p->_lchild)    q.push(p->_lchild);
        if(p->_rchild)    q.push(p->_rchild);
    }
}