「这是我参与11月更文挑战的第3天,活动详情查看:2021最后一次更文挑战」
二叉树的遍历
二叉树的遍历主要有两种办法,一种是递归法,一种是迭代法,从理解的角度而言,我更喜欢递归的做法,更清晰明了。
递归法
最近在做二叉树相关的题目,发现用递归的方法比较多。我看力扣刷题指南里这样明确递归的写法
递归写法三要素
- 确定递归函数的参数和返回值:首先要明确哪些参数是需要在递归的过程里处理的,如果需要处理,就在递归函数里加上这个参数,还需要明确递归函数的返回值和返回类型
- 确定终止条件:如果不确定终止条件或者终止条件不对,系统会一直调用,内存栈必定溢出
- 确定单层递归的逻辑:确定每一层递归需要处理的信息
// 前序遍历递归法
var preorderTraversal = function(root) {
let res = [];
const dfs = function(root) {
if (root === null) return;
res.push(root.val);
dfs(root.left);
dfs(root.right);
}
dfs(root);
return res;
}
// 中序遍历递归法
var inorderTraversal = function(root) {
let res = [];
const dfs = function(root) {
if (root === null) return;
dfs(root.left);
res.push(root.val);
dfs(root.right);
}
dfs(root);
return res;
};
// 后序遍历递归法
var postorderTraversal = function(root) {
let res = [];
const dfs = function(root) {
if (root === null) return;
dfs(root.left);
dfs(root.right);
res.push(root.val);
}
dfs(root);
return res;
};
以前序遍历为例子来确定递归三要素,确定递归函数的参数和返回值:因为我们要获取节点信息然后依照前序遍历打印,所以我们需要传入当前处理节点作为参数,也不需要什么返回值。确定终止条件:当当前节点为空时停止遍历,本层递归结束、确定单层递归逻辑:前序遍历是中左右,所以在单层的逻辑中,先取中节点的值压入结果栈中,然后依次对左子节点和右子节点进行递归遍历
迭代法
在我看来在二叉树的遍历这个问题上,迭代的逻辑是要比递归复杂的,我根据刷题指南里也用迭代法写了一遍二叉树遍历,第一次使用的迭代法,中序遍历和前序,后序的风格很不一样。然后吸取了别人的经验,我学会了二叉树遍历的统一迭代法
导致中序遍历和前序,后序的风格不一样的关键原因是,使用栈的时候,无法解决访问节点和处理节点(将元素放入结果集)不一致的问题,统一迭代法的思路是:将要访问的节点放入栈中,要处理的节点也放入栈中,但是对其加上标记。采用的标记方法是:将处理的节点放入栈之后,紧接着放入一个空指针做标记
// 统一迭代法
// 前序遍历:中左右
// 压栈顺序:右左中
var preorderTraversal = function(root, res = []) {
const stack = [];
if (root) stack.push(root);
while(stack.length) {
const node = stack.pop();
if(!node) {
res.push(stack.pop().val);
continue;
}
if (node.right) stack.push(node.right); // 右
if (node.left) stack.push(node.left); // 左
stack.push(node); // 中
stack.push(null);
};
return res;
};
// 中序遍历:左中右
// 压栈顺序:右中左
var inorderTraversal = function(root, res = []) {
const stack = [];
if (root) stack.push(root);
while(stack.length) {
const node = stack.pop();
if(!node) {
res.push(stack.pop().val);
continue;
}
if (node.right) stack.push(node.right); // 右
stack.push(node); // 中
stack.push(null);
if (node.left) stack.push(node.left); // 左
};
return res;
};
// 后续遍历:左右中
// 压栈顺序:中右左
var postorderTraversal = function(root, res = []) {
const stack = [];
if (root) stack.push(root);
while(stack.length) {
const node = stack.pop();
if(!node) {
res.push(stack.pop().val);
continue;
}
stack.push(node); // 中
stack.push(null);
if (node.right) stack.push(node.right); // 右
if (node.left) stack.push(node.left); // 左
};
return res;
};