遍歷
N叉樹的遍歷
樹的遍歷
一棵二叉樹可以按照前序、中序、後序或者層序來進行遍歷。在這些遍歷方法中,前序遍歷、後序遍歷和層序遍歷同樣可以運用到N叉樹中。
回顧 - 二叉樹的遍歷
- 前序遍歷 - 首先訪問根節點,然後遍歷左子樹,最後遍歷右子樹;
- 中序遍歷 - 首先遍歷左子樹,然後訪問根節點,最後遍歷右子樹;
- 後序遍歷 - 首先遍歷左子樹,然後遍歷右子樹,最後訪問根節點;
- 層序遍歷 - 按照從左到右的順序,逐層遍歷各個節點。
請注意,N叉樹的中序遍歷沒有標準定義,中序遍歷只有在二叉樹中有明確的定義。儘管我們可以通過幾種不同的方法來定義N叉樹的中序遍歷,但是這些描述都不是特別貼切,並且在實踐中也不常用到,所以我們暫且跳過N叉樹中序遍歷的部分。
把上述關於二叉樹遍歷轉換為N叉樹遍歷,我們只需把如下表述:
遍歷左子樹... 遍歷右子樹...
變為:
對於每個子節點:
通過遞迴地呼叫遍歷函式來遍歷以該子節點為根的子樹
我們假設for迴圈將會按照各個節點在資料結構中的順序進行遍歷:通常按照從左到右的順序,如下所示。
N叉樹遍歷示例
我們用如圖所示的三叉樹來舉例說明:
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叉樹
:
返回其前序遍歷: [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叉樹
:
返回其後序遍歷: [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叉樹
:
返回其層序遍歷:
[
[1],
[3,2,4],
[5,6]
]
說明:
- 樹的深度不會超過
1000
。 - 樹的節點總數不會超過
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叉樹中。
在閱讀以下內容之前,請確保你已閱讀過 運用遞迴解決樹的問題 這篇文章。
- "自頂向下"的解決方案
"自頂向下"意味著在每個遞迴層次上,我們首先訪問節點以獲得一些值,然後在呼叫遞迴函式時,將這些值傳給其子節點。
一個典型的 "自頂向下" 函式 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]
- "自底向上"的解決方案
"自底向上" 意味著在每個遞迴層次上,我們首先為每個子節點遞迴地呼叫函式,然後根據返回值和根節點本身的值給出相應結果。
一個典型的 "自底向上" 函式 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叉樹
:
我們應返回其最大深度,3。
說明:
- 樹的深度不會超過
1000
。 - 樹的節點總不會超過
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叉樹
的概念。