在二叉樹的一些應用中,常常要求在樹中查找具有某些特徵的結點,或者對樹中全部結點逐一進行某種處理。這就提出了一個遍歷二叉樹的問題,即如果按某條搜索路徑巡訪樹中的每個結點,使得每個結點均被訪問一次,而且僅被訪問一次。“訪問”的含義很廣,可以是對結點作各種處理,如輸出結點的信息等。遍歷對於線性結構來說是一個很容易解決的問題,而對二叉樹則不然,因爲二叉樹是一種非線性結構。
回顧二叉樹的定義,我們知道二叉樹可以看成是由三個部分組成的:一個根結點,根的左子樹和根的右子樹。因此如果能夠遍歷這三部分,則可以遍歷整棵二叉樹。如果用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
可見,每個算法的結果都完全正確。