文章链接:
代码随想录 二叉树原理遍历讲解
代码随想录 二叉树迭代遍历文章讲解
树的递归遍历
树的遍历方法中,最为经典就是递归遍历的方法,这里直接给出递归的代码,谨记 三种遍历的口诀
- 先序遍历,根左右
- 中序遍历,左根右
- 后序遍历,左右根
// 先序遍历
public void preOrderTraversal(TreeNode t){
// 给出截止条件
if(t == null)
return;
// 左右根的顺序进行遍历
// 节点值操作 list.add(t.val);
preorderTraversal(t.left, list);
preorderTraversal(t.right, list);
}
// 中序遍历
public void inOrderTraversal(TreeNode t){
// 给出截止条件
if(t == null)
return;
// 左右根的顺序进行遍历
inorderTraversal(t.left, list);
// 节点值操作 list.add(t.val);
inorderTraversal(t.right, list);
}
// 后序遍历
public void postOrderTraversal(TreeNode t){
// 给出截止条件
if(t == null)
return;
// 左右根的顺序进行遍历
postOrderTraversal(t.left, list);
postOrderTraversal(t.right, list);
// 节点值操作 list.add(t.val);
}
树的迭代遍历
“理论上来说,迭代遍历速度会快一些,因为递归遍历中计算内存中进栈出栈比较费时间,同时编写递归容易出错;但递归遍历仍然是二叉树最首要的访问方式,迭代方式其次”
上文是今天所额外学习到的树的遍历的经验!迭代遍历的思维就是使用栈来结合循环实现递归的等价。而迭代遍历时,就非常需要主要什么时候应当入栈元素,什么时候应当出栈元素。另外因为栈的后进先出的思想与递归自顶向下再自底向上的过程非常吻合,因此递归可以使用栈+循环的方式等价实现。
以下给出代码随想录所实现的三种迭代遍历的方式一同学习。
// 自己实现的一版前序遍历,但是在中序与后序中的入栈时机就未能实现,非常的复杂
// 当使用栈的时候一定要考虑while的条件要设置什么,是对栈的访问还是对t的判断
// 如果仅仅是t的判断的话会非常的繁琐,除非栈内永远不会压入null,而不压入null的话,t就得不到null,那么无法跳出循环,所以这样前后矛盾的状态指明了while的条件应当是 对栈是否为空进行判断
public static void preorderTraversal(TreeNode t, Stack<TreeNode> stack, List<Integer> res) {
// 二叉树的前序遍历,根-左-右的顺序
while(t != null){
// 根操作
res.add(t.val);
if(t.left != null){
stack.push(t);
t = t.left;
continue;
}
if(t.right != null){
t = t.right;
continue;
}
if(stack.isEmpty() == true)
break;
while(stack.isEmpty() == false){
t = stack.pop().right;
if(t != null)
continue;
}
}
}
// 先序遍历
public static void preorderTraversal(TreeNode t, Stack<TreeNode> stack, List<Integer> res) {
Stack<TreeNode> stack = new Stack<>();
stack.push(root);
while(stack.isEmpty() == false){
// 根左右的操作
TreeNode t = stack.pop();
res.add(t.val);
// 注意一定是先压栈右子树,这样结合栈的特点才会出现根左右的效果
if(t.right != null)
stack.push(t.right);
if(t.left != null)
stack.push(t.left);
}
}
// 中序遍历
public static void inorderTraversal(TreeNode t, Stack<TreeNode> stack, List<Integer> res) {
Stack<TreeNode> stack = new Stack<>();
// 左根右的操作,获取根节点的val值的时候就是出栈的时机
// 而左右子树的访问操作中,入栈的时机是在左子树中,因为我们入栈的目的就是为了可以在访问完左子树之后还可以返回到祖先节点访问右子树
while(t != null || stack.isEmpty() == false){
if(t != null){
stack.push(t);
t = t.left;
}
else{
t = stack.pop();
res.add(t.val);
t = t.right;
}
}
}
public static void postorderTraversal(TreeNode t, Stack<TreeNode> stack, List<Integer> res) {
// 迭代后序遍历的方式非常精彩
// 元素总是在访问左子树的时候进行入栈,入栈的目的就是为了在访问完左子树之后可以返回去访问祖先节点的右子树,但是在出栈指向右子树之后,根结点或者子树的根结点的val值就无法被访问到了,所以我们难以复刻中序遍历的操作来实现后序的迭代遍历
// 结合先序遍历是 根左右的操作,而后序遍历的使 左右根,因此如果我们以 根右左 的方式进行遍历,然后进行元素翻转,就是所需的迭代后续遍历的结果。
// 我们直接使用先序迭代遍历,替换其中左右子树加入栈的顺序即可
Stack<TreeNode> stack = new Stack<>();
stack.push(t);
while(stack.isEmpty() == false){
t = stack.pop();
res.add(t.val);
if(t.left != null)
stack.push(t.left);
if(t.right != null)
stack.push(t.right);
}
Collections.reverse(res);
}