二叉樹的遍歷(先序遍歷,中序遍歷,後序遍歷,層次遍歷)

二叉樹簡介

維基百科對二叉樹的定義:二叉樹(英語:Binary tree)是每個節點最多隻有兩個分支(不存在分支度大於2的節點)的樹結構。通常分支被稱作“左子樹”和“右子樹”。二叉樹的分支具有左右次序,不能顛倒。
二叉樹的遍歷有4種方式,先序遍歷,中序遍歷,後序遍歷,層次遍歷,前三者均屬於深度優先遍歷,先序、中序、後序指的是遍歷時根節點相對於左右孩子的次序,先序遍歷的順序爲根節點->左子樹->右子樹,中序遍歷的順序爲左子樹->根節點->右子樹,後序遍歷的順序爲左子樹->右子樹->根節點,遍歷時對每個子樹採用同樣的順序遍歷。層次遍歷屬於廣度優先遍歷,順序爲第一層->第二層->第三層->…,每層都按照從左到右的順序遍歷。其中三種深度優先遍歷均有遞歸和非遞歸兩種實現方式。
二叉樹
上圖四種遍歷方式的序列分別爲:
先序遍歷:2, 7, 2, 6, 5, 11, 5, 9, 4
中序遍歷:2, 7, 5, 6, 11, 2, 5, 4, 9
後序遍歷:2, 5, 11, 6, 7, 4, 9, 5, 2
層次遍歷:2, 7, 5, 2, 6, 9, 5, 11, 4

1. 先序遍歷

遍歷順序爲:根節點->左子樹->右子樹,分爲遞歸和非遞歸兩種方式

遞歸算法

思路

先讀取根節點的值,然後遞歸調用遍歷函數分別遍歷左子樹和右子樹。

僞代碼

PRE_ORDER_RECURSIVE_TRAVERSE(root)
    if(root == null)
        return
    visit(root) //訪問根節點
    PREORDER_RECURSIVE_TRAVERSE(root.left)
    PREORDER_RECURSIVE_TRAVERSE(root.right)

C++實現

void preOrderRecursiveTraversal(TreeNode *root, vector<int> &sequence)
{
    if(root == null)
    {
        return;
    }
    sequence.push_back(root->val);
    preOrderRecursiveTraversal(root->left, sequence);
    preOrderRecursiveTraversal(root->right, sequence);
}

非遞歸算法

思路

使用棧來存儲節點,每次打印節點後,右孩子先入棧、左孩子後入棧,這樣左孩子就會先出棧,並且打印左孩子時,左孩子的孩子會繼續入棧,這樣就會先把子樹訪問完畢,纔會出棧右孩子。

僞代碼

PRE_ORDER_NONRECURSIVE_TRAVERSE(root)
    if(root == null)
        return
    stack.push(root)
    while(stack not empty)
        node = stack.pop()
        visit(node)
        stack.push(node.right) //先入棧右孩子,右孩子會後出棧
        stack.push(node.left)

C++實現

vector<int> preOrderTraversal(TreeNode *root)
{
    vector<int> sequence;
    if(root == nullptr)
    {
        return sequence;
    }
    stack<TreeNode *> stackIn;
    stackIn.push(root);
    while(!stackIn.empty())
    {
        TreeNode *curNode = stackIn.top();
        stackIn.pop();
        sequence.push_back(curNode->val);
        if(curNode->right)
        {
            stackIn.push(curNode->right);
        }
        if(curNode->left)
        {
            stackIn.push(curNode->left);
        }
    }
    return sequence;
}

2. 中序遍歷

遍歷順序爲左子樹->根節點->右子樹,分爲遞歸和非遞歸兩種方式

遞歸算法

思路

每次遞歸:先遞歸遍歷左子樹,再打印根節點,最後遞歸遍歷右子樹

僞代碼

IN_ORDER_NONRECURSIVE_TRAVERSE(root)
    if(root == null)
        return
    PREORDER_NONRECURSIVE_TRAVERSE(root.left)
    visit(root)
    PREORDER_NONRECURSIVE_TRAVERSE(root->right)

C++實現

void inOrderRecursiveTraversal(TreeNode *root, vector<int> &sequence)
{
    if(root == nullptr)
    {
        return;
    }
    inOrderRecursiveTraversal(root->left, sequence);
    sequence.push_back(root->val);
    inOrderRecursiveTraversal(root->right, sequence);
}

非遞歸算法

思路

遍歷時,根先入棧,然後一直迭代地訪問左孩子,併入棧,直到左孩子爲空。此時出棧,打印該節點,該節點的左孩子一定爲空(否則前面步驟匯會繼續往下走),如果有右孩子,右孩子入棧,進入下次遍歷操作。(如果右孩子無左子樹,即右孩子的左孩子爲空,下次就會打印這個節點,反之會入棧其左子樹)

