2023.3.28 二叉树的前序遍历
方法一 递归法
实现递归,我们需要搞懂几个关键步骤
1.确定好递归方法的返回值和传入的参数
2.确定好终止条件
3.确定好执行的逻辑
对于二叉树的前序遍历而言,我们的逻辑还是比较简单的,就是先遍历当前节点的val值,随后进入左子节点,直到到达了叶节点后,再遍历右子节点。重复这个过程。
那么对应的终止条件就是,只要当前节点为null,我们就返回到上一个节点
返回值:题目要求使用List返回,所以我们直接给递归方法传入一个List,不需要返回值,直接设为void。因此,传入的参数应该为一个用于接收结果的List,和传入一个头结点root。
preorder(res,root);
return res;
}
//返回条件 void,直接放入List传值,传入参数,List,以及头结点
public void preorder(List<Integer> res,TreeNode root){
//终止条件:当前节点为null
if(root == null){
return;
}
//循环过程的逻辑:前序遍历就是先得到当前节点的值,在进入左子树,后进入右子树
res.add(root.val);
//进入当前节点的左子树
preorder(res,root.left);
//进入当前节点的右子树
preorder(res,root.right);
}
}
方法二 迭代法
迭代法的本质就是将递归的细节给实现出来。
要想使用迭代法,我们需要将节点给记录下来,因为需要我们返回上一个节点,因此,这里需要使用到栈这种先进后出的结构(递归的本质就是通过栈来实现的)。
//新建一个辅助节点方便我们进行遍历
TreeNode temp = root;
/*
遍历的逻辑,先将当前节点的值记录下来,随后进入左子树节点,直到左子树节点为空时,返回上一个节点,进入其右子树节点,再重复上面的步骤。显然退出循环的条件为我们储存节点的栈为空,代表所有节点遍历完毕,此时就可以退出循环.而当左子树节点为null时,我们就退出遍历左子树的循环,进入右子树。
*/
while(!stack.isEmpty() || temp != null){
//进入左子节点遍历的循环
while(temp != null){
res.add(temp.val);
//将当前节点入栈
stack.push(temp);
//更新节点
temp = temp.left;
}
//退出循环代表已经遍历到了叶节点,此时temp指向叶节点的左子节点,为null所以退出了循环,此时我们需要返回叶节点,让temp重新指向叶节点的右子节点,此时栈顶的元素就是temp的上一个节点:叶节点
temp = stack.pop().right;
}
return res;
}
}
2023.3.28 二叉树的中序遍历
方法一 递归法
与前序遍历几乎一样,区别在与执行遍历的逻辑有所不同,中序遍历就是先进行左子树的循环遍历,到达叶节点的下一个为null的左子树节点时,返回叶节点,记录值,再进入右子树节点,重复这个过程。
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
//创建一个List用于接收我们的结果
List<Integer> res = new ArrayList<>();
inorder(res,root);
return res;
}
//确定传入的参数和返回值,传入的参数为res用于接收我们的结果,传入头结点root用于递归的开始
public void inorder(List<Integer> res,TreeNode root){
//终止条件,当前节点为null
if(root == null){
return;
}
//中序遍历,先遍历左子树,直至叶节点,获取当前值,再遍历右子树
inorder(res,root.left);
res.add(root.val);
inorder(res,root.right);
}
}
方法二 迭代法
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
//创建一个List用于接收我们的结果
List<Integer> res = new ArrayList<>();
//创建一个stack栈用于接收我们的树节点
Deque<TreeNode> stack = new ArrayDeque<>();
//创建一个辅助指针帮助我们遍历
TreeNode temp = root;
/*
遍历逻辑与前序遍历几乎相同,不同的是记录节点值需要在左子树遍历完成之后进行,随后遍历右子树节点
*/
while(!stack.isEmpty() || temp != null){
//左子树节点的遍历
while(temp != null){
stack.push(temp);
temp = temp.left;
}
//退出循环代表左子树节点遍历到了最后一个节点的null左子节点,此时从栈中弹出该节点并记录当前val值,进入右子树节点。
temp = stack.pop();
res.add(temp.val);
temp = temp.right;
}
return res;
}
}
2023.3.28 二叉树的后序遍历
方法一 递归法
同样的,却别在于后序的定义就是最后遍历当前节点值。
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
postorder(res,root);
return res;
}
public void postorder(List<Integer> res,TreeNode root){
if(root == null){
return;
}
postorder(res,root.left);
postorder(res,root.right);
res.add(root.val);
}
}
方法二 迭代法
直观上,后续遍历的迭代法和我们的前序遍历和中序遍历的过程应该是差不多的,我们尝试根据前序遍历和中序遍历的思路写一下代码
错误代码:
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
Deque<TreeNode> stack = new ArrayDeque<>();
TreeNode temp = root;
while(!stack.isEmpty() || temp != null){
while(temp != null){
stack.push(temp);
temp = temp.left;
}
//退出循环后,代表对于左子树循环的遍历完成,接下来我们要进入右子树节点
temp = stack.pop();
temp = temp.right;
}
}
}
我们会发现,写到这里,不知道下一步该如何写了,因为我们需要判断什么时候这个节点遍历完毕,此时才能记录当前节点的值,也就是左子节点和右子节点都为null时,我们就要记录下该节点的值。
而前序遍历和后序遍历都是在右子节点循环遍历之前的,可以直接记录下来。对于后续节点来说,当左子树节点遍历完成后,我们不能读取栈顶的值,还得遍历右子树节点,但是此时栈顶的值会被pop掉,导致我们在后面无法记录其信息,如何解决?我们可以判断栈顶弹出节点的右子节点是否为null,如果不为null,说明需要继续遍历,此时重新将其入栈,如果为null,则将弹出节点的值记录下来。让temp等于下一个栈顶弹出的值。
但是这会导致另一个问题:当temp弹出下一个栈顶的值后,又会进入到我们的左子树循环判断中,但实际上我们已经判断过了,如何避免这种情况发生?
我的第一版错误代码:记录存在右节点的节点,令prev指向它,如果弹出栈时的节点等于prev,就不再继续遍历右节点。
Bug点:prev只有在有右节点是才会指向,因此导致prev上一个节点为左子节点时,会进入重复判断。
我们应该做的就是记录就是上一个节点的信息为prev节点,因为栈的原理就是先进后出。
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
Deque<TreeNode> stack = new ArrayDeque<>();
TreeNode temp = root;
//用于记录重复遍历节点的信息
TreeNode prev = null;
while(!stack.isEmpty() || temp != null){
while(temp != null){
stack.push(temp);
temp = temp.left;
}
//退出循环后,代表对于左子树循环的遍历完成,接下来我们要进入右子树节点
temp = stack.pop();
//如果当前节点的右子节点仍然为null,代表左右遍历完成,记录节点的值.
/*注意:这里记录节点值的信息还有一个条件,就是一个节点既有左子节点,也有右子节点,但是都遍历完成了,我们也需要记录下这个节点的信息。那么如何才能知道这个节点已经遍历完成了呢?当一个节点的右子节点为null时,代表这个节点遍历完成,我们将其标记为prev节点,此后,如果有节点的右节点为prev的节点,代表这个节点的左节点和右节点都已经遍历完成了,不再继续遍历,弹出栈的上一个节点即可。
*/
if(temp.right == null || prev == temp.right ){
res.add(temp.val);
//令当前节点即为prev节点,代表已经遍历过了,无需再遍历
prev = temp;
//令temp为null,这样就不会重复进入左循环判断,而是判断上一个节点的右子树循环,
temp = null;
}
else{//右子节点不为null,需要我们继续遍历,因此我们要将弹出的节点重新返回栈中
stack.push(temp);
temp = temp.right;
}
}
return res;
}
}