二叉树的遍历问题-迭代和递归

1. 递归

递归问题可以总结成递归三部曲,即递归函数的构建,递归终止条件的确定和单次递归函数的确立这三个方面;完成了这三方面的工作后,相关问题递归算法的实现会容易许多。下面递归来实现用二叉树的前序遍历:

var preorderTraversal = function (root) {
const result = [];
// 1 递归函数的确立,主要考虑函数的参数;
const preTree = function (root) {
// 2 递归的终止条件
if (!root) {
return null;
}
result.push(root.val);
// 3 单次递归函数
preTree(root.left);
preTree(root.right);
};
preTree(root);
return result;
};

在二叉树的递归函数返回值处理中,如果递归函数有返回值,如何区分要搜索一条边,还是搜索整个树呢?详细解释看这里
搜索一条边的写法:

if (递归函数(root->left)) return ;
if (递归函数(root->right)) return ;

搜索整个树写法:

left = 递归函数(root->left);
right = 递归函数(root->right);
left与right的逻辑处理;

二叉树的遍历如果采用递归方法的话相对来说比较简单,逻辑比较清晰。下面着重介绍二叉树的迭代方法

2. 迭代

递归的实现就是每一次递归调用都会将函数的局部变量、参数和返回地址等压入调用的栈中,然后等递归返回的时候从栈中弹出各项参数。因此我们可以直接用栈来实现二叉树的遍历。这里总结的迭代方法,二叉树前中后序上比较通用易理解的方法。

前序 法1:先序节点的访问顺序是中左右,这个方法里是直接顺序记录的,按照中左右的顺序遍历出二叉树节点;

var preOrderTraversal = function(root) { 
const stack=[],result=[]; // !!!务必注意stack为[],不用先入栈,在判断里使用节点判断,先根节点入栈while可以只用栈长度判断
let cur=root; // 指针,其实前序去掉cur
while(stack.length||cur){ // 前序条件只有stack length存在就行(前提是stack里提前入根节点),中序不能这样,还是推荐和中序一样的方式;
if(cur){ //入栈
result.push(cur.val); // 中
stack.push(cur);
cur=cur.left; //左
}else{
cur=stack.pop();
cur=cur.right; // 右
}
}
return result;
};

前序 法2:这个方法里前序前序中的左右在入栈的时候顺序应该调换一下,变成中右左,因为栈先入先出,记录的结果就为左右

var preOrderTraversal = function (root) {
const stack = [], result = [];
if (root) { // 注意判断二叉树是否为空
stack.push(root)
}
while (stack.length) {
const node = stack.pop(); //取出当前的节点
result.push(node.val); // 中 记录
if (node.right) { //右边 注意入栈的话先右后左,因为前序访问时中左右,栈先入先出,入栈就又左了
stack.push(node.right);
}
if (node.left) { //左边存在的话,入栈
stack.push(node.left);
}

}
return result;
};

中序: 上述两个前序的方法,第一种稍加更改可以很方便的用来实现中序遍历,法1无需考虑进出栈顺序,中序读取顺序是左中右

var inOrderTraversal = function (root) {
const stack = [],
results = [];
while (stack.length || root) {
if (root) {
stack.push(root);
root = root.left; //左
} else {
root = stack.pop();
results.push(root.val); //中
root = root.right; //右
}
}
return results;
};

后序:后序遍历和前序的法2类似,后序遍历是左右中,前序的法2中出栈结果是中左右,为前序遍历结果,将法2的左右顺序调换,出栈结果为中右左,然后将结果数组逆序,得到后序遍历结果,代码如下:

var postorderTraversal = function (root) {
const stack = [], results = [];
if (root) {
stack.push(root);
}
while (stack.length) {
const node = stack.pop();
results.push(node.val); // 中
if (node.left) { // 左
stack.push(node.left)
}
if (node.right) { // 右
stack.push(node.right)

}
}
return results.reverse(); //将出栈后得到的中右左逆序,为后序遍历结果,左右中
};

总结:前序两种方法要深入理解,两种方法分别对应着中序和后序的迭代遍历实现,这是我目前了解到的比较简单的方法。

3. 层序遍历

二叉树的层序遍历主要是借助队列来实现,队列中每次保存的都是当前一层的信息,原理就不过多的赘述了,代码如下:

var levelOrder = function (root) {
if (!root) { // 防止空树
return []
}
const queue = [root], results = []; // 根节点入队列
while (queue.length) { // 队列不为空时
let level = [], size = queue.length; // 当前一层、queue length 表示当前一层长度
while (size--) { // 当前一层存在节点的时候
const node = queue.shift(); // 取出节点
level.push(node.val); // 值存到遍历数组里
if (node.left) { // 左存在
queue.push(node.left); //当作下一层入队
}
if (node.right) { // 同理
queue.push(node.right);
}
}
results.push(level); // 当前一层遍历结束,存到结果数组里
}
return results;
};

提示:求二叉树的深度等问题用层序很容易解决

4. 判断相同的二叉树/对称

判断二叉树对称/相同这里是借助队列来实现,(类似层序)队列中每次保存的都是当前一层的信息,判断相同的话将左右孩子入队顺序调换就可以了,也有在此基础上判断是否子数的,但是我觉得有些复杂,判断子树不如两个树的前序节点值前后加标识,空子树用另外替换,然后include判断的方法,代码和解释如下:

var isSymmetric = function(root) {
// 迭代
if(root===null){
return true
}
let queue = []
queue.push(root.left)
queue.push(root.right)
while(queue.length){
let leftNode = queue.shift()
let rightNode = queue.shift()
//判断一共几种情况,当都是null不能简单的返回true,还需要再继续判断
if(leftNode===null&&rightNode===null){
continue
}
//false情况,左右结点均空上面已经处理了
if(leftNode===null||rightNode===null||leftNode.val!==rightNode.val){
return false
}
queue.push(leftNode.left) //左节点左孩子入队
queue.push(rightNode.right) //右节点右孩子
queue.push(leftNode.right) //左节点右孩子
queue.push(rightNode.left) //右节点左孩子
}
return true //为空未返回false后 就是剩下的val都相等情况
};
文章作者: 鐔雨
文章链接: https://caichunyu.github.io/2022/04/20/二叉树的遍历/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 鐔雨的Blog