二叉树的非递归遍历可以依赖于栈结构解决。其中先序和中序遍历思路较为相似,后序遍历需要另外设置一个访问位变量,比前两种较为复杂一些。

首先是二叉树的构造,这里使用二叉树的先序序列,递归的方法去构造,将构造二叉树的任务分为构造多个子树的小任务。首先对树根结点调用构造二叉树的方法,在每一个节点处对左子树和右子树依次调用构造二叉树的方法。

我们本篇使用下面的树

java二叉树非递归遍历 java实现二叉树非递归遍历_子树

这里的先序序列是一个数组,在用循环结构去构造的时候,应该使索引 i 是一个全局变量,否则每一次递归地调用create()方法的时候,i 值都会是0,最后构造出来是错误的

public class BinaryTree<T> {

	public BinaryNode<T> root;
	public BinaryTree(T[] prelist){
		this.root = create(prelist);
	}
	private int i = 0;
	private BinaryNode<T> create(T[] prelist){
		BinaryNode<T> p = null;
		if(i < prelist.length) {
			T elem = prelist[i];
			i++;
			if(elem != null) {
				p = new BinaryNode<T> (elem);
				p.left = create(prelist);
				p.right = create(prelist);
			}
		}
		return p;
	}
}

这样,构造出来了二叉树的逻辑结构。
需要注意的是,构造的时候传入的数列,应该是一个完全二叉树的形式,也就是不存在的结点也要设为null,否则这个方法错误
接下来是二叉树的非递归先序遍历方法
首先结合上面的构造体会一下效果

public static void main(String[] args) {
		//必须把所有叶子节点后面跟两个null,否则输出结果不对
		String[] prelist = {"A","B","D",null, "H",null,null,"E","I",null,null,
				"J",null,null,
				"C","F",null,"K",null,null,
				"G",null,null};
		BinaryTree<String> bitree = new BinaryTree<String>(prelist);
		bitree.preorderTraverse();
	}

输出结果是:
先根次序遍历 非递归
A B D ^ H ^ ^ E I ^ ^ J ^ ^ C F ^ K ^ ^ G ^

先序遍历
先根次序遍历,我们从根节点开始,设置一个指针变量指向当前节点,一直访问左子树(指针一直指向左子树),在这个过程中遇到的每一个结点都要输出它的元素值,然后入栈,直到左子树为空,我们将栈顶出栈,指针指向栈顶元素的右子树,然后仍然是入栈然后指向左子树(这里就又递归了)。依次类推…

public void preorderTraverse() {
		System.out.println("先根次序遍历 非递归");
		LinkedStack<BinaryNode<T>> stack = new LinkedStack<BinaryNode<T>>();
		BinaryNode<T> p = this.root;
		while (p != null || !stack.isEmpty()) {
			if (p != null) {
				System.out.print(p.data + " ");
				stack.push(p);
				p = p.left;
			}else {
				System.out.print("^ ");
				p = stack.pop();
				p = p.right;
			}
		}
		System.out.println();
	}

中序遍历
然后是中序遍历,他和先序的区别也就是。先序遍历是在结点入栈的时候进行输出元素值的。而中序遍历是在元素出栈的时候输出出栈元素的元素值。怎么讲也不如体会一下代码!:

//@ author ZhaoKe
	public void inorderTraverse() {
		System.out.println("中根次序遍历 非递归");
		LinkedStack<BinaryNode<T>> stack = new LinkedStack<BinaryNode<T>>();
		BinaryNode<T> p = this.root;
		while (p != null || !stack.isEmpty()) {
			if (p != null) {
				stack.push(p);
				p = p.left;
			}else {
				System.out.print("^ ");
				p = stack.pop();
				System.out.print(p.data + " ");
				p = p.right;
			}
		}
		System.out.println();
	}

它的输出结果是:
中根次序遍历 非递归
^ D ^ H ^ B ^ I ^ E ^ J ^ A ^ F ^ K ^ C ^ G

后序遍历
后序遍历不太一样,这里比较麻烦。由于顺着二叉树的左子树去寻找最下面的右子树的叶子结点,中间一定要有结点的入栈和出栈,那么无论是入栈还是出栈去输出结点的元素值,都是先序或中序遍历的操作,后续自然不能使用了。
因此这里我们设置一个临时存储的访问变量 last,表示刚刚是哪一个结点已经被访问过,而且刚刚访问它的时候没有输出它的值。那么这里问题是何时去输出它的值。
根据上面的例子,我们过程是:

  • 仍然一直顺着左子树去到达最下面的结点, 回到上一层结点不使用出栈操作,而是“返回栈顶元素”,
  • 然后去右子树,如果右子树不为空并且刚刚未访问过,就去右子树。
  • 否则输出结点的值,并把该节点出栈赋值给变量 last,表示它被访问过。然后再回到上一层结点,
  • 该节点再次入栈,左子树为空,去右子树,右子树被访问过,那么这个结点输出并出栈,又到了上一层结点。以此类推就完成了后序遍历
//@ author ZhaoKe
	public void postorderTraverse() {
		System.out.println("后根次序遍历 非递归");
		LinkedStack<BinaryNode<T>> stack = new LinkedStack<BinaryNode<T>>();
		BinaryNode<T> last = null;
		BinaryNode<T> p = this.root;
		while (p != null || !stack.isEmpty()) {
			if (p != null) {
				stack.push(p);
				p = p.left;
			}else {
				System.out.print("^ ");
				p = stack.peek();
				if(p.right != null && last != p.right) {
					p = p.right;
				}
				else {
					System.out.print(p.data+" ");
					last  =stack.pop();
//					p = stack.peek();//这是错误演示,这会导致死循环。会导致一直进入if里面而不停地入栈出栈
					p = null;
				}	
			}//else
		}//while
		System.out.println();
	}

遍历的结果是:
后根次序遍历 非递归
^ ^ H ^ D ^ ^ I ^ ^ J ^ E ^ B ^ ^ ^ K ^ F ^ ^ G ^ C ^ A