二叉树的遍历是数据结构这门课中一个很基础的要求,通常我们会使用递归的方式来遍历二叉树,递归也比较利于理解。

而对于二叉树的非递归遍历,通常我们采用栈的方式实现。下面就分别说一下如何使用栈来实现二叉树的先序、中序、后序遍历。

首先给出二叉树的数据结构:

/**
* @author thomas
* @version 1.0
* @date 2019/12/20 23:30
* TODO
**/
public class TreeNode {

private String value;

private TreeNode leftChild;

private TreeNode rightChild;

public TreeNode() {}
public TreeNode(String value) {
this.value = value;
}

public String getValue() {
return value;
}

public void setValue(String value) {
this.value = value;
}

public Boolean hasLeftChild() {
return leftChild != null;
}

public Boolean hasRightChild() {
return rightChild != null;
}

public void setLeftChild(TreeNode node) {
this.leftChild = node;
}

public void setRightChild(TreeNode node) {
this.rightChild = node;
}

public TreeNode getLeftChild() {
return leftChild;
}

public TreeNode getRightChild() {
return rightChild;
}
}

先序遍历(先根遍历)

先序遍历的顺序是

  • 先访问根节点
  • 再访问左子树
  • 再访问右子树

简记遍历顺序就是根左右

我们可以使用一个栈来模拟这个访问过程:

  • 先将根节点入栈
  • 然后弹出栈顶元素并打印
  • 然后将右孩子入栈,再将左孩子入栈(注意顺序),然后转到第二步,直到栈中元素为空。

转换成代码很简单

/**
* @author thomas
* @date 2019/12/20 23:34
* 非递归先序遍历二叉树
**/
public void preOrder(TreeNode root) {
Stack<TreeNode> stack = new Stack<>();
stack.push(root);
while (!stack.empty()) {
// 栈顶元素出栈并打印之
TreeNode top = stack.pop();
System.out.print(top.getValue());
// 右孩子、左孩子入栈
if (top.hasRightChild()) {
stack.push(top.getRightChild());
}
if (top.hasLeftChild()) {
stack.push(top.getLeftChild());
}

}

}

后序遍历(后根遍历)

后序遍历的顺序是:

  • 先访问左孩子
  • 再访问右孩子
  • 再访问根节点

简记遍历顺序就是左右根

回想一下先序遍历的顺序,根左右,如果我们把左右子树入栈的顺序变换一下,那么遍历的顺序就变成了根右左,然后我们在之前打印的时候不执行打印操作,而是改为将要打印的元素再压入一个新的栈里,然后再依次将元素出栈打印,那不就得到了我们想要的后序遍历的顺序左右根吗?

按照这个想法,我们将上面的代码可以修改为:

/**
* @author thomas
* @date 2019/12/21 0:02
* 非递归后序遍历二叉树
**/
public void postOrder(TreeNode root) {
Stack<TreeNode> preOrderStack = new Stack<>();
Stack<TreeNode> postOrderStack = new Stack<>();
preOrderStack.push(root);
while (!preOrderStack.empty()) {
// 栈顶元素出栈并打印之
TreeNode top = preOrderStack.pop();
// 将原本的打印操作改为压入新的栈中
inOrderStack.push(top);
if (top.hasLeftChild()) {
preOrderStack.push(top.getLeftChild());
}
if (top.hasRightChild()) {
preOrderStack.push(top.getRightChild());
}

}
// 出栈依次打印
while (!inOrderStack.empty()) {
System.out.print(inOrderStack.pop().getValue());
}

}

中序遍历(中根遍历)

中序遍历的顺序是:

  • 先访问左孩子
  • 再访问根节点
  • 再访问右孩子

按照这个顺序,我们可以得出以下步骤:

  • 访问根节点,压栈
  • 访问根节点的左孩子,继续压栈,循环这个过程,直到栈顶元素没有左孩子
  • 开始处理栈中的元素,栈不为空时循环。每次弹出栈顶元素并访问之。
  • 将弹出的栈顶元素的右孩子作为根节点,跳到第一步执行,直到栈内元素为空

转换成代码如下:

/**
* @author thomas
* @date 2019/12/21 0:22
* 非递归中序遍历
**/
public void inOrder(TreeNode root) {
Stack<TreeNode> stack = new Stack<>();
stack.push(root);
// 先找到最左子节点
TreeNode p = stack.peek().getLeftChild();
while (p != null ) {
stack.push(p);
p = p.getLeftChild();
}
while (!stack.empty()) {
// 栈顶元素出栈并访问之
TreeNode peak = stack.pop();
System.out.print(peak.getValue());
// 如果有右孩子则压栈
if (peak.hasRightChild()) {
stack.push(peak.getRightChild());
// 压栈之后寻找最左子节点
p = stack.peek().getLeftChild();
while (p != null ) {
stack.push(p);
p = p.getLeftChild();
}
}
}
}

以上就是非递归遍历二叉树的方法了,虽然代码不复杂,但还是很值得深入理解其中的思想的。