分治法在二叉树遍历中是一种深度优先遍历(DFS),将问题拆解成子问题后,然后合并子问题。
- 分解:拆解为规模更小的子问题,将问题拆解为足够小时,然后求解。
- 合并:将每个子问题结果进行合并,然后完成整体问题。
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
// 结束条件
if(root==null){
ArrayList<Integer> res = new ArrayList<Integer>();
return res;
}
// 递归拆分问题,获取子问题。
List<Integer> leftres = preorderTraversal(root.left);
List<Integer> rightres = preorderTraversal(root.right);
// 合并子问题
List<Integer> result = new ArrayList<>();
result.add(root.val);
result.addAll(leftres);
result.addAll(rightres);
// 返回合并结果
return result;
}
}
第一步:拆分左右子树。
// 拆分问题,获取子问题。
List<Integer> leftres = preorderTraversal(root.left); // 将左子树递归
List<Integer> rightres = preorderTraversal(root.right); // 将右子树递归
第二步:合并子问题,递归到无法递归时,则会返回到叶子节点的递归调用出继续执行,开始合并结果。
// 合并子问题
List<Integer> result = new ArrayList<>(); // 创建列表存储节点信息
result.add(root.val); // 前序遍历,先访问子树的根节点,再合并左右子树
result.addAll(leftres); // 合并该节点的左子树的值,如果是叶子结点,左右为空,会合并一个空列表。
result.addAll(rightres);// 合并右子树的值
// 返回合并结果
return result; // 返回本次递归的所合并的列表
-
在本例子中,第一个合并是先合并
4
号节点,因为是叶子节点,所以在该节点递归时会将叶子的节点的左右子树进行递归,因为是空节点所以会触发递归结束条件,所以叶子节点的两次左右子树的递归回返回两个空列表。 -
在对叶子节点进行合并子问题时,因为是前序遍历,叶子节点也可以视为一个左右孩子为空的二叉树,所以会先存储节点值,然后合并叶子节点递归调用时返回的两个空列表,然后本次递归结束并返回结果集,结果集中只有一个叶子节点的值。
-
上次是在
2
号节点递归左子树时递归到的4
号节点,所以继续在2
号节点继续递归右子树,5
号节点仍然是个叶子节点,因此从5
号递归调用时也会返回一个只含有5
号节点值的列表。 -
2
号节点的左右子树递归都执行完毕,程序继续执行,接下来则是在2
号节点处合并子问题。因为前序遍历规则,所以先将2
号节点值存入列表,然后把2
号节点左子树的结果集合并到2
号节点列表中,再把右子树的结果集合并2
号节点的结果集中。所以在2
号节点结果集中的顺序就是[2,4,5]
,合并顺序就是前序遍历规则的顺序。然后返回2
号节点的结果集[2,4,5]
-
上次是在
1
号节点递归左子树时递归到的2
号节点,所以继续在1
号节点继续递归右子树,1
号节点也是整棵二叉树的根节点,所以递归右子树顺序和左子树顺序一样,就不在赘述右子树递归顺序。2
节点返回的结果集后,我们这里认为3
号节点结果集也已返回。因此接下来执行合并子问题,根据前序规则,先存入1
号节点的值,然后将2
号节点的返回的结果集合并到1
号节点的结果集中得到[1,2,4,5]
,然后再把3
号节点结果并入结果集中得到[1,2,4,5,3,6,7]
,合并操作结束后,返回1
号节点的结果集,至此二叉树递归完毕。
总结:先递归寻找最左叶子节点,叶子节点也是一颗特殊的二叉树,每个节点把自己的左右子树结果合并,然后将结果返回给上层调用,上层继续合并,直到返回到根节点,根节点将左右子树的结果合并后返回整颗二叉树的遍历的结果。