資料結構丨N叉樹

vincent1997發表於2019-07-16

遍歷

N叉樹的遍歷

樹的遍歷

一棵二叉樹可以按照前序、中序、後序或者層序來進行遍歷。在這些遍歷方法中,前序遍歷、後序遍歷和層序遍歷同樣可以運用到N叉樹中。

回顧 - 二叉樹的遍歷

  1. 前序遍歷 - 首先訪問根節點,然後遍歷左子樹,最後遍歷右子樹;
  2. 中序遍歷 - 首先遍歷左子樹,然後訪問根節點,最後遍歷右子樹;
  3. 後序遍歷 - 首先遍歷左子樹,然後遍歷右子樹,最後訪問根節點;
  4. 層序遍歷 - 按照從左到右的順序,逐層遍歷各個節點。

請注意,N叉樹的中序遍歷沒有標準定義,中序遍歷只有在二叉樹中有明確的定義。儘管我們可以通過幾種不同的方法來定義N叉樹的中序遍歷,但是這些描述都不是特別貼切,並且在實踐中也不常用到,所以我們暫且跳過N叉樹中序遍歷的部分。

把上述關於二叉樹遍歷轉換為N叉樹遍歷,我們只需把如下表述:

遍歷左子樹... 遍歷右子樹...

變為:

對於每個子節點:
通過遞迴地呼叫遍歷函式來遍歷以該子節點為根的子樹

我們假設for迴圈將會按照各個節點在資料結構中的順序進行遍歷:通常按照從左到右的順序,如下所示。

N叉樹遍歷示例

我們用如圖所示的三叉樹來舉例說明:

img

1.前序遍歷

在N叉樹中,前序遍歷指先訪問根節點,然後逐個遍歷以其子節點為根的子樹。
例如,上述三叉樹的前序遍歷是: A->B->C->E->F->D->G.

2.後序遍歷

在N叉樹中,後序遍歷指前先逐個遍歷以根節點的子節點為根的子樹,最後訪問根節點。
例如,上述三叉樹的後序遍歷是: B->E->F->C->G->D->A.

3.層序遍歷

N叉樹的層序遍歷與二叉樹的一致。通常,當我們在樹中進行廣度優先搜尋時,我們將按層序的順序進行遍歷。
例如,上述三叉樹的層序遍歷是: A->B->C->D->E->F->G.

練習

接下來,我們將為你提供幾道與N叉樹相關的習題。

N-ary Tree Preorder Traversal

給定一個 N 叉樹,返回其節點值的前序遍歷

例如,給定一個 3叉樹 :

img

返回其前序遍歷: [1,3,5,6,2,4]

說明: 遞迴法很簡單,你可以使用迭代法完成此題嗎?

#include <iostream>
#include <vector>
#include <stack>

using namespace std;

class Node{
public: 
    int val;
    vector<Node*> children;

    Node(){}
    Node(int _val, vector<Node*>_children){
        val = _val;
        children = _children;
    }
};

/// Recursion
/// Time Complexity: O(n)
/// Space Complexity: O(h)
class SolutionA{
public: 
    vector<int> preorder(Node* root){
        vector<int> res;
        dfs(root, res);
        return res;
    }
private: 
    void dfs(Node* node, vector<int>& res){
        if(!node)
            return;
        res.push_back(node->val);
        for(Node* next: node->children)
            dfs(next, res);
    }
};

/// Non-Recursion
/// Using stack
/// Time Complexity: O(n)
/// Space Complexity: O(h)
class SolutionB{
public: 
    vector<int> preorder(Node* root){
        vector<int> res;
        if(!root)
            return res;
        stack<Node*> stack;
        stack.push(root);
        while(!stack.empty()){
            Node* cur = stack.top();
            stack.pop();

            res.push_back(cur->val);
            for(vector<Node*>::reverse_iterator iter = cur->children.rbegin();
                iter != cur->children.rend(); iter++)
                    stack.push(*iter);
        }
        return res;
    }
};

int main(){
    return 0;
}

N-ary Tree Postorder Traversal

給定一個 N 叉樹,返回其節點值的後序遍歷

例如,給定一個 3叉樹 :

img

返回其後序遍歷: [5,6,3,2,4,1].

說明: 遞迴法很簡單,你可以使用迭代法完成此題嗎?

#include <iostream>
#include <vector>
#include <stack>

using namespace std;

class Node{
public: 
    int val;
    vector<Node*> children;

    Node(){}
    Node(int _val, vector<Node*> _children){
        val = _val;
        children = _children;
    }
};
/// Recursion
/// Time Complexity: O(n)
/// Space Complexity: O(h)
class SolutionA{
public: 
    vector<int> postorder(Node* root){
        vector<int> res;
        dfs(root, res);
        return res;
    }
private: 
    void dfs(Node* node, vector<int>& res){
        if(!node)
            return;
        for(Node* next: node->children)
            dfs(next, res);
        res.push_back(node->val);
    }
};

