二叉树的遍历

二叉树的中序遍历

根据leetCode中94题的二叉树中序遍历而整理

给定一个二叉树的根节点 root ,返回 它的 中序 遍历

示例 1:

img

1
2
输入:root = [1,null,2,3]
输出:[1,3,2]

示例 2:

1
2
输入:root = []
输出:[]

解法一:递归

​ 最简单的方法,也是很好想到的解法,一开始只会这个 ,哈哈哈。。但总是在进步嘛

中序遍历的访问顺序是:左子树—->根节点—->右子树(在代码中就是左 - 打印 - 右

由于 访问左子树或者右子树的时候我们按照同样的方式遍历,直到遍历完整棵树。所以具有递归的性质,可以直接用递归函数来模拟这一过程。

递归函数实现:

  • 终止条件:当前节点为空时
  • 函数内:递归的调用左节点,打印当前节点,再递归调用右节点。

代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
//递归的方式实现
List<Integer> list = new LinkedList<>();
public List<Integer> inorderTraversal(TreeNode root) {
if(root!=null){
//左子树
if (root.left!=null) inorderTraversal(root.left);
//打印
list.add(root.val);
//右子树
if (root.right!=null) inorderTraversal(root.right);
}
return list;
}
}

解法二:迭代

一开是不会这个方法,但是看了官方题解中的动图步骤,竟然自己写出来了!~~加鸡腿!!哈哈

觉得官方题解的步骤太好了,清晰明了,在这里显示一下吧,以免过后忘记了~~记性太差了!!

在递归的过程中,操作系统/虚拟机自动帮我们用栈来保存,所以在迭代的过程中需要自己手动维护一个栈进行实现。

image-20220602091512478

image-20220602091528495

image-20220602091612672

image-20220602091649940image-20220602091714841

image-20220602091731552

image-20220602091807918

代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
//迭代
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<Integer>();
Deque<TreeNode> stk = new LinkedList<TreeNode>();
while (root != null || !stk.isEmpty()) {
while (root != null) {
stk.push(root);
root = root.left;
}
root = stk.pop();
res.add(root.val);
root = root.right;
}
return res;
}
}

解法三:Morris

这个算法,由于不需要维护一个栈,所以空间复杂度为O(1)。优点是节省了空间,缺点是改变了整个树的结构,强行把一棵二叉树改成一段链表结构。

Morris 遍历算法整体步骤如下(假设当前遍历到的节点为 x):

(1)如果x没有左孩子,则加入答案组,再访问x的右节点,即:x=x.rigth

(2)如果x有左孩子,则找到x左子树上最右的节点(即左子树中序遍历的最后一个节点,x在中序遍历中的前驱节点),我们记作 predecdssor。根据predecdssor的右孩子是否为空,进行如下操作。

  • 如果predecdssor的右孩子为空,则将其右孩子指向x,然后访问x的左孩子,即x=x.left
  • 如果predecdssor的右孩子不为空,则此时右孩子指向x,说明我们已经遍历完x的左子树,我们将predecdssor的右孩子置空,将 x 的值加入答案数组,然后访问 x 的右孩子,即 x=x.right

其实整个过程我们就多做一步:假设当前遍历到的节点为 x,将 x 的左子树中最右边的节点的右孩子指向 x,这样在左子树遍历完成后我们通过这个指向走回了 x,且能通过这个指向知晓我们已经遍历完成了左子树,而不用再通过栈来维护,省去了栈的空间复杂度。

代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
//Morris方法
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> list = new LinkedList<>();
while (root!=null){
//如果root有左子树,则寻找左子树的最右节点,也就当前root节点的前驱节点
if (root.left!=null) {
//找左子树的最后节点,从左子树的根找起
TreeNode pre = root.left;
//一次找取最右节点,这个找取是再原树上找取,所以之前添加的pre.right = root;不能算,所以pre.right!=root
while (pre.right!=null && pre.right!=root){
pre = pre.right;
}
//判断pre 的右孩子是否为空,不为空说明右节点已经指向root,已经在原来的树上改变了,说明已经遍历完成root的左子树了
if (pre.right!=null){
list.add(root.val);
root = root.right;
pre.right = null;
//让 pre 的右指针指向 root,继续遍历左子树
}else {
pre.right = root;
root = root.left;
}
//root没有左子树,则访问右子树
}else {
list.add(root.val);
root = root.right;
}
}
return list;
}
}

二叉树的前序遍历

leetCode144题

