关于栈的笔记,整理如下:
建议:不要跳过
1)用栈实现二叉树先序遍历
2)用栈实现二叉树中序遍历
3)用两个栈实现二叉树后序遍历,好写但是不推荐,因为需要收集所有节点,最后逆序弹出,额外空间复杂度为O(n)
4)用一个栈实现二叉树后序遍历
5)遍历二叉树复杂度分析:
-
a. 时间复杂度O(n),递归和非递归都是每个节点遇到有限几次,当然O(n)
-
b. 额外空间复杂度O(h),递归和非递归都需要二叉树高度h的空间来保存路径,方便回到上级去
-
c. 存在时间复杂度O(n),额外空间复杂度O(1)的遍历方式:Morris遍历
-
d. Morris遍历比较难,也比较冷门,会在【扩展】课程里讲述
关于递归更多的内容会在【必备】课程里继续
二叉树更多更难的题会在【必备】课程里继续
1 用栈实现二叉树先序遍历
1.1 思路
利用栈,先压入头节点,弹出头节点,打印头节点的值,然后压入头节点的右孩子,再压入左边孩子,然后按照对待头节点那样一次对待栈中剩下的节点(每次先压入右再压入左,因为要求就是打印:“头左右”)
代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public void preOrder(TreeNode head) {
if (head != null) {
Stack<TreeNode> stack = new Stack<>();
stack.push(head);
while (!stack.isEmpty()) {
TreeNode pop = stack.pop();
System.out.print(pop.val + " ");
// 先压右再压左
if (pop.right != null) {
stack.push(pop.right);
}
if (pop.left != null) {
stack.push(pop.left);
}
}
}
}
1.2 力扣144
给你二叉树的根节点 root
,返回它节点值的 前序 遍历。
示例 1:
输入:root = [1,null,2,3]
输出:[1,2,3]
解释:
示例 2:
输入:root = [1,2,3,4,5,null,8,null,null,6,7,9]
输出:[1,2,4,5,6,7,3,8,9]
解释:
示例 3:
输入:root = []
输出:[]
示例 4:
输入:root = [1]
输出:[1]
提示:
- 树中节点数目在范围
[0, 100]
内 -100 <= Node.val <= 100
进阶:递归算法很简单,你可以通过迭代算法完成吗?
解法1:递归
思路可以参考:017 二叉树及其三种序的递归实现
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 {
public List<Integer> res;
public List<Integer> preorderTraversal(TreeNode root) {
res = new ArrayList<>();
preOrder(root);
return res;
}
public void preOrder(TreeNode root) {
if (root == null) return;
res.add(root.val);
preOrder(root.left);
preOrder(root.right);
}
}
解法2:非递归
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
/**
* 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> res = new ArrayList<>();
if (root != null) {
Stack<TreeNode> stack = new Stack<>();
stack.push(root);
while (!stack.isEmpty()) {
TreeNode pop = stack.pop();
res.add(pop.val);
// 先压右再压左
if (pop.right != null) {
stack.push(pop.right);
}
if (pop.left != null) {
stack.push(pop.left);
}
}
}
return res;
}
}
2 用栈实现二叉树中序遍历
中序遍历:左->根->右
2.1思路
从根节点开始,将所有左子节点入栈,直到左子树为空;然后弹出栈顶节点,访问它,再处理其右子树。
代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public void inOrder(TreeNode head) {
if (head != null) {
Stack<TreeNode> stack = new Stack<>();
while (!stack.isEmpty() || head != null) {
if (head != null) {
stack.push(head);
head = head.left;
} else {
head = stack.pop();
System.out.print(head.val + " ");
head = head.right;
}
}
}
}
建议可以画棵树模拟下上面的过程。
2.2 力扣94
给定一个二叉树的根节点 root
,返回 它的 中序 遍历 。
示例 1:
1
2
输入:root = [1,null,2,3]
输出:[1,3,2]
示例 2:
1
2
输入:root = []
输出:[]
示例 3:
1
2
输入:root = [1]
输出:[1]
提示:
- 树中节点数目在范围
[0, 100]
内 -100 <= Node.val <= 100
进阶: 递归算法很简单,你可以通过迭代算法完成吗?
解法1:递归
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution {
List<Integer> res;
public List<Integer> inorderTraversal(TreeNode root) {
res = new ArrayList<>();
inOrder(root);
return res;
}
public void inOrder(TreeNode t) {
if (t == null) return;
inOrder(t.left);
res.add(t.val);
inOrder(t.right);
}
}
解法2:非递归
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
if (root != null) {
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;
}
3 用两个栈实现二叉树后序遍历
3.1 思路
用第一个栈模拟先序遍历的变种(根 -> 左 -> 右),将结果压入第二个栈;最后从第二个栈弹出所有节点,得到后序遍历(左 -> 右 -> 根)。
代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public void postOrder(TreeNode head) {
if (head != null) {
Stack<TreeNode> stack = new Stack<>();
Stack<TreeNode> out = new Stack<>();
stack.push(head);
while (!stack.isEmpty()) {
TreeNode pop = stack.pop();
out.push(pop);
if (pop.left != null) {
stack.push(pop.left);
}
if (pop.right != null) {
stack.push(pop.right);
}
}
while (!out.isEmpty()) {
TreeNode pop = out.pop();
System.out.print(pop.val + " ");
}
}
}
这种方法空间复杂度为 O(n),不推荐,但适合理解后序遍历的逻辑。
4 用一个栈实现二叉树后序遍历
4.1 思路
用一个栈和一个指针记录上一次访问的节点,判断当前节点是刚从左子树返回还是右子树返回,从而决定是否访问根节点。 需要额外逻辑判断,比两个栈方法更复杂,但空间复杂度优化到 O(h)。
代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public void postOrder1(TreeNode h) {
if (h != null) {
Stack<TreeNode> stack = new Stack<>();
stack.push(h);
while (!stack.isEmpty()) {
TreeNode cur = stack.peek();
if (cur.left != null && h != cur.left && h != cur.right) {
stack.push(cur.left);
} else if (cur.right != null && h != cur.right) {
stack.push(cur.right);
} else {
System.out.print(cur.val + " ");
h = stack.pop();
}
}
}
}
5 力扣145
给你一棵二叉树的根节点 root
,返回其节点值的 后序遍历 。
示例 1:
输入:root = [1,null,2,3]
输出:[3,2,1]
解释:
示例 2:
输入:root = [1,2,3,4,5,null,8,null,null,6,7,9]
输出:[4,6,7,5,2,9,8,3,1]
解释:
示例 3:
输入:root = []
输出:[]
示例 4:
输入:root = [1]
输出:[1]
提示:
- 树中节点的数目在范围
[0, 100]
内 -100 <= Node.val <= 100
进阶:递归算法很简单,你可以通过迭代算法完成吗?
解法1:递归版
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> res;
public List<Integer> postorderTraversal(TreeNode root) {
res = new ArrayList<>();
postOrder(root);
return res;
}
public void postOrder(TreeNode root) {
if (root == null) return;
postOrder(root.left);
postOrder(root.right);
res.add(root.val);
}
}
解法2:非递归版+2个栈
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
/**
* 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> res = new ArrayList<>();
if (root != null) {
Stack<TreeNode> stack = new Stack<>();
Stack<TreeNode> out = new Stack<>();
stack.push(root);
while(!stack.isEmpty()) {
TreeNode pop = stack.pop();
out.push(pop);
if (pop.left != null) {
stack.push(pop.left);
}
if (pop.right != null) {
stack.push(pop.right);
}
}
while (!out.isEmpty()) {
res.add(out.pop().val);
}
}
return res;
}
}
解法3:非递归版+1个栈
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
/**
* 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> res = new ArrayList<>();
if (root != null) {
Stack<TreeNode> stack = new Stack<>();
stack.push(root);
while (!stack.isEmpty()) {
TreeNode cur = stack.peek();
if (cur.left != null && root != cur.left && root != cur.right) {
stack.push(cur.left);
} else if (cur.right != null && root != cur.right) {
stack.push(cur.right);
} else {
res.add(cur.val);
root = stack.pop();
}
}
}
return res;
}
}
6 复杂度总结
用两个栈实现二叉树后序遍历,一个栈收集节点(压栈:根->左->右),另一个栈逆序输出。
这种方法空间复杂度为 O(n),不推荐,但适合理解后序遍历的逻辑。
用一个栈和一个指针记录上一次访问的节点,判断当前节点是刚从左子树返回还是右子树返回,从而决定是否访问根节点。 需要额外逻辑判断,比两个栈方法更复杂,但空间复杂度优化到 O(h)。
额外空间复杂度:用栈实现的非递归遍历,栈的最大深度取决于树的高度 h,因此空间复杂度为 O(h)。
时间复杂度 O(n),空间复杂度 O(1) 的 Morris 遍历,后续学习
用一个栈实现的或者递归实现的【中/先/后序遍历】,时间复杂度?
首先:递归版本,每个节点来到3次,显然对于一个有n个节点的二叉树,其时间复杂度就是O(n)
其次:非递归版本,一个数进栈/出栈(出去了也不会再进来)的次数也一定是有限次,显然时间复杂度也是O(n)
用一个栈实现的或者递归实现的【中/先/后序遍历】,空间复杂度?
不管是递归/非递归,空间复杂度都是O(h)。(h是二叉树的高度)
递归方法压的层数也就是树的高度。非递归中,栈压的数的个数也是树的高度。