「这是我参与2022首次更文挑战的第31天,活动详情查看:2022首次更文挑战」。
本节对应二叉树的先序中序以及后序排列,分别对应LeetCode题目144、94、145,介绍方法包括递归法和非递归法。
递归
递归解决二叉树的遍历很简单,无非就是首先访问根节点,之后访问左子树和右子树,三种遍历顺序的不同往往取决于对节点的打印时机。
先序
先序排列是先访问根节点,之后访问左子树和右子树,在访问根节点的时候就打印根节点,之后打印左子树和右子树,依次类推。代码如下:
public List<Integer> preorderTraversal(TreeNode root) {
ArrayList<Integer> res = new ArrayList<>();
pre(root, res);
return res;
}
public void pre(TreeNode node, ArrayList<Integer> res){
if(node==null) return;
res.add(node.val);
pre(node.left, res);
pre(node.right, res);
}
中序
中序排列是先访问节点的左子树,之后访问根节点,最后访问右子树。在访问左子树时就直接打印左子树,之后打印根节点的值,最后打印右子树的值即可,代码如下:
public List<Integer> inorderTraversal(TreeNode root) {
ArrayList<Integer> res = new ArrayList<>();
dfs(root, res);
return res;
}
public void dfs(TreeNode node, List<Integer> res){
if(node==null) return;
dfs(node.left, res);
res.add(node.val);
dfs(node.right, res);
}
后序
后序排列则是首先访问左子树,直接打印左子树的值,之后访问右子树,打印右子树的值,最后打印根节点的值,代码如下:
public List<Integer> postorderTraversal(TreeNode root) {
ArrayList<Integer> res = new ArrayList<>();
post(root, res);
return res;
}
public void post(TreeNode node, ArrayList<Integer> res){
if(node==null) return;
post(node.left, res);
post(node.right, res);
res.add(node.val);
}
上述三种方式的时间复杂度和空间复杂度都是。三种递归方式实际上是一样的,所不同的是对节点数值的处理时机不同。
非递归
需要明白的是,所有的递归都是可以通过使用特定的数据结构转为非递归。非递归方式无非就是利用相关数据结构来模拟实现系统递归,系统递归其底层是数据的压栈访问,那么对于此三种遍历,我们也可以使用栈作为数据结构来完成人工模拟入栈操作。
先序
先序排列需要准备一个栈,首先将根节点入栈,之后:
- 每次从栈中弹出一个节点,记作
cur。 - 处理
cur, 例如将cur存入结果List。 - 如果
cur存在右孩子则将右孩子入栈,之后入栈左孩子(如果存在)。 - 重复上面步骤即可。
此处需要注意的是先序排列是首先入栈右孩子之后入栈左孩子,这是由栈的特性决定的,因为栈的弹出是先入后出,如果先入栈左孩子再入栈右孩子则会导致先处理右孩子再处理左孩子。代码如下:
public List<Integer> preorderTraversal(TreeNode root) {
if(root==null) return new ArrayList<Integer>();
ArrayList<Integer> res = new ArrayList<>();
Stack<TreeNode> stack = new Stack<>();
stack.push(root);
while (!stack.isEmpty()){
TreeNode cur = stack.pop();
res.add(cur.val);
if(cur.right!=null) stack.push(cur.right);
if(cur.left!=null) stack.push(cur.left);
}
return res;
}
中序
中序排列也需要准备一个栈,其执行过程如下:
- 首先将树的所有左子树入栈。
- 依次弹出栈中节点记作
cur。 - 处理
cur。 - 将当前节点的右孩子重复上面步骤。
代码如下:
public List<Integer> inorderTraversal(TreeNode root) {
if(root==null) return new ArrayList<Integer>();
ArrayList<Integer> res = new ArrayList<>();
Stack<TreeNode> stack = new Stack<>();
while(!stack.isEmpty()||root!=null){
if(root!=null){
stack.push(root);
root = root.left;
}else {
root = stack.pop();
res.add(root.val);
root = root.right;
}
}
return res;
}
后序
后序遍历和上面两个遍历有所不同,后序遍历是先处理左子树再处理右子树最后处理根节点,其打印顺序是左右根,此时无法直接将结果存入List中,解决方法是先将结果存入另一个栈中,之后依次出栈即为最终结果。因为栈的特性,此时我们遍历到的每个节点直接入栈2,之后先处理左孩子入栈1,再处理右孩子入栈1。此时栈1先出栈右孩子,其结果入栈2,之后处理左孩子。栈1的处理顺序是根右左,因此栈2的顺序反过来即为左右根。代码如下:
public List<Integer> postorderTraversal(TreeNode root) {
ArrayList<Integer> res = new ArrayList<>();
Stack<TreeNode> stack = new Stack<>();
Stack<Integer> stack1 = new Stack<>();
stack.push(root);
while(!stack.isEmpty()){
TreeNode cur = stack.pop();
stack1.push(cur.val);
if(cur.left!=null) stack.push(cur.left);
if(cur.right!=null) stack.push(cur.right);
}
while(!stack1.isEmpty()){
res.add(stack1.pop());
}
return res;
}
上述代码的时间复杂度和空间复杂度都为。