僞代碼

IN_ORDER_NONRECURSIVE_TRAVERSE(root)
    curNode = root
    while(curNode != null && stack is not empty)
        while(curNode != null) //先把左孩子都入棧
            stack.push(curNode)
            curNode = curNode.next
        curNode = stack.pop()
        visit(curNode)
        curNode = curNode->right //左孩子訪問完了,訪問右孩子

C++實現

vector<int> inOrderTraversal(TreeNode *root)
{
    vector<int> sequence;
    stack<TreeNode *> stackIn;
    TreeNode *curNode = root;
    while(curNode || !stackIn.empty())
    {
        while(curNode)
        {
            stackIn.push(curNode);
            curNode = curNode->left;
        }
        curNode = stackIn.top();
        stackIn.pop();
        sequence.push_back(curNode->val);
        curNode = curNode->right;
    }
    return sequence;
}

3. 後序遍歷

遍歷順序爲左子樹->右子樹->根節點,分爲遞歸和非遞歸兩種方式

遞歸算法

思路

先分別遞歸遍歷左子樹和右子樹,然後打印根節點

僞代碼

POST_ORDER_NONRECURSIVE_TRAVERSE(root)
    if(root == null)
        return
    PREORDER_NONRECURSIVE_TRAVERSE(root.left)
    PREORDER_NONRECURSIVE_TRAVERSE(root->right)
    visit(root)

C++實現

void postOrderRecursiveTraversal(TreeNode *root, vector<int> &sequence)
{
    if(root == nullptr)
    {
        return;
    }
    postOrderRecursiveTraversal(root->left, sequence);
    postOrderRecursiveTraversal(root->right, sequence);
    sequence.push_back(root->val);
}

非遞歸算法

思路

使用棧來存儲節點,對每個節點,如果沒有左右孩子了,那麼打印該節點,如果有孩子,孩子入棧(先右孩子後左孩子,這樣左孩子會先出棧)。這種方式面臨一個問題,一個節點的左右子樹都遍歷結束後,會再次訪問該節點,這個時候是應該入棧該節點的孩子呢,還是出棧打印該節點呢。無法判斷,所以這個時候需要使用一個數據結構(unordered_set)來記錄該節點的左右孩子是否已經被訪問過了,即入棧了。

僞代碼

POST_ORDER_NONRECURSIVE_TRAVERSE(root)
    if(root == null)
        return
    stack.push(root)
    while(stack is not empty)
        curNode = stack.top()
        if(curNode has no child or curNode's child has visited)
            visit(curNode)
            stack.pop()
        else
            if(curNode.right != null)
                stack.push(curNode.right)
            if(curNode.left != null)
                stack.push(curNode.left)
            visitedNode.add(curNode) //將curNode標記爲已訪問

C++實現

vector<int> postOrderTraversal(TreeNode *root)
{
    vector<int> sequence;
    if(!root)
    {
        return sequence;
    }
    stack<TreeNode *> stackIn;
    stackIn.push(root);
    set<TreeNode *> traversed;
    while(!stackIn.empty())
    {
        TreeNode *curNode = stackIn.top();
        if(traversed.find(curNode) != traversed.end() || !curNode->left && !curNode->right)
        {
            sequence.push_back(curNode->val);
            stackIn.pop();
            continue;
        }
        if(curNode->right)
        {
            stackIn.push(curNode->right);
        }
        if(curNode->left)
        {
            stackIn.push(curNode->left);
        }
        traversed.insert(curNode);
    }
    return sequence;
}

4. 層次遍歷

思路

使用隊列來按次序保存節點,每次出隊列,打印該節點,然後將其左、右孩子分別入棧。

僞代碼

LAYER_TRAVERSAL(TreeNode* root)
    if(root == null)
        return
    queue.push(root)
    while(queue is not empty)
        curNode = queue.pop()
        visite(curNode)
        if(curNode->left != null)
            queue.push(curNode->left)
        if(curNode->right != null)
            quque.push(curNode->right)

C++實現

vector<int> layerTraversal(TreeNode *root)
{
    vector<int> sequence;
    if(root == nullptr)
    {
        return sequence;
    }
    deque<TreeNode *> que;
    que.push_back(root);
    while(!que.empty())
    {
        TreeNode *curNode = que.front();
        que.pop_front();
        sequence.push_back(curNode->val);
        if(curNode->left != nullptr)
        {
            que.push_back(curNode->left);
        }
        if(curNode->right != nullptr)
        {
            que.push_back(curNode->right);
        }
    }
    return sequence;
}

THE END

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章