第六部分:深度优先搜索
144.二叉树的前序遍历(简单)
题目:给你二叉树的根节点 root
,返回它节点值的 前序 遍历。
示例 1:
输入:root = [1,null,2,3] 输出:[1,2,3]
第一种思路:
通过递归的方式实现前序遍历,对于每个节点,先记录当前节点的值,然后递归调用左子树和右子树的方法,这种方式直观且易于理解。
创建结果列表:
preorderTraversal
方法接收一个树的根节点(root
)作为参数,并创建一个List
类型的列表list
用于存储遍历结果。处理空树的情况:
方法首先检查传入的
root
是否为null
。如果是,说明树为空,直接返回空列表。访问当前根节点:
如果根节点不为空,首先将当前节点的值(
root.val
)添加到结果列表list
中。递归遍历左子树:
接着,调用
preorderTraversal(root.left)
方法递归遍历当前节点的左子树,并将返回的结果(即左子树的遍历结果)添加到list
中。这里使用了list.addAll()
将子树的值合并到主列表中。递归遍历右子树:
然后,类似地,调用
preorderTraversal(root.right)
方法递归遍历当前节点的右子树,并将结果添加到list
中。返回结果列表:
最后,返回合并后的结果列表
list
,该列表包含了整个二叉树的前序遍历结果。
// 二叉树节点的定义
public class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode() {
}
TreeNode(int val) {
this.val = val;
}
TreeNode(int val, TreeNode left, TreeNode right) {
this.val = val;
this.left = left;
this.right = right;
}
}
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> list = new ArrayList<>();
if (root == null) return list;
// 访问当前根节点
list.add(root.val);
// 递归遍历左子树并将结果添加到列表
list.addAll(preorderTraversal(root.left));
// 递归遍历右子树并将结果添加到列表
list.addAll(preorderTraversal(root.right));
return list; // 返回结果列表
}
}
第二种思路:
使用迭代的方式,本质上是在模拟递归,因为在递归的过程中使用了系统栈,所以在迭代的解法中常用
Stack
来模拟系统栈。!这里需要稍微注意的是Stack栈是先进后出(后进先出),所以先序是先进右子树,然后是左子树!
创建结果存储列表:
在
Solution
类中,定义了preorderTraversal
方法用于实现前序遍历。首先,创建一个List
列表list
用于存储遍历结果。处理空树的情况:
若输入的根节点(
root
)为空(null
),直接返回空列表,表示没有节点可供遍历。初始化栈结构:
创建一个
Stack
对象stack
,用以辅助实现树的遍历。将根节点压入栈中,开始遍历。循环遍历栈:
使用一个while循环,只要栈非空,就继续遍历。每次循环中,从栈中弹出一个节点(node):
将弹出的节点值(
node.val
)添加到结果列表list
中。处理子节点:
首先检查弹出节点的右子节点:
若右子节点不为空,将其压入栈中。
然后检查左子节点:
若左子节点不为空,也将其压入栈中。
由于栈是后进先出(LIFO)的特性,右子节点在栈中被压入后,会在左子节点之前被弹出和处理。
返回结果列表:
当栈空时,表示所有可遍历的节点都已被处理,最后返回结果列表
list
,其中包含了按照前序遍历顺序收集的所有节点值。
// 二叉树节点的定义
public class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode() {
}
TreeNode(int val) {
this.val = val;
}
TreeNode(int val, TreeNode left, TreeNode right) {
this.val = val;
this.left = left;
this.right = right;
}
}
class Solution {
// 前序遍历二叉树
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> list = new ArrayList<>(); // 用于存储遍历结果
// 如果根节点为空,直接返回空列表
if (root == null)
return list;
// 创建一个栈来辅助遍历
Stack<TreeNode> stack = new Stack<>();
stack.push(root); // 将根节点推入栈中
// 当栈不为空时,继续遍历
while (!stack.isEmpty()) {
TreeNode node = stack.pop(); // 弹出栈顶节点
list.add(node.val); // 将当前节点值添加到结果列表中
// 先推入右子节点再推入左子节点,确保左子节点先被处理
if (node.right != null)
stack.push(node.right);
if (node.left != null)
stack.push(node.left);
}
return list; // 返回结果列表
}
}
94.二叉树的中序遍历(简单)
题目:给定一个二叉树的根节点 root
,返回 它的 中序 遍历 。
示例 1:
输入:root = [1,null,2,3] 输出:[1,3,2]
第一种思路:
通过递归的方式实现中序遍历,对于每个节点,先递归调用左子树,然后记录当前节点的值,接着再递归调用右子树的方法,这种方式直观且易于理解。
中序遍历定义:
中序遍历的顺序是:先遍历左子树,再访问根节点,最后遍历右子树。
递归实现:
在
inorderTraversal
方法中,首先创建一个结果列表list
用以存放遍历的结果。如果当前传入的节点
root
为null
,说明树已经遍历完毕,直接返回空列表。通过递归调用
inorderTraversal(root.left)
遍历左子树,返回的结果添加到list
中。将当前节点的值
root.val
添加到list
。最后,递归调用
inorderTraversal(root.right)
遍历右子树,将结果添加到list
中。返回结果:最终返回包含中序遍历节点值的列表。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> list = new ArrayList<>(); // 创建一个列表用于存储遍历结果
if (root == null)
return list; // 如果当前节点为空,返回空列表
// 递归遍历左子树并将其结果添加到列表中
list.addAll(inorderTraversal(root.left));
// 将当前节点的值添加到列表中
list.add(root.val);
// 递归遍历右子树并将其结果添加到列表中
list.addAll(inorderTraversal(root.right));
return list; // 返回包含中序遍历结果的列表
}
}
第二种思路:
初始化:
创建一个空的结果列表
list
来存储遍历结果。创建一个栈
stack
用于辅助遍历。当前节点
current
初始化为树的根节点。循环遍历:
使用
while
循环检查当前节点是否为null
或栈是否为空。这确保我们在有节点可处理的情况下继续遍历。遍历左子树:
使用嵌套的
while
循环将所有左子节点依次推入栈中,直到current
为null
。这相当于深入到最左端节点。访问根节点:
当到达最左端节点后,弹出栈顶的节点(即当前节点),将其值添加到结果列表中。
遍历右子树:
将当前节点更新为其右子节点(
current = current.right
),并循环回到外层的while
继续进行中序遍历。结束条件:
当栈为空且没有更多的节点可供处理时,整个树的中序遍历完成,此时返回结果列表
list
。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
// 中序遍历二叉树
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> list = new ArrayList<>(); // 用于存储遍历结果
Stack<TreeNode> stack = new Stack<>();
TreeNode current = root; // 当前节点从根节点开始
// 当栈不为空或当前节点不为空时,继续遍历
while (current != null || !stack.isEmpty()) {
// 将当前节点的所有左子节点和当前节点(最底下)推入栈中
while (current != null) {
stack.push(current);
current = current.left;
}
// 弹出栈顶节点
current = stack.pop();
list.add(current.val); // 将当前节点值添加到结果列表中
// 现在访问节点的右子树
current = current.right;
}
return list; // 返回结果列表
}
}
145.二叉树的后序遍历(简单)
使用场景:后序遍历主要用于需要在访问节点之前处理其子节点的场合,如删除树、计算树的权重等。
题目:给你一棵二叉树的根节点 root
,返回其节点值的 后序遍历 。
示例 1:
输入:root = [1,null,2,3] 输出:[3,2,1]
第一种思路:
初始化结果列表:创建一个空的列表
list
,该列表用于存储遍历过程中遇到的节点值。检查节点是否为空:
如果
root
(当前节点)为null
,这说明在递归中某个节点没有子节点,此时直接返回空列表list
。这也是递归的终止条件。递归遍历左子树:
调用
postorderTraversal(root.left)
递归遍历左子树,返回结果并加入list
中。此时,遍历到左子树的所有节点,并将它们的值按照后序顺序添加到list
中。递归遍历右子树:
之后,调用
postorderTraversal(root.right)
递归遍历右子树,同样返回结果并加入list
中。此时,遍历到右子树的所有节点,并将它们的值按照后序顺序添加到list
中。添加当前节点的值:
最后,将当前节点的值
root.val
添加到list
中。根据后序遍历的规则,当前节点的值在所有子节点值之后添加。返回结果列表:
返回
list
,即当前子树的后序遍历结果。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
// 方法:后序遍历二叉树
public List<Integer> postorderTraversal(TreeNode root) {
// 创建一个空列表,用于存储遍历结果
List<Integer> list = new ArrayList<>();
// 如果根节点为空,直接返回空列表
if (root == null)
return list;
// 递归遍历左子树并将结果添加到列表中
list.addAll(postorderTraversal(root.left));
// 递归遍历右子树并将结果添加到列表中
list.addAll(postorderTraversal(root.right));
// 添加根节点的值到列表中,表示后序遍历的顺序
list.add(root.val);
// 返回包含后序遍历结果的列表
return list;
}
}
第二种思路:
栈的使用:使用栈来保存尚未访问的节点,以便在后续操作中能够逐个访问它们。这样可以模拟递归调用的过程。
初始遍历:
首先,从根节点开始,一直向左子树移动,并将路径上的所有节点压入栈中。这部分保证了我们可以在后续的操作中首先处理左子树。
处理栈顶节点:
一旦到达最左下的节点(
root
为null
),进入处理栈顶节点的阶段。获取栈顶节点(
peekNode),然后看看它的右子节点:
如果 当前节点的右子树是
null
,或者它的右子节点已经在之前的遍历中访问过(通过lastVisited
来判断),那么我们就可以访问这个节点(添加它的值到结果列表中)并将其从栈中弹出。否则,如果当前节点有右子节点且尚未访问,则将
root
指向该右子节点,以便接下来访问右子树。更新
lastVisited
:在访问完一个节点后,将其赋值给lastVisited
,以便在后续判断中使用。循环终止条件:循环继续,直到栈为空且当前节点为
null
,此时遍历完成。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> list = new ArrayList<>();
Stack<TreeNode> stack = new Stack<>();
TreeNode lastVisited = null; // 用于记录上一个访问的节点
while (root != null || !stack.isEmpty()) {
while (root != null) {
stack.push(root);
root = root.left; // 优先访问左子树
}
TreeNode peekNode = stack.peek(); // 获取栈顶节点
// 如果右子树为null或已经访问过
if (peekNode.right == null || peekNode.right == lastVisited) {
list.add(peekNode.val); // 添加当前节点的值
lastVisited = stack.pop(); // 将当前节点标记为已访问
} else {
root = peekNode.right; // 如果有右子树,访问右子树
}
}
return list;
}
}
迭代的后序遍历方法充分利用了栈的特性,模拟递归中的函数调用栈,确保在访问节点时能够保持后序遍历的顺序。通过
lastVisited
的引入,有效地管理了节点的访问状态,避免了重复访问的问题。