【每日算法Day 26】二叉树基础1
Wallace Xu 2020-06-26 树
内存中的树多数情况下都是二叉树的形式,其前序、中序、后序和层次遍历的方法以及遍历结果的特性是需要掌握的。在它们的基础上又会产生更复杂的问题。
二叉树遍历的递归写法很简单,只需要调整访问当前节点的值
、递归调用左节点的遍历
和递归调用左节点的遍历
的顺序就可以了。
public void preOrder(TreeNode node){
if (node != null) {
System.out.print(node.val+" ");
preOrder(node.left);
preOrder(node.right);
}
}
public void inOrder(TreeNode node){
if (node != null) {
inOrder(node.left);
System.out.print(node.val+" ");
inOrder(node.right);
}
}
public void postOrder(TreeNode node){
if (node != null) {
postOrder(node.left);
postOrder(node.right);
System.out.print(node.val+" ");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
然而,递归写法用到了系统的调用栈,一旦二叉树过大就可能造成调用栈溢出,所以非递归写法也需要掌握。非递归遍历主要使用自己的栈和循环来实现。
二叉树的层次遍历和其他三者不同,可以利用队列:
public void levelOrder(TreeNode node){
if (node==null) return;
Queue<TreeNode> q=new LinkedList<>();
q.offer(node);
while (!q.isEmpty()){
TreeNode tmp=q.poll();
System.out.print(tmp.val+" ");
if (tmp.left!=null)
q.offer(tmp.left);
if (tmp.right!=null)
q.offer(tmp.right);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
# LeetCode 144. 二叉树的前序遍历 (opens new window)
前序遍历的非递归写法,核心思想是当节点不为空就访问当前节点的值
,同时用栈保存访问过的父亲节点
,再把指针指向当前节点的左孩子,就这样一直遍历直到左孩子为空。此时如果栈为空则遍历结束,当栈不为空时弹出栈顶(最后一次访问的节点),并对该节点的右孩子进行相同操作。
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> list=new ArrayList<>();
if (root==null) return list;
Deque<TreeNode> s= new LinkedList<>();
while (true){
while (root!=null){
list.add(root.val);
s.push(root);
root=root.left;
}
if (s.size()==0)
break;
root=s.pop();
root=root.right;
}
return list;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
另一种前序遍历的非递归写法,思想是用栈保存将要访问的两个孩子节点
,一开始将根节点入栈,接下来每次出栈顶的节点并访问,再依次压入该节点的右节点和左节点(不为空的话)。该方法与树的层次遍历思维类似。
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> list = new ArrayList<>();
if(root==null) return list;
Deque<TreeNode> stack = new LinkedList<>();
stack.push(root);
while(stack.size()!=0){
TreeNode node = stack.pop();
list.add(node.val);
if(node.right!=null)
stack.push(node.right);
if(node.left!=null)
stack.push(node.left);
}
return list;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# LeetCode 94. 二叉树的中序遍历 (opens new window)
中序遍历的非递归写法,与第一种前序遍历类似,同样是用栈保存访问过的节点
,区别是将访问值的过程移到了每次出栈之后:
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> list=new ArrayList<>();
if (root==null) return list;
Deque<TreeNode> s= new LinkedList<>();
while (true){
while (root!=null){
s.push(root);
root=root.left;
}
if (s.size()==0)
break;
root=s.pop();
list.add(root.val);
root=root.right;
}
return list;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# LeetCode 145. 二叉树的后序遍历 (opens new window)
后序遍历的非递归写法,与第二种前序遍历类似,同样是用栈保存将要访问的两个孩子节点
,区别是访问值的过程放在出栈后,而仅当栈顶元素的两个子节点都为空
或者 栈顶元素是在其两个子节点(不全部为空)出栈后来到栈顶
才将栈顶元素出栈,并更新最后一个出栈的元素,否则将该节点的右节点和左节点依次入栈。
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> list = new ArrayList<>();
if(root==null) return list;
Deque<TreeNode> stack = new LinkedList<>();
stack.push(root);
TreeNode curNode,lastPopedNode=null;
while(stack.size()!=0){
curNode=stack.peek();
if((curNode.left==null&&curNode.right==null)||
(lastPopedNode!=null&&(lastPopedNode==curNode.left||lastPopedNode==curNode.right))){
list.add(stack.pop().val);
lastPopedNode=curNode;
}else{
if(curNode.right!=null)
stack.push(curNode.right);
if(curNode.left!=null)
stack.push(curNode.left);
}
}
return list;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22