二叉树前中后三种遍历方式的递归和非递归写法
本文所用的二叉树节点定义如下
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;
}
}
本文所有遍历方式都是将节点的值按遍历顺序放进ArrayList
中。
递归写法
对二叉树使用递归进行遍历比较简单。
前序遍历
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> list = new ArrayList<>();
preorder(root, list);
return list;
}
private void preorder(TreeNode root, List<Integer> list){
if(root != null){
list.add(root.val);
preorder(root.left, list);
preorder(root.right, list);
}
}
}
中序遍历
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> list = new ArrayList<>();
inorder(root, list);
return list;
}
private void inorder(TreeNode root, List<Integer> list){
if(null != root){
inorder(root.left, list);
list.add(root.val);
inorder(root.right, list);
}
}
}
后序遍历
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> list = new ArrayList<>();
postorder(root, list);
return list;
}
private void postorder(TreeNode node, List<Integer> list){
if(node != null){
postorder(node.left, list);
postorder(node.right, list);
list.add(node.val);
}
}
}
非递归写法
三种遍历方式都需要借助Stack
栈这种数据结构进行辅助。
但由于Java
官方不推荐使用Stack
类,所以本文都使用Deque
类来代替Stack
的使用,读者可根据自己的喜好决定用哪个数据结构。
为了方便理解三种遍历方式的非递归写法,本文将二叉树拟化成 前、中、后 三个部分。
前序遍历
前序遍历的输出结果在数组中可以看成 {中 前 后} 三块,因此在遍历节点时,需要先将被遍历到的中部分的节点值输出,再将右儿子的节点抛入栈,最后将左儿子的节点抛入栈,这样便可以保证下次抛出栈的是左儿子的节点,输出前的部分,最后抛出右儿子的节点,输出后的部分。
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> list = new ArrayList<>();
if(root == null){
return list;
}
Deque<TreeNode> deque = new ArrayDeque<>();
deque.addLast(root);
while(!deque.isEmpty()){
TreeNode node = deque.pollLast();
list.add(node.val);
if(node.right != null)
deque.addLast(node.right);
if(node.left != null)
deque.addLast(node.left);
}
return list;
}
}
中序遍历
中序遍历的输出结果是 {前 中 后} 三块。如果要保证前部分优先输出,就需要先把所有的左儿子的节点依次抛入栈中。第一次进行抛出时,如果此时有右儿子,那么第一次抛出的就是没有左儿子的中间节点,前的部分是空的所以跳到中的部分输出,然后输出右儿子后的部分。如果第一次抛出的没有右儿子,那么抛出时输出的自然就是前部分,然后再抛出的就是上一个抛出的父节点,也就是输出的中部分,最后输出后部分。
初次理解中序遍历可能有些困难,可以画图思考一下。
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> list = new ArrayList<>();
Deque<TreeNode> deque = new ArrayDeque<>();
TreeNode cur = root;
while(cur != null || !deque.isEmpty()){
while(cur != null){
deque.addLast(cur);
cur = cur.left;
}
cur = deque.pollLast();
list.add(cur.val);
cur = cur.right;
}
return list;
}
}
后序遍历
后序遍历的输出结果是 {前 后 中} ,可以利用两个栈结构来实现。
第一个辅助栈结构通过上文“前序遍历”非递归的方法来实现 {中 后 前} 的特殊顺序,输出的值不存进结果数组中,而是压入第二个栈结构。
由于第二个栈结构入栈顺序是 {中 后 前},那么出栈顺序就变成了 {前 后 中},后序遍历也就得以实现。
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> list = new ArrayList<>();
Deque<TreeNode> auxDeque = new ArrayDeque<>();
Deque<Integer> stack = new ArrayDeque<>();
if(root != null)
auxDeque.addLast(root);
while(!auxDeque.isEmpty()){
TreeNode node = auxDeque.pollLast();
stack.addLast(node.val);
if(node.left != null){
auxDeque.addLast(node.left);
}
if(node.right != null){
auxDeque.addLast(node.right);
}
}
while(!stack.isEmpty()){
list.add(stack.pollLast());
}
return list;
}
}
题外话,由于前序遍历的输出顺序是 {中 前 后} ,中序遍历的输出顺序是{前 中 后} ,后序遍历的输出顺序是 {前 后 中},所以可以根据 前序遍历输出结果和中序遍历输出结果 或者 后序遍历输出结果和中序遍历输出结果 得到原先的二叉树结构,具体方式可见这篇文章通过前序序列和中序序列或中序序列和后序序列还原二叉树。