二叉树的遍历,通常分为前序、中序、后序和层序遍历。而前序、中序和后序遍历,我们一般刚开始接触的都是递归实现的方式,这种情况不再赘述,本文是主要介绍的是二叉树遍历的非递归实现方法。
首先,定义树节点的结构:
class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int x) {
val = x;
}
}
1.堆栈实现
递归说白了就是栈的调用,这里我们使用Stack来自己实现,用堆栈实现的空间复杂度为O(n)。
前序遍历:
public void preorderStack(TreeNode root) {
if (root == null) {
return ;
}
Stack<TreeNode> stack = new Stack<TreeNode> ();
stack.push(root);
while (!stack.isEmpty()) {
TreeNode node = stack.pop();
System.out.println(node.val);
if (node.right != null) {
stack.push(node.right);
}
if (node.left != null) {
stack.push(node.left);
}
}
}
该方法每次处理一个root节点时,首先输出root的值,然后先将root的右子树入栈,再将root的左子树入栈,如果栈不为空,将栈顶元素赋值给root,循环该过程。
中序遍历:
public void inorderStack(TreeNode root) {
if (root == null) {
return;
}
Stack<TreeNode> stack = new Stack<TreeNode> ();
TreeNode cur = root;
while (!stack.isEmpty() || cur != null) {
if (cur != null) {
while (cur != null) {
stack.push(cur);
cur = cur.left;
}
} else {
cur = stack.pop();
System.out.println(cur.val);
cur = cur.right;
}
}
}
该方法利用栈先进后出的特点,对于当前cur节点,依次将cur和cur的左子树入栈,这样会首先访问cur的左子树,然后访问cur。当cur为空的时候,我们取出栈顶的元素并输出该节点的值,然后将该节点的右节点赋值给cur,开始访问该节点的右子树。
后序遍历:
public void postorderStack(TreeNode root) {
if (root == null) {
return ;
}
Stack<TreeNode> stack = new Stack<TreeNode> ();
stack.push(root);
TreeNode pre = root;
while (!stack.isEmpty()) {
TreeNode cur = stack.peek();
if ((cur.left == null && cur.right == null) || cur.left == pre || cur.right == pre) {
System.out.println(stack.pop().val);
pre = cur;
} else {
if (cur.right != null) {
stack.push(cur.right);
}
if (cur.left != null) {
stack.push(cur.left);
}
}
}
}
该方法利用pre来记录上一个访问的节点,如果当前节点cur为叶子节点,或者pre为cur的孩子节点,表明cur的值可以被输出,否则我们会把cur的右孩子和左孩子相继入栈。
2.Morris实现
Morris方法所使用的空间复杂度为O(1),对上述堆栈方法的O(n)空间复杂度有很大的改善,这里有一篇文章(http://www.cnblogs.com/AnnieKim/archive/2013/06/15/MorrisTraversal.html),对Moriis遍历讲得非常详细。这里我主要展示一下我的代码,并介绍一下我的Morris后序遍历方法。
前序遍历:
public void preorderMorris(TreeNode root) {
if (root == null) {
return;
}
TreeNode cur = root;
while (cur != null) {
if (cur.left != null) {
TreeNode tmp = cur.left;
while (tmp.right != null && tmp.right != cur) {
tmp = tmp.right;
}
if (tmp.right == null) {
System.out.println(cur.val);
tmp.right = cur;
cur = cur.left;
} else {
tmp.right = null;
cur = cur.right;
}
} else {
System.out.println(cur.val);
cur = cur.right;
}
}
}
中序遍历:
public void inorderMorris(TreeNode root) {
if (root == null) {
return;
}
TreeNode cur = root;
while (cur != null) {
if (cur.left != null) {
TreeNode tmp = cur.left;
while (tmp.right != null && tmp.right != cur) {
tmp = tmp.right;
}
if (tmp.right == null) {
tmp.right = cur;
cur = cur.left;
} else {
tmp.right = null;
System.out.println(cur.val);
cur = cur.right;
}
} else {
System.out.println(cur.val);
cur = cur.right;
}
}
}
后序遍历:
public List<Integer> postorderMorris(TreeNode root) {
List<Integer> list = new ArrayList<Integer> ();
if (root == null) {
return list;
}
TreeNode cur = root;
while (cur != null) {
if (cur.right != null) {
TreeNode tmp = cur.right;
while (tmp.left != null && tmp.left != cur) {
tmp = tmp.left;
}
if (tmp.left == null) {
list.add(cur.val);
tmp.left = cur;
cur = cur.right;
} else {
tmp.left = null;
cur = cur.left;
}
} else {
list.add(cur.val);
cur = cur.left;
}
}
Collections.reverse(list);
return list;
}
在引用的那篇文章的Morris后序遍历中,需要用到倒序输出的方法,这样无疑加大了代码量。这里,当最后遍历结果不是简单输出,而是保存在一个数组中时,我们可以利用先序遍历的思想,来得到后序遍历。由于先序遍历的访问顺序是:父节点-左孩子-右孩子,而后序遍历是:左孩子-右孩子-父节点,因此我们可以按照父节点-右孩子-左孩子的访问顺序来得到遍历的数组,然后将整个数组翻转,这样就能得到后序遍历的结果。其实这种思想不单单适用于Morris遍历,还适用于堆栈实现的遍历。