做题目前随便说点
-
树是一种抽象数据类型,一种具有树结构形式的数据集合。
-
节点个数确定,有层次关系。
-
有根节点。
-
除了根,每个节点有且只有一个父节点。
-
没有环路。
-
所有数据结构都可以用链表表示或者用数组表示,树也一样。
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 ...;
}
}