【算法】二叉树、N叉树先序、中序、后序、BFS、DFS遍历的递归和迭代实现记录(Java版)
本文总结了刷LeetCode过程中,有关树的遍历的相关代码实现,包括了二叉树、N叉树先序、中序、后序、BFS、DFS遍历的递归和迭代实现。这也是解决树的遍历问题的固定套路。
一、二叉树的先序、中序、后序遍历
1、递归模板
(1)先序
1 public void preorder(TreeNode root) { 2 if (root == null) { 3 return; 4 } 5 res.add(root.val); 6 preorder(root.left); 7 preorder(root.right); 8 }
(2)中序
1 public void inorder(TreeNode root) { 2 if (root == null) { 3 return; 4 } 5 inorder(root.left); 6 res.add(root.val); 7 inorder(root.right); 8 }
(3)后序
1 public void postorder(TreeNode root) { 2 if (root == null) { 3 return; 4 } 5 postorder(root.left); 6 postorder(root.right); 7 res.add(root.val); 8 }
2、迭代模板:显式使用栈
(1)先序 。也可以使用后文的DFS实现
1 public void preorder(TreeNode root) { 2 Deque<TreeNode> stack = new ArrayDeque<>(); 3 while(root !=null || !stack.isEmpty()) { 4 while(root != null) { 5 stack.push(root); 6 res.add(root.val);//保存结果 7 root = root.left; 8 } 9 root = stack.pop(); 10 root = root.right; 11 } 12 }
(2)中序
1 public void inorder(TreeNode root) { 2 Deque<TreeNode> stack = new ArrayDeque<>(); 3 while(root != null || !stack.isEmpty()) { 4 while(root != null) { 5 stack.push(root); 6 root = root.left; 7 } 8 root = stack.pop(); 9 res.add(root.val);//保存结果 10 root = root.right; 11 } 12 }
(3)后序。先序是根——左——右,而后序是左-右-根,可以将先序改成根-右-左,然后将结果反转。如下代码对比先序的代码:
1 public void postorder(TreeNode root) { 2 Deque<TreeNode> stack = new ArrayDeque<>(); 3 while(root != null || !stack.isEmpty()) { 4 while(root != null) { 5 stack.push(root); 6 res.add(root.val);//保存结果 7 root = root.right;//注意这里和先序、中序的差别 8 } 9 root = stack.pop(); 10 root = root.left(); 11 } 12 Collections.reverse(res);//将前面的结果反转 13 }
当然,上面这种方法比较间接,借助于先序遍历的理解。下面采用一种直接的迭代方式:
1 public void postorder(TreeNode root) { 2 Deque<TreeNode> stack = new ArrayDeque<>(); 3 TreeNode preNode = new TreeNode();//该节点用于保存前一个出栈的节点 4 while (root != null || !stack.isEmpty()) { 5 //将当前节点的左子树节点一次入栈 6 while (root != null) { 7 stack.push(root); 8 root = root.left; 9 } 10 root = stack.peek(); 11 //当前节点没有右孩子了,或者其右孩子已经出栈了,则当前节点也出栈 12 if (root.right == null || root.right == preNode) { 13 root = stack.pop(); 14 res.add(root.val);//保存结果 15 preNode = root; //记录本次刚输出的节点 16 root = null; 17 } else { 18 //当前节点还有右孩子,且其右孩子还没有出栈,则先处理其右孩子 19 root = root.right; 20 } 21 } 22 }
二、N叉树的先序与后序遍历
1、递归模板
(1)先序
1 public void preorder(TreeNode root) { 2 if (root == null) { 3 return; 4 } 5 res.add(root.val);//保存结果 6 if (root.children != null) { 7 for (int i = 0; i < root.children.size(); i++) { 8 dfs(root.children.get(i)); 9 } 10 } 11 }
(2)后序
1 public void postorder(TreeNode root) { 2 if (root == null) { 3 return; 4 } 5 for (int i = 0; i < root.children.size(); i++) { 6 postorder(root.children.get(i)); 7 } 8 res.add(root.val);//保存结果 9 }
2、迭代模板
(1)先序。即下文中DFS的实现
1 public void preorder(Node root) { 2 Deque<Node> stack = new ArrayDeque<>(); //BFS使用队列,这里使用栈 3 stack.push(root); 4 while (!stack.isEmpty()) { 5 root = stack.pop(); 6 res.add(root.val);//保存结果 7 int childCount = root.children == null ? 0 : root.children.size(); 8 //这里需要注意孩子节点入栈的顺序 9 for (int i = childCount - 1; i >= 0; i--) { 10 stack.push(root.children.get(i)); 11 } 12 } 13 }
(2)后序。先序是根——左——右,而后序是左-右-根,可以将先序改成根-右-左,然后将结果反转。如下代码对比先序的代码:
1 public void postorder(Node root) { 2 List<Integer> result = new ArrayList<>(); 3 Deque<Node> stack = new ArrayDeque<>(); 4 stack.push(root); 5 while (!stack.isEmpty()) { 6 root = stack.pop(); 7 result.add(root.val);//保存结果 8 int childCount = root.children == null ? 0 : root.children.size(); 9 //还入栈的顺序正好和先序遍历相反 10 for (int i = 0; i < childCount; i++) { 11 stack.push(root.children.get(i)); 12 } 13 } 14 //将结果反转 15 Collections.reverse(result); 16 for (Integer i:result) { 17 System.out.print(i); 18 } 19 }
目前还没有找到类似二叉树直接后序遍历的方法
三、树的BFS(广度优先搜索)和DFS(深度优先搜索)实现模板
1、递归实现
(1)BFS
一般来说不能使用递归来实现BFS,因为BFS使用的时队列实现,而递归使用的是栈实现。
(2)DFS
普通的N叉树的DFS包括先序遍历和后序遍历,它们的递归实现和前文一致。如果是二叉树,还有中序遍历,递归实现和前文一致。
2、迭代实现
(1)BFS。即按层次来遍历
1 public void bfs(Node root) { 2 Queue<Node> queue = new LinkedList<>(); 3 queue.offer(root); 4 while (!queue.isEmpty()) { 5 root = queue.poll(); 6 res.add(root.val);//保存结果 7 if (root.children != null) { 8 queue.addAll(root.children);//这里的addAll后,root的children会从左往右依次入队。这一点Deque实现的栈正好相反,这一点需要注意 9 } 10 } 11 }
(2)DFS
先序遍历、中序遍历(二叉树)和后序遍历的迭代方式实现和前文一致。
四、图的BFS和DFS遍历模板
树是图的一种特殊情况,相比于树而言图中可能出现环,所以在遍历的时候可能重复访问。所以树的BFS和DFS实现需要在树的基础上维护一个Set来避免访问重复的节点即可。
1、BFS
1 public void graphyBfs(Node root) { 2 if (root == null) { 3 return; 4 } 5 Set<Node> visitedSet = new HashSet<>(); //维护一个set,保存已经访问过的节点 6 Queue<Node> queue = new LinkedList<>(); 7 queue.offer(root); 8 while (!queue.isEmpty()) { 9 root = queue.poll(); 10 visitedSet.add(root);//保存每个已经输出的节点 11 res.add(root.val);//保存结果 12 if (root.children == null) { 13 return; 14 } 15 for (int i = 0; i < root.children.size(); i++) { 16 Node tmpNode = root.children.get(i); 17 if (!visitedSet.contains(tmpNode)) {//已经输出过的节点,则不用再加入 18 queue.offer(tmpNode); 19 } 20 } 21 } 22 }
2、DFS
迭代方式、递归方式,都维护一个set来保存已经访问过的节点,在输出节点时保存到set中,在添加节点时添加去重操作即可。