在本文的最开始,首先介绍一下二叉树的解题思路。所有的二叉树解题思路,仅分为两种:
但无论使用哪种思路,都需要明确对于当前正在处理的二叉树节点,它需要「做什么」「什么时候做」。至于其他的节点,都可以通过「递归」的方法执行相同的操作
我们可能存在一种固有思维,把「前中后」三种遍历理解为三种不同的顺序。其实这种理解仅仅是在第一层而已
前序遍历:刚刚进入一个节点的时候执行
中序遍历:当一个节点的左子树处理完,即将要处理右子树之前执行
后序遍历:即将离开一个节点的时候执行
说了这么多基础的,就是要帮你建立对二叉树正确的认识,然后你会发现:
二叉树的所有问题,就是让你在前中后序位置注入巧妙的代码逻辑,去达到自己的目的,你只需要单独思考每一个节点应该做什么,其他的不用你管,抛给二叉树遍历框架,递归会在所有节点上做相同的操作
先来一个最最最简单的题目,前序遍历。详情可见题目 二叉树的前序遍历
首先我们用最常见的「遍历法」
注:遍历法一般都是通过全局变量来记录结果
// 定义全局变量,存储结果public List<Integer> res = new ArrayList<>();public void traversal(TreeNode root) { if (root == null) return ; // 前序:刚刚进入当前节点 res.add(root.val); traversal(root.left); traversal(root.right);}然后我们使用「分解子问题」的方法来解决一下这个问题
xxxxxxxxxxpublic List<Integer> traversal(TreeNode root) { List<Integer> res = new ArrayList<>(); if (root == null) return res; // 前序:刚刚进入当前节点 res.add(root.val); // 加入左子树处理完的结果 res.addAll(traversal(root.left)); // 加入右子树处理完的结果 res.addAll(traversal(root.right)); // 返回 return res;}见识了两种不同思路,当我们在遇到题目的时候,思考:是否可以通过遍历一遍二叉树得到答案?如果不能的话,是否可以定义一个递归函数,通过子问题(子树)的答案推导出原问题的答案? 如果需要设计到子树信息, 建议使用后序遍历
上面给出的是一道基础题目的两种不同思路的方法,如果想见识更难的可见 二叉树所有路径
说后序位置之前,先简单说下中序和前序
可以发现,前序位置的代码执行是「自顶向下」的,而后序位置的代码执行是「自底向上」的。这不奇怪,因为本文开头就说了前序位置是刚刚进入节点的时刻,后序位置是即将离开节点的时刻
但这里面大有玄妙,意味着前序位置的代码只能从函数参数中获取父节点传递来的数据;而后序位置的代码不仅可以获取参数数据,还可以获取到子树通过函数返回值传递回来的数据
例:现在有一棵二叉树,有两个简单的问题:
第一个问题可以这样写代码:
xxxxxxxxxxpublic void traversal(TreeNode root, int level) { if (root == null) return ; System.out.println("节点 " + root.val + " 在第 " + lever + " 层"); traversal(root.left, level + 1); traversal(root.right, level + 1);}第二个问题可以这样写代码:
xxxxxxxxxxpublic int traversal(TreeNode root) { if (root == null) return ; int leftCount = traversal(root.left); int rightCount = traversal(root.right); System.out.println("节点 " + root.val + " 的左子树有 " + leftCount + " 个节点,右子树有 " + rightCount + " 个节点"); return leftCount + rightCount + 1;}这两个问题的根本区别在于:一个节点在第几层,从根节点遍历过来的过程就能顺带记录;而以一个节点为根的整棵子树有多少个节点,需要遍历完子树之后才能数清楚
结合这两个简单的问题,可以品味一下后序位置的特点,只有后序位置才能通过返回值获取子树的信息
二叉树题型主要是用来培养递归思维的,而层序遍历属于迭代遍历,也比较简单,这里就过一下代码框架吧:
xxxxxxxxxxpublic List<Integer> levelTraverse(TreeNode root) { List<Integer> res = new ArrayList<>(); if (root == null) return res; Queue<TreeNode> q = new LinkedList<>(); q.offer(root); while (!q.isEmpty()) { int sz = q.size(); for (int i = 0; i < sz; i++) { TreeNode cur = q.poll(); res.add(cur.val); if (cur.left != null) q.offer(cur.left); if (cur.right != null) q.offer(cur.right); } } return res;}关于更多层序遍历的应用,可见 二叉树关于行的相关操作技巧