此篇文章我们将详细理解二叉树的遍历,想必大家跟我一样第一次学习二叉树非常头疼,它是怎么递归的呀?怎么搞也搞不明白,想将递归的过程画出来却越画越乱,不画却又不理解,因此借此篇文章谈谈我是怎么理解二叉树中的递归的
在写递归时我们可以遵循以下四个步骤:

前序遍历的核心:前序位置的代码在刚刚进入一个二叉树节点的时候执行;
https://leetcode.cn/problems/binary-tree-preorder-traversal/ 第一步确定递归函数的参数和返回值:
因为要打印前序遍历的节点值,因此第一个参数为 TreeNode root
每次打印之后我们都需要存储到List中,因此第二个参数为 List<Integer> result
返回void即可
public void preorder(TreeNode root,List<Integer> result)第二步将原问题化为子问题
一棵二叉树的前序遍历结果 = 根节点 + 左子树的前序遍历结果 + 右子树的前序遍历结果。
第三步确定非边界条件:
我们想打印整棵树的节点值,即打印根节点+左子树+右子树的节点值即可
//根节点
result.add(root.val);
//左
preorder(root.left, result);
//右
preorder(root.right, result);第四步确定边界条件:
当我们一直往里递时,总会有一个尽头,即节点为空时我们不再进行遍历打印,此时就开始归
if (root == null) {
return;
}整体代码:
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<Integer>();
preorder(root,result);
return result;
}
public void preorder(TreeNode root,List<Integer> result) {
//刚刚进入一个二叉树节点的时候执行;
if (root == null) {
return;
}
//根节点
result.add(root.val);
//左
preorder(root.left, result);
//右
preorder(root.right, result);
}
}中序遍历的核心:中序位置的代码在一个二叉树节点左子树都遍历完,即将开始遍历右子树的时候执行。 中序遍历OJ
https://leetcode.cn/problems/binary-tree-inorder-traversal/description/
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<Integer>();
preorder(root,result);
return result;
}
public void preorder(TreeNode root,List<Integer> result) {
if (root == null) {
return;
}
//左
preorder(root.left, result);
//根节点
result.add(root.val);
//右
preorder(root.right, result);
}
}后续遍历的核心:后序位置的代码在将要离开一个二叉树节点的时候执行; 后序遍历OJ
https://leetcode.cn/problems/binary-tree-postorder-traversal/
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<Integer>();
inorder(root,result);
return result;
}
public void inorder(TreeNode root,List<Integer> result) {
//后序位置的代码在将要离开一个二叉树节点的时候执行;
if(root == null) {
return;
}
//左
inorder(root.left,result);
//右
inorder(root.right,result);
//中
result.add(root.val);
}
}为什么可以用迭代法(非递归的方式)来实现二叉树的前后中序遍历呢? 递归的实现就是:每一次递归调用都会把函数的局部变量、参数值和返回地址等压入调用栈中,然后递归返回的时候,从栈顶弹出上一次递归的各项参数,所以这就是递归为什么可以返回上一层位置的原因。
充分利用栈的特点,前序遍历顺序:中-左-右,入栈顺序:中-右-左
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<>();
if (root == null){
return result;
}
Stack<TreeNode> stack = new Stack<>();
stack.push(root);
while (!stack.isEmpty()){
TreeNode node = stack.pop();
result.add(node.val);
if (node.right != null){
stack.push(node.right);
}
if (node.left != null){
stack.push(node.left);
}
}
return result;
}
}根左右,刚进入节点就执行
思路:因为前序遍历的顺序为根左右,我们定义一个cur变量从根节点出发,沿着左子树依次入栈并打印,直到cur为空时停止,紧接着弹出当前节点(相当于递归中归的操作,回退到上一个节点进行判断)并记录判断当前节点是否有右子树 遍历左子树的循环条件为cur != null,只要cur不为空我们就一直循环入栈并打印的操作
遍历右子树的循环条件是什么呢?当我们走完遍历左子树的操作想要紧接着判断右子树,而且我们不知道右子树下面是否还存在一颗完整的树或左子树,因此我们要将两个循环写在一个大循环下
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> list = new ArrayList<>();
TreeNode cur = root;
if (root == null){
return list;
}
Stack<TreeNode> stack = new Stack<>();
while(cur != null || !stack.isEmpty()) {
while(cur != null) {
stack.push(cur);
list.add(cur.val);
cur = cur.left;
}
TreeNode top = stack.pop();
cur = top.right;
}
return list;
}
}中序遍历顺序: 左-中-右 入栈顺序: 左-右
// 中序遍历顺序: 左-中-右 入栈顺序: 左-右
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<>();
if (root == null){
return result;
}
Stack<TreeNode> stack = new Stack<>();
TreeNode cur = root;
while (cur != null || !stack.isEmpty()){
if (cur != null){
stack.push(cur);
cur = cur.left;
}else{
cur = stack.pop();
result.add(cur.val);
cur = cur.right;
}
}
return result;
}
}左根右,左子树都遍历完再执行
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> list = new ArrayList<>();
TreeNode cur = root;
if (root == null){
return list;
}
Stack<TreeNode> stack = new Stack<>();
while(cur != null || !stack.isEmpty()) {
while(cur != null) {
stack.push(cur);
cur = cur.left;
}
TreeNode top = stack.pop();
list.add(top.val);
cur = top.right;
}
return list;
}
}后序遍历顺序 左-右-中 入栈顺序:中-左-右 出栈顺序:中-右-左, 最后翻转结果
// 后序遍历顺序 左-右-中 入栈顺序:中-左-右 出栈顺序:中-右-左, 最后翻转结果
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<>();
if (root == null){
return result;
}
Stack<TreeNode> stack = new Stack<>();
stack.push(root);
while (!stack.isEmpty()){
TreeNode node = stack.pop();
result.add(node.val);
if (node.left != null){
stack.push(node.left);
}
if (node.right != null){
stack.push(node.right);
}
}
Collections.reverse(result);
return result;
}
}左右根 将要离开一个二叉树节点的时候执行;
该方法需要在打印根节点前,标记根节点,防止重复打印; 当我们走else语句到达H时,进行下一次循环,cur != null,又将H进行了入栈,cur = cur.left,此时cur为空跳出while循环判断右子树,top为H,H的右子树为空,弹出H并打印,此时cur为空,没进入while循环,而top此时为D,D的右树不为空,又走了else将H入栈并判断造成了死循环,所以我们这里需要进行标记,打印过该节点就不再进行打印,直接判断上一个节点