/// Non-Recursion
/// Using stack
///
/// Time Complexity: O(n)
/// Space Complexity: O(h)
class SolutionB{
public: 
    vector<int> postorder(Node* root){
        vector<int> res;
        if(!root)
            return res;
        stack<Node*> stack;
        stack.push(root);
        while(!stack.empty()){
            Node* cur = stack.top();
            stack.pop();
            res.push_back(cur->val);
            for(Node* next: cur->children)
                stack.push(next);
        }
        reverse(res.begin(), res.end());
        return res;
    }
};

int main(){
    return 0;
}

N叉樹的層序遍歷

給定一個 N 叉樹,返回其節點值的層序遍歷。 (即從左到右,逐層遍歷)。

例如,給定一個 3叉樹 :

img

返回其層序遍歷:

[
     [1],
     [3,2,4],
     [5,6]
]

說明:

  1. 樹的深度不會超過 1000
  2. 樹的節點總數不會超過 5000
#include <iostream>
#include <vector>
#include <queue>

using namespace std;

class Node {
public:
    int val = NULL;
    vector<Node*> children;

    Node() {}

    Node(int _val, vector<Node*> _children) {
        val = _val;
        children = _children;
    }
};

/// BFS
/// Store step in the queue
///
/// Time Complexity: O(n)
/// Space Complexity: O(n)
class SolutionA{
public: 
    vector<vector<int>> levelOrder(Node* root){
        vector<vector<int>> res;
        if(!root)
            return res;

        queue<pair<Node*, int>> q;
        q.push(make_pair(root, 0));
        while(!q.empty()){
            Node* cur = q.front().first;
            int step = q.front().second;
            q.pop();

            if(step == res.size())
                res.push_back({cur->val});
            else 
                res[step].push_back(cur->val);
            
            for(Node* next: cur->children)
                q.push(make_pair(next, step + 1));
        }
        return res;
    }
};

int main(){
    return 0;
}

遞迴

N叉樹的經典遞迴解法

經典遞迴法

我們在之前的章節中講過如何運用遞迴法解決二叉樹問題。在這篇文章中,我們著重介紹如何將這個思想引入到N叉樹中。

在閱讀以下內容之前,請確保你已閱讀過 運用遞迴解決樹的問題 這篇文章。

  1. "自頂向下"的解決方案

"自頂向下"意味著在每個遞迴層次上,我們首先訪問節點以獲得一些值,然後在呼叫遞迴函式時,將這些值傳給其子節點。

一個典型的 "自頂向下" 函式 top_down(root, params) 的工作原理如下:

1. 對於 null 節點返回一個特定值
2. 如果有需要,對當前答案 answer 進行更新                         // answer <-- params
3. for each child node root.children[k]:
4.      ans[k] = top_down(root.children[k], new_params[k])  // new_params <-- root.val, params
5. 如果有需要,返回答案 answer                                 // answer <-- all ans[k]
  1. "自底向上"的解決方案

"自底向上" 意味著在每個遞迴層次上,我們首先為每個子節點遞迴地呼叫函式,然後根據返回值和根節點本身的值給出相應結果。

一個典型的 "自底向上" 函式 bottom_up(root) 的工作原理如下:

1.對於 null 節點返回一個特定值
2.for each child node root.children[k]:
3.    ans[k] = bottom_up(root.children[k]) // 為每個子節點遞迴地呼叫函式
4. 返回答案 answer                          // answer <- root.val, all ans[k]

Maximum Depth of N-ary Tree

給定一個 N 叉樹,找到其最大深度。

最大深度是指從根節點到最遠葉子節點的最長路徑上的節點總數。

例如,給定一個 3叉樹 :

img

我們應返回其最大深度,3。

說明:

  1. 樹的深度不會超過 1000
  2. 樹的節點總不會超過 5000
#include <iostream>
#include <vector>

using namespace std;

/// DFS
/// Time Complexity: O(n)
/// Space Complexity: O(n)

/// Definition for a Node.
class Node{
public: 
    int val;
    vector<Node*> children;

    Node(){}
    Node(int _val, vector<Node*> _children){
        val = _val;
        children = _children;
    }
};

class Solution{
public: 
    int maxDepth(Node* root){
        if(!root)
            return 0;
        
        int res = 1;
        for(Node* child: root->children)
            res = max(res, 1 + maxDepth(child));
        return res;
    }
};

int main(){
    return 0;
}

小結

這張卡旨在介紹N叉樹的基本思想。 實際上,二叉樹只是N叉樹的一種特殊形式,N叉樹相關問題的解決方案與二叉樹的解法十分相似。 因此,我們可以把在二叉樹中學到的知識擴充套件到N叉樹中。

我們提供了一些經典的N叉樹習題,以便進一步幫助你理解本章中N叉樹的概念。

相關文章