给你二叉树的根节点 root ,返回它节点值的 前序 遍历。

示例 1:

img

1
2
输入:root = [1,null,2,3]
输出:[1,2,3]

示例 2:

1
2
输入:root = []
输出:[]

解法一:递归

前序遍历的访问顺序是:根节点—->左子树—->右子树(在代码中就是打印 - 左 - 右

代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
//前序遍历:递归
List<Integer> list = new LinkedList<>();
public List<Integer> preorderTraversal(TreeNode root){
if (root!=null){
list.add(root.val);
if (root.left!=null) preorderTraversal(root.left);
if (root.right!=null) preorderTraversal(root.right);
}
return list;
}
}

解法二:迭代

前序遍历的迭代,就是先加入list。

代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
//迭代
public List<Integer> preorderTraversal(TreeNode root){
List<Integer> list = new LinkedList<>();
Stack<TreeNode> stack = new Stack<>();
while (root!=null || !stack.isEmpty()){
while (root!=null){
list.add(root.val);
stack.add(root);
root = root.left;
}
root = stack.pop();
root = root.right;
}
return list;
}
}

解法三:Morris

哇塞今天是很开心的一天呀有着中序遍历中Morris方法的经验,看着下图就爽了,希望不要忘记把,咋可能呢,明天估计就不会了。

image-20220602113552988

代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
//Mirros
public List<Integer> preorderTraversal(TreeNode root){
List<Integer> list = new LinkedList<>();
if (root == null) {
return res;
}
TreeNode pre = new TreeNode();
while (root!=null){
list.add(root.val);
if (root.left!=null){
pre = root.left;
while (pre.right!=null && pre.right!=root.right){
pre = pre.right;
}
if (pre.right==null) {
pre.right = root.right;
}else {
pre = pre.right;
list.add(pre.val);
}
root = root.left;
}else {
root = root.right;
}
}
return list;
}
}

二叉树的后序遍历

leetCode145题:

给你一棵二叉树的根节点 root ,返回其节点值的 后序遍历

示例 1:

img

1
2
输入:root = [1,null,2,3]
输出:[3,2,1]

示例 2:

1
2
输入:root = []
输出:[]

解法一:递归

后序遍历的访问顺序是:左子树—->右子树—->根节点(在代码中就是左 - 右 - 打印

代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
//递归
List<Integer> list = new LinkedList<>();
public List<Integer> postorderTraversal(TreeNode root) {
if (root!=null){
if (root.left!=null) postorderTraversal(root.left);
if (root.right!=null) postorderTraversal(root.right);
list.add(root.val);
}
return list;
}
}

解法二:迭代

后序遍历真难呀!

代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
//迭代
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> list = new LinkedList<>();
Stack<TreeNode> stack = new Stack<>();
//从栈中弹出的节点,我们只能确定其左子树肯定访问完了,但是无法确定右子树是否访问过。因此,我们在后序遍历中,引入了一个prev来记录历史访问记录。
TreeNode pre = new TreeNode();
while (root!=null || !stack.isEmpty()){
while (root!=null){
stack.push(root);
root = root.left;
}
root = stack.pop();
//这步就是要判断一下是不是左右子树都已经遍历完成
//如果没有右子树,或者右子树访问完了,也就是上一个访问的节点是右子节点时
//说明可以访问当前节点
if (root.right==null || root.right==pre){
list.add(root.val);
//更新历史访问记录,这样回溯的时候父节点可以由此判断右子树是否访问完成
pre = root;
root = null;
//如果没有,则要继续进行右子树的遍历,但根节点在右子树之后,所以要再次入队
}else {
stack.push(root);
root = root.right;
}
}
return list;
}
}

解法三:Morris

不会!不会!不会! 要了命了

但是可以把前序遍历的left变为right right变为left 最后输出的list进行逆转,Collections.recerse(list);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
//Mirros
public List<Integer> postorderTraversal(TreeNode root){
List<Integer> list = new LinkedList<>();
TreeNode pre = new TreeNode();
while (root!=null){
list.add(root.val);
if (root.right!=null){
pre = root.right;
while (pre.left!=null && pre.left!=root.left){
pre = pre.left;
}
if (pre.left==null) {
pre.left = root.left;
}else {
pre = pre.left;
list.add(pre.val);
}
root = root.right;
}else {
root = root.left;
}
}
Collections.reverse(list);
return list;
}
}

二叉树的遍历
http://example.com/2022/06/01/二叉树的遍历/
作者
zlw
发布于
2022年6月1日
许可协议