在二叉树的一些应用中,常常要求在树中查找具有某些特征的结点,或者对树中全部结点逐一进行某种处理。这就提出了一个遍历二叉树的问题,即如果按某条搜索路径巡访树中的每个结点,使得每个结点均被访问一次,而且仅被访问一次。“访问”的含义很广,可以是对结点作各种处理,如输出结点的信息等。遍历对于线性结构来说是一个很容易解决的问题,而对二叉树则不然,因为二叉树是一种非线性结构。
回顾二叉树的定义,我们知道二叉树可以看成是由三个部分组成的:一个根结点,根的左子树和根的右子树。因此如果能够遍历这三部分,则可以遍历整棵二叉树。如果用L、D、R分别表示遍历左子树、访问根结点、遍历右子树。那么对于二叉树的遍历次序就可以有6中方案,即L、D、R的排列组合,那么如果限制对左子树的遍历要先于对右子树的遍历,这就剩下3中情况,分别是:
- (1)访问根,遍历左子树,遍历右子树(DLR)
- (2)遍历左子树,访问根,遍历右子树(LDR)
- (3)遍历左子树,遍历右子树,访问根(LRD)
根据对根访问的不同顺序,分别成DLR为先根(序)遍历,LDR为中根(序)遍历,LRD为后根(序)遍历。
需要注意的是,这里的先序遍历、中序遍历和后序遍历是递归定义的,即在左右子树中也是按相应的规律进行遍历。
例如,如上图所示的二叉树表示下述表达式:
4 + (6 - 8 + 2 * 2)*2
若先序遍历此二叉树,按访问结点的先后次序将结点排列起来,可得二叉树的先序序列为:
+ 4 * + - 6 8 * 2 2 2 (式1)
类似的,中序遍历此二叉树,可得此二叉树的中序序列为:
4 + 6 - 8 + 2 * 2 * 2 (式2)
后序遍历此二叉树,可得此二叉树的后序序列为:
4 6 8 - 2 2 * + 2 * + (式3)
从表达式上看,式1、2、3恰好是表达式的前缀表示(波兰式)、中缀表示和后缀表示(逆波兰式)。
我们可以很简单地写出遍历二叉树的递归算法,同时,根据递归时工作栈的状态变化情况,我们也可以很简单地写出遍历二叉树的非递归算法。
每一个遍历的非递归算法都有两种思路,如果借助访问标记的话,我们可以根据递归的特点,手动地用一个栈来保存每一个结点,举例来说,先序遍历时先访问根,再访问左子树,接着访问右子树,即DLR,那么我们对于每一个结点,都按照RLD的顺序入栈,如果一个结点的左右子树已经入过栈,那么该结点视为已访问过的,下一次再遇到的话直接从栈中弹出,并且访问即可;而如果一个结点是未访问过的,则先将其弹出,再按照RLD的顺序,将其右孩子、左孩子入栈,接着自己再入栈。重复这个过程直到栈为空。这种方法实现和理解起来都比较简单。
另一种思路不需要借助访问标记,但是也要借助一个栈来实现,这种方式理解起来比上一种方法稍微有点复杂。
那么下述代码,实现了二叉树的递归与非递归的遍历方法。并且,每一种非递归方法都采用至少两种思路实现。
另外,我们是通过二叉树的顺序存储结构(即数组)来创建二叉树,与此同时,也实现了二叉树的层序遍历。
import java.util.HashMap;
import java.util.Map;
import com.gavin.datastructure.queue.ArrayQueue;
import com.gavin.datastructure.stack.ArrayStack;
/**
* 实现二叉树的构建以及各种遍历
*
* @author Gavin
*
* @param <T>
*/
public class BinaryTree<T> {
/**
* 从顺序存储结构,即数组中构建二叉树
*
* @param array
* 数组
* @return 返回二叉树根结点
*/
public TripleLinkedNode<T> createTreeFromArray(T[] array) {
if (array == null || array.length == 0) {
return null;
}
Map<Integer, TripleLinkedNode<T>> nodeMap = new HashMap<>();
for (int i = 0; i < array.length; i++) {
// 如果值为空,说明当前结点不存在
if (array[i] == null) {
continue;
}
TripleLinkedNode<T> currentNode = null;
if (nodeMap.containsKey(i)) {
// 当前结点已存在
currentNode = nodeMap.get(i);
} else {
// 当前结点不存在
currentNode = new TripleLinkedNode<T>(array[i]);
// 加入
nodeMap.put(i, currentNode);
}
// 设置当前结点的左孩子
if (2 * i + 1 < array.length && array[2 * i + 1] != null) {
TripleLinkedNode<T> leftNode = new TripleLinkedNode<T>(array[2 * i + 1]);
currentNode.setLeft(leftNode);
nodeMap.put(2 * i + 1, leftNode);
}
// 设置当前结点的右孩子
if (2 * i + 2 < array.length && array[2 * i + 2] != null) {
TripleLinkedNode<T> rightNode = new TripleLinkedNode<T>(array[2 * i + 2]);
currentNode.setRight(rightNode);
nodeMap.put(2 * i + 2, rightNode);
}
}
// 返回根结点root
return nodeMap.get(0);
}
/**
* 访问一个结点
*
* @param node
*/
public void visit(TripleLinkedNode<T> node) {
System.out.print(node.getData() + " ");
}
/**
* 将访问标记复位为false
*
* @param root
*/
public void resetVisited(TripleLinkedNode<?> root) {
if (root == null) {
return;
}
root.setVisited(false);
resetVisited(root.getLeft());
resetVisited(root.getRight());
}
/**
* 递归实现前序遍历
*
* @param root
*/
public void preOrderTraverse(TripleLinkedNode<T> root) {
if (root == null) {
return;
}
visit(root);
preOrderTraverse(root.getLeft());
preOrderTraverse(root.getRight());
}
/**
* 递归实现后续遍历
*
* @param root
*/
public void postOrderTraverse(TripleLinkedNode<T> root) {
if (root == null) {
return;
}
postOrderTraverse(root.getLeft());
postOrderTraverse(root.getRight());
visit(root);
}
/**
* 递归实现中序遍历
*
* @param root
*/
public void inOrderTraverse(TripleLinkedNode<T> root) {
if (root == null) {
return;
}
inOrderTraverse(root.getLeft());
visit(root);
inOrderTraverse(root.getRight());
}
/**
* 层序遍历
*
* @param root
*/
public void levelOrderTraverse(TripleLinkedNode<T> root) {
// 要使用一个队列
ArrayQueue<TripleLinkedNode<T>> queue = new ArrayQueue<>();
queue.enqueue(root);
while (!queue.isEmpty()) {
TripleLinkedNode<T> currentNode = queue.dequeue();
// 访问队头结点
visit(currentNode);
// 左孩子入队
if (currentNode.hasLeft()) {
queue.enqueue(currentNode.getLeft());
}
// 右孩子入队
if (currentNode.hasRight()) {
queue.enqueue(currentNode.getRight());
}
}
}
/**
* 使用访问标记的非递归前序遍历
*
* @param root
*/
public void preOrderTraverseNoRecursionWithVisited(TripleLinkedNode<T> root) {
ArrayStack<TripleLinkedNode<T>> stack = new ArrayStack<>();
TripleLinkedNode<T> node = root;
stack.push(node);
while (!stack.isEmpty()) {
node = stack.peek();
if (node.isVisited() || (!node.hasLeft() && !node.hasRight())) {
// 已被访问过或者是叶子结点,则直接访问
visit(node);
stack.pop();
continue;
}
// 先弹出
stack.pop();
if (node.hasRight()) {
stack.push(node.getRight());
}
if (node.hasLeft()) {
stack.push(node.getLeft());
}
// 再加入
node.setVisited(true);
stack.push(node);
}
}
/**
* 不使用访问标记的非递归实现前序遍历
*
* @param root
*/
public void preOrderTraverseNoRecursionWithoutVisited(TripleLinkedNode<T> root) {
// 要用到一个栈
ArrayStack<TripleLinkedNode<T>> stack = new ArrayStack<>();
TripleLinkedNode<T> node = root;
while (node != null) {
// 向左走到底
while (node != null) {
visit(node); // 访问根结点
// 将该根结点的右孩子入栈
if (node.hasRight()) {
stack.push(node.getRight());
}
node = node.getLeft();
}
// 右子树根退栈遍历右子树
if (!stack.isEmpty()) {
node = stack.pop();
}
}
}
/**
* 不使用访问标记的非递归实现前序遍历2:简单好理解
*
* @param root
*/
public void preOrderTraverseNoRecursionWithoutVisited2(TripleLinkedNode<T> root) {
// 要用到一个栈
ArrayStack<TripleLinkedNode<T>> stack = new ArrayStack<>();
TripleLinkedNode<T> node = root;
stack.push(node);
while (!stack.isEmpty()) {
node = stack.pop();
visit(node);
if (node.hasRight()) {
stack.push(node.getRight());
}
if (node.hasLeft()) {
stack.push(node.getLeft());
}
}
}
/**
* 使用访问标记的非递归中序遍历
*
* @param root
*/
public void inOrderTraverseNoRecursionWithVisited(TripleLinkedNode<T> root) {
ArrayStack<TripleLinkedNode<T>> stack = new ArrayStack<>();
TripleLinkedNode<T> node = root;
stack.push(node);
while (!stack.isEmpty()) {
node = stack.peek();
if (node.isVisited() || (!node.hasLeft() && !node.hasRight())) {
visit(node);
stack.pop();
continue;
}
// 先弹出
stack.pop();
if (node.hasRight()) {
stack.push(node.getRight());
}
// 再加入
node.setVisited(true);
stack.push(node);
if (node.hasLeft()) {
stack.push(node.getLeft());
}
}
}
/**
* 不使用访问标记的非递归实现中序遍历
*
* @param root
*/
public void inOrderTraverseNoRecursionWithoutVisited(TripleLinkedNode<T> root) {
// 要用到一个栈
ArrayStack<TripleLinkedNode<T>> stack = new ArrayStack<>();
TripleLinkedNode<T> node = root;
while (node != null || !stack.isEmpty()) {
// 向左走到尽头
while (node != null) {
stack.push(node);
node = node.getLeft();
}
if (!stack.isEmpty()) {
// 取出根结点并访问
node = stack.pop();
visit(node);
// 转向根的右子树进行遍历
node = node.getRight();
}
}
}
/**
* 使用访问标记的非递归后序遍历
*
* @param root
*/
public void postOrderTraverseNoRecursionWithVisited(TripleLinkedNode<T> root) {
// 要用到一个栈
ArrayStack<TripleLinkedNode<T>> stack = new ArrayStack<>();
TripleLinkedNode<T> node = root;
stack.push(node);
while (!stack.isEmpty()) {
node = stack.peek();
// 如果曾经已经访问过其孩子,这里直接访问即可。或者是叶子结点的话,这里也直接访问
if (node.isVisited() || (!node.hasLeft() && !node.hasRight())) {
// 叶子结点
visit(node);
stack.pop();
continue;
}
// 先加入右孩子
if (node.hasRight()) {
stack.push(node.getRight());
}
// 再加入左孩子
if (node.hasLeft()) {
stack.push(node.getLeft());
}
// 其子结点加入栈,这里设置其访问标记为true
node.setVisited(true);
}
}
/**
* 不使用访问标记的非递归后序遍历
*
* @param root
*/
public void postOrderTraverseNoRecursionWithoutVisited(TripleLinkedNode<T> root) {
ArrayStack<TripleLinkedNode<T>> stack = new ArrayStack<>();
TripleLinkedNode<T> node = root;
while (node != null || !stack.isEmpty()) {
// 先左后右不断深入
while (node != null) {
// 将根结点入栈
stack.push(node);
if (node.hasLeft()) {
node = node.getLeft();
} else {
node = node.getRight();
}
}
if (!stack.isEmpty()) {
// 取出栈顶元素访问
node = stack.pop();
visit(node);
}
// 满足条件时,说明栈顶根结点右子树已经访问过,应出栈访问之
while (!stack.isEmpty() && stack.peek().getRight() == node) {
node = stack.pop();
visit(node);
}
// 转向栈顶根结点的右子树继续后续遍历
if (!stack.isEmpty()) {
node = stack.peek().getRight();
} else {
node = null;
}
}
}
/**
* 不使用访问标记的非递归后序遍历的第二种解法:使用两个栈,简单也好理解
*
* @param root
*/
public void postOrderTraverseNoRecursionWithoutVisited2(TripleLinkedNode<T> root) {
ArrayStack<TripleLinkedNode<T>> stack = new ArrayStack<>();
ArrayStack<TripleLinkedNode<T>> output = new ArrayStack<>();
TripleLinkedNode<T> node = root;
stack.push(node);
while (!stack.isEmpty()) {
node = stack.pop();
output.push(node);
if (node.hasLeft()) {
stack.push(node.getLeft());
}
if (node.hasRight()) {
stack.push(node.getRight());
}
}
while (!output.isEmpty()) {
visit(output.pop());
}
}
}
下面代码对当前的二叉树遍历进行测试:
import static org.junit.Assert.*;
import org.junit.Test;
public class BinaryTreeTest {
@Test
public void testCreateTreeFromArray() {
BinaryTree<String> bt = new BinaryTree<>();
String[] array = { "+", "4", "*", null, null, "+", "2", null, null, null, null, "-", "*", null, null, null,
null, null, null, null, null, null, null, "6", "8", "2", "2", null, null, null, null };
TripleLinkedNode<String> root = bt.createTreeFromArray(array);
assertEquals(11, root.getSize());
assertEquals(5, root.getHeight());
// 遍历
System.out.println("递归先序遍历:");
bt.preOrderTraverse(root);
System.out.println();
System.out.println("使用访问标记的非递归先序遍历:");
bt.preOrderTraverseNoRecursionWithVisited(root);
System.out.println();
System.out.println("不使用访问标记的非递归先序遍历1:");
bt.preOrderTraverseNoRecursionWithoutVisited(root);
// 标记复位
bt.resetVisited(root);
System.out.println();
System.out.println("不使用访问标记的非递归先序遍历2:");
bt.preOrderTraverseNoRecursionWithoutVisited2(root);
System.out.println();
System.out.println();
System.out.println("递归中序遍历:");
bt.inOrderTraverse(root);
System.out.println();
System.out.println("使用访问标记的非递归中序遍历:");
bt.inOrderTraverseNoRecursionWithVisited(root);
System.out.println();
System.out.println("不使用访问标记的非递归中序遍历:");
bt.inOrderTraverseNoRecursionWithoutVisited(root);
//标记复位
bt.resetVisited(root);
System.out.println();
System.out.println();
System.out.println("递归后序遍历:");
bt.postOrderTraverse(root);
System.out.println();
System.out.println("使用访问标记的非递归后序遍历:");
bt.postOrderTraverseNoRecursionWithVisited(root);
System.out.println();
System.out.println("不使用访问标记的非递归后序遍历1:");
bt.postOrderTraverseNoRecursionWithoutVisited(root);
bt.resetVisited(root);
System.out.println();
System.out.println("不使用访问标记的非递归后序遍历2:");
bt.postOrderTraverseNoRecursionWithoutVisited2(root);
System.out.println();
System.out.println();
System.out.println("层序遍历:");
bt.levelOrderTraverse(root);
}
}
输出如下:
递归先序遍历:
+ 4 * + - 6 8 * 2 2 2
使用访问标记的非递归先序遍历:
+ 4 * + - 6 8 * 2 2 2
不使用访问标记的非递归先序遍历1:
+ 4 * + - 6 8 * 2 2 2
不使用访问标记的非递归先序遍历2:
+ 4 * + - 6 8 * 2 2 2
递归中序遍历:
4 + 6 - 8 + 2 * 2 * 2
使用访问标记的非递归中序遍历:
4 + 6 - 8 + 2 * 2 * 2
不使用访问标记的非递归中序遍历:
4 + 6 - 8 + 2 * 2 * 2
递归后序遍历:
4 6 8 - 2 2 * + 2 * +
使用访问标记的非递归后序遍历:
4 6 8 - 2 2 * + 2 * +
不使用访问标记的非递归后序遍历1:
4 6 8 - 2 2 * + 2 * +
不使用访问标记的非递归后序遍历2:
4 6 8 - 2 2 * + 2 * +
层序遍历:
+ 4 * + 2 - * 6 8 2 2
可见,每个算法的结果都完全正确。