Java语言实现二叉树的非递归遍历

版权声明:本文为博主原创文章,转载请注明出处! https://blog.csdn.net/q1406689423/article/details/85072087

很早之前写过一篇遍历二叉树的博客,那个是用递归方式进行遍历的。下面有评论写我没写非递归调用的方式进行遍历,现在进行补充一下。

二叉树遍历有三种方式,先序遍历、中序遍历、后序遍历,分别又被称为"先根遍历"、“中根遍历”、“后根遍历”,其实后一种叫法更直观也更好理解一点,让人看名就知道要干啥了,因为它们本来就是根据遍历根的先后顺序来命名的。

在遍历之前,我们先写一个用来定义结点的类BinaryTreeNode

public class BinaryTreeNode {
	String data;//数据域
	BinaryTreeNode right;//右子结点
	BinaryTreeNode left;//左子节点

	public String getData() {
		return data;
	}

	public void setData(String data) {
		this.data = data;
	}

	public BinaryTreeNode getRight() {
		return right;
	}

	public void setRight(BinaryTreeNode right) {
		this.right = right;
	}

	public BinaryTreeNode getLeft() {
		return left;
	}

	public void setLeft(BinaryTreeNode left) {
		this.left = left;
	}

	public BinaryTreeNode(String data, BinaryTreeNode right, BinaryTreeNode left) {
		super();
		this.data = data;
		this.right = right;
		this.left = left;
	}

	public BinaryTreeNode() {
		super();
	}

	public BinaryTreeNode(String data) {
		this.data = data;
	}
}

1. 先序遍历(先根遍历)

遍历的顺序是:根——左子节点——右子节点
要进行非递归遍历的话,肯定需要有用来存储数据的一种结构,在这里使用“栈”来存储。下面是实现的具体代码

private void preTravle(BinaryTreeNode node) {
		if (node == null) {
			return;
		}
		BinaryTreeNode p = node;
		Stack<BinaryTreeNode> stack = new Stack<BinaryTreeNode>();//定义一个栈用来存储需要遍历的二叉树结点
		while (p != null) {
			while (p != null) {
				System.out.println(p.getData());
				if (p.getRight() != null) {
					stack.push(p.getRight());
				}
				p = p.getLeft();
			}
			if (!stack.isEmpty()) {
				p = stack.pop();
			}
		}
	}

以上算法的思路是从二叉树根结点出发,先对其进行打印,之后判断该结点是否具有右子树,有就将该右子树存入栈中,待之后取出。之后转到左子结点进行相同的操作直到左子树为null。
当左子树为null的时候,也就说明找到了这棵树的最左结点,就进行 if 语句中的操作,从栈内将栈顶元素取出,此时的栈顶元素即为与最左元素具有相同父亲的右子结点。由于循环还未结束,所以会对以该右子结点为根结点的子树继续进行先序遍历,当到达最右叶子结点时,再从栈中取出的元素就变为了该右叶子结点的父结点。
根据以上的思路就实现了顺序为“根——左子结点——右子结点”的遍历操作。

2. 中序遍历(中根遍历)

遍历的顺序是:左子节点——根——右子节点
对于中序遍历,就不能再像先序遍历那样进来就对根结点进行打印了,而是要先找到最靠左的叶子结点。这里也继续用“栈”来存储尚未使用到的元素。
先看中序遍历的代码

private void inTravle(BinaryTreeNode node) {
		if (node == null) {
			return;
		}
		BinaryTreeNode p = node;
		Stack<BinaryTreeNode> stack = new Stack<BinaryTreeNode>();
		while (!stack.isEmpty() || p != null) {
			while (p != null) {
				stack.push(p);
				p = p.getLeft();
			}
			if (!stack.isEmpty()) {
				p = stack.pop();//取出栈顶元素
				System.out.println(p.getData());
				p = p.getRight();//访问右子树
			}
		}
	}

这里的思路是先将根结点存入栈中“备用”,之后左子结点作为父结点继续将元素存储栈中,直到找到最左的叶子结点为止。此时栈顶元素存放的就是最左叶子结点,取出进行打印即可。取出最左叶子结点之后,栈顶元素就成了最左叶子结点的父结点,打印。有了最左叶子结点的父结点,我们当然可以找到与之对应的右子结点。这样我们就实现了顺序为“左子节点——根——右子节点”的操作。

3. 后序遍历(后根遍历)

遍历的顺序是:左子节点——右子节点——根
后序遍历相比前两种要复杂,因为可能会出现重复遍历的情况,先看具体代码

private void postTravle(BinaryTreeNode node) {
		if (node== null) {
			return;
		}
		BinaryTreeNode p = node;
		Stack<BinaryTreeNode> stack = new Stack<BinaryTreeNode>();
		while (p != null || !stack.isEmpty()) {
			
			while (p != null) {
				stack.push(p);
				if (p.getLeft() != null) {
					p = p.getLeft();
				} else {
					p = p.getRight();
				}
			}
			if (!stack.isEmpty()) {
				p = stack.pop();
				System.out.println(p.getData());
			}
			while (!stack.isEmpty() && stack.peek().getRight() == p) {// 已遍历该节点的右子树,则继续遍历到上一层的父结点
				p = stack.pop();
				System.out.println(p.getData());
			}
			if (!stack.isEmpty()) {
				p = stack.peek().getRight();//通过栈顶元素取出对应的右子结点
			} else {
				p = null;
			}
		}
	}

后序遍历首先要做的事情和中序遍历思路是有些相似的,都是要先找到最左的叶子结点。但是之后的操作就不太一样了。中序遍历是从栈中取出为栈顶元素的父结点,再根据父结点找到右子结点。后序遍历是先通过作为栈顶元素的父结点找到与左子结点相对应的右子结点,却不将作为父结点的栈顶元素取出。直到右子结点中的元素打印之后,再将作为栈顶元素的父结点取出。
这里的实现方法是在找到右子结点之后再继续将该右子结点压入栈内,以便对以该右子结点作为父结点的树进行深入。在右子结点没有子结点(即右子结点为叶子结点)时,取出的栈顶元素就是它本身了(因为之前进行了压栈操作),打印即可。到这一步就实现了”左子树——右子树“的操作。那么怎样再找到父结点元素呢?当然是再次取栈顶元素,但这时候就面临一个问题,那就是我们取出该父结点之后,如果我们不做处理它就会继续执行第一个while语句中的内容,这样就会无限循环下去,我当然不是我们想要的。所以在此我们就判断一下他是否已经被访问,已经被访问就不再对其处理,继续向上找父结点即可。于是有了我们使用了第二个while语句。
通过每一次取左,取右,判断右是否已访问,取根,就实现了”左子结点——右子结点——根“的顺序对二叉树的遍历。

后序遍历的非递归操作是一个相对来说比较复杂的操作,需要进行很多次出入栈和判断,但是这样是很巧妙的。这几个非递归的遍历方法都不是很好理解,要花费一些时间,但是觉得在理解之后对自己的思维还是很有帮助的。

猜你喜欢

转载自blog.csdn.net/q1406689423/article/details/85072087