class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<Integer>();
inorder(root,result);
return result;
}
public void inorder(TreeNode root,List<Integer> result) {
Stack<TreeNode> stack = new Stack<>();
TreeNode cur = root;
TreeNode prev = null;
while (cur != null || !stack.isEmpty()) {
while (cur != null) {
stack.push(cur);
cur = cur.left;
}
TreeNode top = stack.peek();
if (top.right == null || top.right == prev) {
//打印当前top 并且弹出
stack.pop();
result.add(top.val);
prev = top;
} else {
cur = top.right;
}
}
}
}层序遍历一个二叉树。就是从左到右一层一层的去遍历二叉树。这种遍历的方式和我们之前讲过的都不太一样。 需要借用一个辅助数据结构即队列来实现,队列先进先出,符合一层一层遍历的逻辑,而用栈先进后出适合模拟深度优先遍历也就是递归的逻辑。 思路:先将根节点入队,在出队的同时将当前节点的左右节点入队(需要判断左右节点是否存在)循环这一操作即可 二叉树层序遍历OJ
https://leetcode.cn/problems/binary-tree-level-order-traversal/description/
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> ret = new ArrayList<List<Integer>>();
if (root == null) {
return ret; // 如果根节点为空,直接返回空的二维列表
}
Queue<TreeNode> queue = new LinkedList<TreeNode>();
queue.offer(root); // 先将根节点放入队列
while (!queue.isEmpty()) {
List<Integer> list = new ArrayList<>();
int size = queue.size(); // 记录当前层的节点数
for (int i = 0; i < size; i++) {
TreeNode cur = queue.poll(); // 出队
// list.add(cur.val); // 将当前节点的值加入当前层的列表中
// 将当前节点的子节点(如果有的话)加入队列
if (cur.left != null) {
queue.offer(cur.left);
}
if (cur.right != null) {
queue.offer(cur.right);
}
}
// 将当前层的列表加入结果列表中
ret.add(list);
}
return ret;
}