引言
本文介绍了二叉树的先/中/后序遍历的递归实现和非递归实现、深度优先搜索以及层次遍历的实现。
先序遍历
所谓先序遍历,就是先访问根节点,再访问左右子节点。在这些遍历策略中,左子节点的访问都在右节点访问之前。
我们以这样一颗二叉树为例,其实它是更为严格的二叉搜索树。
对该树进行先序遍历,输出:3 1 0 8 5 16 11 10 13 19
其访问顺序如下虚线箭头所示:
递归版本
/**
* 先序遍历(先根遍历):先访问根节点,再访问左右子节点
*/
public void preOrder() {
preOrder(root);
System.out.println();
}
private void preOrder(Node<E> node) {
if (node != null) {
System.out.print(node.data + " ");
preOrder(node.left);
preOrder(node.right);
}
}
递归版本的实现非常简单,注意访问当前节点代码System.out.print(node.data + " ");
的位置,是在访问左右孩子节点之前。这就是先序遍历的递归版本实现。
但是由于递归版本用到了栈,如果树中节点过多,会导致栈溢出异常。因此是非常有必要研究非递归版本的实现的。
非递归版本
观察上图,可以发现每次访问一个新节点后,都尝试访问左孩子,然后左孩子的左孩子。直到左孩子为空,或者左孩子已经被访问过。
可以总结一下,对于任何子树,访问根节点后,都会去访问它的左子树,从局部来看,就像沿着左侧节点不停的下行。直到左子树为空后,转而去访问它的右子树,对于该右子树,同样沿着左侧节点下行。我们称这些左侧节点为左侧链。
我们的算法根据左侧链展开。
我们可以将每一条左侧链突出的绘制出来。
是左侧链上最深的节点。最大深度为d。 表示以 右孩子节点 为根的右子树。
整个二叉树的先序遍历访问顺序如上图所示。当达到最深的节点后,继而访问它的右孩子(也可能不存在),进而依次访问它们的右子树(对于右子树也是沿着左侧链访问)。 右孩子节点 是最后访问的,这符合栈结构的特性。
我们根据这个过程来实现代码:
/**
* 非递归实现先序遍历
*/
public void preOrderNonRecursion() {
preOrderNonRecursion(root);
System.out.println();
}
private void preOrderNonRecursion(Node<E> node) {
//栈中缓存的是右子树根节点(可能为空)
Stack<Node<E>> stack = new Stack<>();
while (true) {
visitLeft(node,stack);
//访问了之后,判断栈是否为空
if (stack.isEmpty()) {
//访问完毕,跳出
break;
}
//弹出下一(右)子树的根节点(可能为空),对于右子树继续沿着左侧链下行
node = stack.pop();
}
}
private void visitLeft(Node<E> node,Stack<Node<E>> stack) {
while (node != null) {
//先访问根节点
System.out.print(node.data + " ");
//将右孩子入栈(可能为空)
stack.push(node.right);
//继续沿左侧下行
node = node.left;
}
}
中序遍历
就是先访左子节点,其次是根节点,最后是右子节点
对该树进行中序遍历,输出:0 1 3 5 8 10 11 13 16 19。(对BST进行中序遍历可有序输出)
其访问顺序如下虚线箭头所示:
递归版本
/**
* 中序遍历=:先访问左子节点,再访问根节点,最后访问右子节点
*/
public void inOrder() {
inOrder(root);
System.out.println();
}
private void inOrder(Node<E> node) {
if (node != null) {
inOrder(node.left);
System.out.print(node.data + " ");
inOrder(node.right);
}
}
非递归版本
开始从整颗树的根节点3
介入,依照先访问左子树的原则,我们此时不访问3
,而是继续向下到1
,同样它有左孩子,继续向下到0
,此时0
没有左孩子(其他位置可以看成左孩子已经访问过),接下来就可以访问0
了。然后是0
的右孩子,这里为空,因此以0
为根的子树已经访问完毕。可以看成0
被删除掉了。接下来访问以1
为根的子树的根节点,然后是右孩子(很巧,还是没有)。按照这个策略,继续下去。
整体来看,访问顺序为:从根节点开始,一直沿着左侧分支,逐层向下,直到不能再向下,再访问(最左侧节点的)根节点,然后是其右子树**。对右子树也按照同样的顺序访问。
同样地,我们可以抽象成这样一个访问顺序。
我们先看左边红框中的结构。在访问 时,会先访问它的左孩子 ,因为 没有左孩子,所以可以访问它,然后是它的右孩子 ,在访问完 的右孩子后,即 的左子树访问完毕,此时才可以访问 。
再看上图右边,也是和先序遍历类似的左侧链结构,不同的是按照上面所说的访问顺序进行访问的。第一个访问的是最左侧节点 ,其次是它的右子树,然后是它的根节点,根节点的右子树…以此类推。
/**
* 非递归实现中序遍历
*/
public void inOrderNonRecursion() {
inOrderNonRecursion(root);
System.out.println();
}
private void inOrderNonRecursion(Node<E> node) {
Stack<Node<E>> stack = new Stack<>();
while (true) {
//沿着左侧链下行
goLeft(node,stack);
if (stack.isEmpty()) {
break;
}
//弹出最左侧元素,其左子树为空(上图的Ld)或已经访问过
node = stack.pop();
//访问它
System.out.print(node.data + " ");
//接下来访问右子树(可能为空),相当于 goLeft(node.right,stack);
node = node.right;
}
}
private void goLeft(Node<E> node,Stack<Node<E>> stack) {
while (node != null) {
//沿左侧分支,反复地入栈
stack.push(node);
node = node.left;
}
}
后序遍历
就是先访左子节点,其次是右子节点,最后才是根节点。
递归版本
/**
* 后序遍历:先访问左子节点,再访问右子节点,最后访问根节点
*/
public void postOrder() {
postOrder(root);
System.out.println();
}
private void postOrder(Node<E> node) {
if (node != null) {
postOrder(node.left);
postOrder(node.right);
System.out.print(node.data + " ");
}
}
层次遍历
层次遍历挺简单的,主要利用了队列的先进先出。一层一层的遍历即可,类似图的广度优先遍历。
/**
* 层次遍历
*/
public void levelOrder() {
levelOrder(root);
System.out.println();
}
/**
* 和图的广度优先遍历类似,也需要用到队列
* 一层一层的遍历
*
* @param node
*/
private void levelOrder(Node<E> node) {
if (node != null) {
Queue<Node<E>> queue = new Queue<>();
queue.enqueue(node);
while (!queue.isEmpty()) {
Node<E> p = queue.dequeue();
System.out.print(p.data + " ");
if (p.left != null) {
queue.enqueue(p.left);
}
if (p.right != null) {
queue.enqueue(p.right);
}
}
}
}
深度优先搜索
/**
* 深度优先遍历,相当于先序遍历
*/
public void depthOrder() {
depthOrder(root);
System.out.println();
}
/**
* 和图的深度优先搜索类似
*
* @param node
*/
private void depthOrder(Node<E> node) {
if (node != null) {
Stack<Node<E>> stack = new Stack<>();
stack.push(node);
while (!stack.isEmpty()) {
Node<E> p = stack.pop();
System.out.print(p.data + " ");
//先push right,再push left 达到pop时先访问left的效果
if (p.right != null) {
stack.push(p.right);
}
if (p.left != null) {
stack.push(p.left);
}
}
}
}