递归遍历
首先我们要说明一下递归的三要素
- 确认返回值和传入值
- 确认终止条件
- 确认单层递归的逻辑
以二叉树来学习递归几乎是最好的学习递归方式;
接下来我们来仔细阐述一下在二叉树中的这三个要素
首先,要做到递归,第一步就是定义一个可以循环利用的函数,那么定义函数第一步除去起函数名就是确认传入条件,又因为是可循环的函数那么确认return返回条件也是必须的。
那么一力扣二叉树为例,递归要传入谁呢,毫无疑问就是根节点root。那么可以得到以下函数。
function dfs(root){}
那么接下来是否要确定返回条件了呢,需要注意的是,我们所说的返回条件不一定是return,而应该是收获胜利的果实。同样以力扣题为例他需要我们返回一个数组,那么明显我们可以返回一个全新的数组res
const res = []
这里需要注意的是关于返回结果,返回结果前必须要验证元素非空也就是不能==null
即
if(root===null)return ;
这里碰巧的是验证非空就是关键第二步:确认终止条件,一旦该节点为空说明他的父节点没有左节点或者右节点(是左还是右就取决于这个节点是左还是右嘛)
接下来我们来到了最后一步:确认单层递归的逻辑
我们可以想一想,当我们把根节点放入函数时,它验证不为空,然后其val放入res数组当做结果(以前序遍历为例)。接下来它的左右节点也是完整的节点,也可以当做root节点来处理,所以此时只需要把他们两放入函数中就行。
dfs(root.left)
dfs(root.right)
完整代码(以前序遍历为例)
var preorderTraversal = function(root) {
const res = []//1定义返回结果
function dfs(root){//1确认传入参数
if(root==null) return //2确定终止条件
res.push(root.val)//1确认返回结果
dfs(root.left)//3确认单层递归的逻辑
dfs(root.right)//3确认单层递归的逻辑
}
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;
};
迭代遍历
首先说明一下,大家看递归遍历可能会觉得前中后序遍历只是放入根节点的val的顺序区别;其实并不是这样的。关于前序遍历它是先放入根节点val的值没错,但中序和后序遍历并不是如此,这两个与前序遍历不同的是,他们是需要先像遍历链表一样来遍历最远端的左节点的。然后在进行收获胜利果实。而这在递归遍历代码中是很难体现出来的。而两种截然不一样的方式也就造就不同迭代代码结构。
迭代前序遍历
- 定义一个栈,用来模拟递归函数的先进后出
- 不断进行入栈以及出栈,直到栈为空
也许大家对第一点比较能够理解,但是第二点什么叫不断进行出入栈呢。
其实就是分别把根节点右节点左节点依次入栈,为什么是先右后左这就跟栈的特性相关,尤为入栈和出栈顺序相反嘛。继续说回来,其实完全可以把根右左作为循环一直循环下去,直到栈空。
完整代码:
var preorderTraversal = function(root) {
const res = []
if(!root) return
let stack = [root]
let cur = null
while(stack.length){
cur = stack.pop()
res.push(cur.val)
cur.left && stack.push(cur.left)
cur.right && stack.push(cur.right)
}
return res
}
中序遍历
- 定义一个指针,遍历到最后一个
- 再在栈里弹出
完整代码:
var inorderTraversal = function(root) {
if(!root) return
let stack = []
let res = []
let cur = root
while(cur || stack.length){
if(cur){
stack.push(cur)
cur=cur.left
}else{
cur = stac.pop()
res.push(cur.val)
cur=cur.right
}
}
return res
};
后序遍历可以使用一个巧方法
改变前序遍历的顺序在倒序输出
层序遍历
关于层序遍历,因为是从左到右,可以使用队列来解决,那么如何符分层级是最终问题。
首先,可以定义一个变量来记录正在遍历层的节点数量,如何记录正确呢,可是队列将要遍历时保证队列中只有这一层。
完整代码:
var levelOrder = function(root) {
let res =[]//所有层结果
let queue=[root]
if(!root) return res
while(queue.length){
let len = queue.length//记录这次遍历层数
let arr =[]//一层的结果数组
while(len--){//开始遍历该层
let node = queue.shift()//不断根据len长度弹出
arr.push(node.val)
node.left && queue.push(node.left)
node.right && queue.push(node.right)
}
res.push(arr)
}
};