二叉树的遍历算法(DFS和BFS)
/* Definition for a binary tree node. */
public class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int x) {
val = x;
}
}
深度优先遍历(DFS)
迭代算法
前序遍历(先访问根节点)
用一个stack进行辅助
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
Deque<TreeNode> stack = new ArrayDeque<>();
LinkedList<Integer> res = new LinkedList<>();
if (root == null) {
return res;
}
stack.push(root);
while (!stack.isEmpty()) {
TreeNode node = stack.pop();
res.add(node.val);
if (node.right != null) {
stack.push(node.right);
}
if (node.left != null) {
stack.push(node.left);
}
}
return res;
}
}
中序遍历(中间访问根节点)
用一个stack进行辅助
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> res = new LinkedList<>();
Deque<TreeNode> stack = new ArrayDeque<>();
if(root == null)
return res;
TreeNode curr = root;
while(curr!=null || !stack.isEmpty()){
while(curr != null){
stack.push(curr);
curr = curr.left;
}
curr = stack.pop();
res.add(curr.val);
curr = curr.right;
}
return res;
}
}
后序遍历(最后访问根节点)
后序遍历相较于前两种更复杂一些,有两种方法。
方法一:
用一个stack进行辅助,mark标记了上一个访问过的节点,在判断节点是否入栈时将mark作为判断条件之一,这样就避免了访问过的节点再次入栈。
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
LinkedList<Integer> res = new LinkedList<>();
Deque<TreeNode> stack = new ArrayDeque<>();
if(root == null)
return res;
stack.push(root);
TreeNode mark = root;
while(!stack.isEmpty()){
TreeNode top = stack.peek();
if(top.left != null && top.left != mark && top.right != mark){
stack.push(top.left);
}else if(top.right != null && top.right != mark){
stack.push(top.right);
}else{
mark = stack.pop();
res.add(mark.val);
}
}
return res;
}
}
方法二:
用了两个stack进行辅助,思路是将待访问的节点按照逆序push进stack2,然后遍历stack2的顺序即为后续遍历的顺序。之所以能够这么做是因为我们最先访问的节点是父节点,这个节点在后续遍历中最后访问,我们就可以将它最先push进stack2,然后再push右子节点、左子节点。在push右子节点时,它本身也是一个父子节点,所以再push它的右子节点、左子节点。这种递归关系被保存在stack1中,使得节点按照顺序push进stack2中。
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
LinkedList<Integer> res = new LinkedList<>();
if(root == null)
return res;
Deque<TreeNode> stack1 = new ArrayDeque<>();
Deque<TreeNode> stack2 = new ArrayDeque<>();
stack1.push(root);
while(!stack1.isEmpty()){
TreeNode temp = stack1.pop();
stack2.push(temp);
if(temp.left != null)
stack1.push(temp.left);
if(temp.right != null)
stack1.push(temp.right);
}
while(!stack2.isEmpty()){
res.add(stack2.pop().val);
}
return res;
}
}
递归算法
递归算法比较简单,这里不再赘述
迭代算法与递归算法比较
因为每一个节点都要被访问,所以两种方法的时间复杂度都为O(N)。对于空间复杂度,递归算法的空间复杂度为O(N),当树很大时,会出现stackoverflow的问题。迭代算法由于借助了stack,最坏的情况下节点全部入栈,因此空间复杂的也为O(N),同样存在stackoverflow的问题。
值得注意的是,有一种迭代算法可以将空间复杂度降低到O(1),它就是Morris迭代算法。
广度优先遍历(BFS)
递归算法
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> res = new ArrayList<>();
if(root == null)
return res;
helper(root, 0, res);
return res;
}
public void helper(TreeNode node, int level, List<List<Integer>> res){
if(res.size() == level)
//如果没有这一层,则创建它
res.add(new ArrayList<Integer>());
res.get(level).add(node.val);
if(node.left != null)
helper(node.left, level+1, res);
if(node.right != null)
helper(node.right, level+1, res);
}
}
迭代算法
每次迭代都创建新的一层,把这层上的节点(被储存在queue中)的值放进去,并按从左到右的顺序把每个节点的左右子节点放进queue,作为下一层的节点储存下来。
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> res = new ArrayList<>();
if(root == null)
return res;
Queue<TreeNode> queue = new LinkedList<>();
queue.add(root);
int level = 0;
while(!queue.isEmpty()){
res.add(new ArrayList<Integer>());
int queueSize = queue.size();
for(int i = 0; i<queueSize; i++){
TreeNode temp = queue.remove();
res.get(level).add(temp.val);
if(temp.left != null)
queue.add(temp.left);
if(temp.right != null)
queue.add(temp.right);
}
level++;
}
return res;
}
}
迭代算法与递归算法比较
两种算法的时间复杂度和空间复杂度都为O(N)。