做題目前隨便說點
-
樹是一種抽象數據類型,一種具有樹結構形式的數據集合。
-
節點個數確定,有層次關係。
-
有根節點。
-
除了根,每個節點有且只有一個父節點。
-
沒有環路。
-
所有數據結構都可以用鏈表表示或者用數組表示,樹也一樣。
589. N叉樹的前序遍歷
給定一個 N 叉樹,返回其節點值的前序遍歷。
例如,給定一個 3叉樹 :
1
| \
3 2 4
|
5 6
返回其前序遍歷: [1,3,5,6,2,4]。
說明: 遞歸法很簡單,你可以使用迭代法完成此題嗎?
來源:力扣(LeetCode)
鏈接:https://leetcode-cn.com/problems/n-ary-tree-preorder-traversal
著作權歸領釦網絡所有。商業轉載請聯繫官方授權,非商業轉載請註明出處。
解題:
審題:
代碼框架如下:
class Solution {
Node recursive(Node node) {
// 邊界判斷
if(...) {
return node;
}
// 先序遍歷node
// 遍歷子節點
for( Node son : node.children ) {
recursive(son);
}
// 後序遍歷node
// 返回值。
return node;
}
}
- 多叉樹沒有絕對意義上的中序遍歷。中序遍歷是二叉樹獨有的概念,除非你把n叉樹的分支分爲兩堆?這個看起來有點怪怪的。
開始解題:
/*
// Definition for a Node.
class Node {
public int val;
public List<Node> children;
public Node() {}
public Node(int _val) {
val = _val;
}
public Node(int _val, List<Node> _children) {
val = _val;
children = _children;
}
};
*/
class Solution {
public static List<Integer> list;
Node recursive(Node node) {
// 邊界判斷
if(node == null) {
return node;
}
list.add(node.val);
// 遍歷子節點
for( Node son : node.children ) {
recursive(son);
}
// 返回值。
return node;
}
public List<Integer> preorder(Node root) {
list = new ArrayList();
recursive(root);
return list;
}
}
總結:
迭代法(模擬計算機的遞歸函數執行過程):
- 計算機的本質就是一個while循環,然後不停的執行函數指令。
- 我們可以定義一個函數體,然後將root節點作爲函數的參數構造出函數體,交給一個while循環執行。
- 其實我們知道一個計算機處理器,除了一個while循環,還是一個函數棧stack;
- 其他與遞歸有關的細節我們可以耦合到函數體裏面去(畢竟我們又不是寫虛擬機對吧,不需要做到這個高級的封裝。)
/*
// Definition for a Node.
class Node {
public int val;
public List<Node> children;
public Node() {}
public Node(int _val) {
val = _val;
}
public Node(int _val, List<Node> _children) {
val = _val;
children = _children;
}
};
*/
class Solution {
// 定義函數體,等價於一份遞歸代碼
class FunsBody {
Node arg;
public List<Node> otherCallArg;
int returnVal;
int programCounter;
public FunsBody(Node node) {
this.arg = node;
this.otherCallArg = node.children;
this.returnVal = node.val;
this.programCounter = node.children.size();
}
}
// 定數函數輸出接口
class SystemOut {
public List<Integer> list;
SystemOut() {
this.list = new ArrayList();
}
public void print(int returnVal) {
this.list.add(returnVal);
}
public List<Integer> getConsole() {
return this.list;
}
}
public List<Integer> postorder(Node root) {
SystemOut systemOut = new SystemOut();
Stack<FunsBody> funcStack = new Stack();
// 參數校驗
if( root == null ) {
return systemOut.getConsole();
}
// 將代碼指令放入函數棧
funcStack.push(new FunsBody(root));
// 執行函數
while(!funcStack.isEmpty()) {
FunsBody func = funcStack.pop();
if(func.programCounter == 0) {
// 由於這裏我們要模擬後序遍歷,所以要等所有指令執行完成,函數才能返回
systemOut.print(func.returnVal);
} else {
// 保護函數現場,將代碼指令放入函數棧
funcStack.push(func);
// 將新函數的代碼指令放入函數棧頂部等待執行
funcStack.push(new FunsBody(func.otherCallArg.get(func.otherCallArg.size() - func.programCounter)));
// 調整該函數的PC程序計數器。
func.programCounter--;
}
}
return systemOut.getConsole();
}
}
- 上面我們定義了一個函數體,函數體包含了現場PC程序計數器,以及參數、其他外部函數調用指令和返回值表達式。
- 還定義了一個函數棧和while循環,while會不停的遍歷函數棧中的函數體,如果函數沒有外部函數調用(程序計數器將要指向函數體結束位置時),就執行返回值表達式。
- 如果執行外部函數需要,先保存現場到函數棧,然後將外部函數體入棧頂,調整程序計數器計數器。
代碼框架如下:
/*
// Definition for a Node.
class Node {
public int val;
public List<Node> children;
public Node() {}
public Node(int _val) {
val = _val;
}
public Node(int _val, List<Node> _children) {
val = _val;
children = _children;
}
};
*/
class Solution {
// 定義函數體,等價於一份遞歸代碼
class FunsBody {
// 參數
Node arg;
// 外部函數調用
public List<Command> otherCall;
// 返回值表達式
int returnVal;
// 程序計數器
int programCounter;
}
public List<Integer> postorder(Node root) {
// 邊界判斷
if( ....) {
return systemOut;
}
// 開機調用main函數
// 將代碼指令放入函數棧,調用main入口函數
funcStack.push(new FunsBody(root));
// 執行函數
while(!funcStack.isEmpty()) {
FunsBody func = funcStack.pop();
// 程序指針指向完成,返回函數值
if(func.programCounter == 0) {
// 由於這裏我們要模擬後序遍歷,所以要等所有指令執行完成,函數才能返回
systemOut.print(func.returnVal);
} else {
// 否則執行外部函數
// 保護函數現場,將代碼指令放入函數棧
funcStack.push(func);
// 將新函數的代碼指令放入函數棧頂部等待執行
funcStack.push(func.nextOutFuncBody);
// 調整該函數的PC程序計數器。
func.programCounter--;
}
}
// 釋放計算機資源,然後關機
return ...;
}
}