前序遍历
二叉树的前序遍历访问顺序是:根节点 -> 左子树 -> 右子树。
递归实现
public void preOrderRecursive(TreeNode root){
if(root == null) return;
System.out.println(root.val);
printPreOrder(root.left);
printPreOrder(root.right);
}
非递归实现
非递归实现利用栈来实现,主要是利用栈来存储根节点,因为之后需要访问根节点的右孩子。每次在压栈之前,先输出当前节点的值(因为当前节点就是根节点或者子树的根节点)。如果向左走已经为空了,就从栈中弹出节点,并访问弹出节点的右孩子。
public void preOrderNonRecursive(TreeNode root){
TreeNode node = root;
Stack<TreeNode> stack = new Stack<>();
while(node != null || !stack.isEmpty()){
while(node!=null){
System.out.println(node.val);
stack.push(node);
node = node.left;
}
if(!stack.isEmpty()){
node = stack.pop();
node = node.right;
}
}
}
中序遍历
中序遍历的顺序是:左子树 -> 根节点 -> 右子树
递归实现
public void inOrderRecursive(TreeNode root){
if(root == null) return;
printInOrder(root.left);
System.out.println(root.val);
printInOrder(root.right);
}
非递归实现
中序遍历的非递归实现其实只是调换了左孩子和根节点的位置。因此我们不断向左遍历并且压栈,直到没有左孩子节点为止。然后弹栈,并且输出当前节点(因为压入栈的其实是:根节点-左孩子,所以弹栈的时候,先将左孩子弹出访问,然后再弹出根节点访问)。然后再访问该节点的右孩子节点。
public void inOrderNonRecursive(TreeNode root){
TreeNode node = root;
Stack<TreeNode> stack = new Stack<>();
while(node != null || !stack.isEmpty()){
while(node!=null){
stack.push(node);
node = node.left;
}
if(!stack.isEmpty()) {
node = stack.pop();
System.out.println(node.val);
node = node.right;
}
}
}
后序遍历
后序遍历的访问顺序是:左子树 -> 右子树 -> 根节点
递归实现
public void postOrderRecursive(TreeNode root){
if(root == null) return;
printPostOrder(root.left);
printPostOrder(root.right);
System.out.println(root.val);
}
非递归实现
后序遍历的非递归实现是三种遍历方式中最复杂的一种。因为它需要先访问右孩子节点之后才能访问根节点。因此需要一个变量来存储前一个访问的节点,用判断根节点的右孩子是否访问过。并且在访问节点右孩子节点访问过或者为空的情况下,才从栈中弹出根节点。
public void postorderNonRecursive(TreeNode root) {
if(root == null) return ;
Stack<TreeNode> stack = new Stack<>();
TreeNode node = root;
TreeNode pre = null;
while(!stack.isEmpty() || node!=null){
while (node!=null){
stack.push(node);
node = node.left;
}
if(!stack.isEmpty()){
node = stack.peek();
if(node.right==null||node.right == pre){
stack.pop();
System.out.println(node.val);
pre = node;
node = null;
}
else{
node = node.right;
}
}
}
}
总结
二叉树的前序、中序、后序遍历的递归实现都是很容易理解和实现的。重点是它们的非递归实现,利用的栈来存储之后需要再次访问的节点。但是无论是哪种实现,都需要我们能够流畅的写出来。另外,这三种的时间复杂度都是O(n),空间复杂度也是O(n)。下一篇的莫里斯(Morris)遍历,可以将空间复杂度优化到O(1)。