目录
一、栈实现前序遍历
二、栈实现后序遍历
三、完整的程序代码
总结
一、栈实现前序遍历
先来看看我们之前写的用递归实现前序遍历的程序代码:
/**
*********************
* Pre-order visit.
*********************
*/
public void preOrderVisit() {
System.out.print("" + value + " ");
if(leftChild != null) {
leftChild.preOrderVisit();
} // Of if
if(rightChild != null) {
rightChild.preOrderVisit();
} // Of if
} // Of preOrderVisit
通过观察分析,我们可以将上述递归函数总结如下:
(1)每次调用函数时都优先输出当前结点的值,然后再来判断它的左子树。
(2)如果当前结点的左子树不空,则把该左子树作为新的当前结点,进入新一层的递归函数;进入新一层的函数后,仍然优先输出当前结点的值,然后再来判断它的左子树。
(3)如果当前结点的左子树为空,下一步紧接着就是判断该结点的右子树。
(4)若该右子树不空,则把该右子树作为新的当前结点,进入新一层的递归函数;进入新一层的函数后,仍然优先输出当前结点的值,然后再来判断它的左子树。若该右子树为空,则结束遍历。
(5)必须将左子树全部判断完之后,再来判断对应的右子树,因此当我们前序遍历一个二叉树时,一定是下层的结点最先完成“根 左 右”,上层的结点必须等下层结点遍历完之后才能完成“根 左 右”。
接下来,我们进行递归与栈迭代的一一对应:
(1)中要求每次调用函数时都优先输出当前结点的值。用栈来实现就是得到当前结点后,立马输出其值。
(2)中要求当左子树不空时,不断的将左子树作为新的当前结点,进入新一层的递归函数,然后输出其值。用栈来实现就是利用tempNode = tempNode.leftChild进行左子树迭代,不断的将左子树变成当前结点(变成当前结点后也就得到了当前结点),然后立马输出其值。
(3)中要求当某结点左子树为空时,立马判断该结点的右子树。这个过程如果要想用栈来实现,最重要的便是想办法得到当前结点的右子树,因为我们之前进行了左子树迭代,不断的将左子树变成了当前结点,也就是说当前结点是在不断改变的,我们最多只能通过tempNode.rightChild得到最新一级的当前结点的右子树,而以往的当前结点的右子树就没办法直接获得了。这个时候,我们就要充分利用栈“先进后出”的特性了,如果我们将每一个当前结点依次入栈(相当于暂时存起来,便于后面右子树的获得),后面判断右子树时再根据需要出栈,这样就可以通过tempNode.rightChild得到每一个当前结点的右子树了。不过需要注意,入栈肯定得放在左子树迭代之前,因为只要进行一次左子树迭代,当前结点就会改变一次,所以我们应该先将当前结点入栈,然后再通过左子树迭代得到新的当前结点。
(4)中要求判断右子树时,若该右子树不空,则把该右子树作为新的当前结点,进入新一层的递归函数,然后输出其值。用栈来实现就是利用tempNode = tempNode.rightChild将右子树变成新的当前结点(变成新的当前结点也就相当于得到了此时的当前结点),然后立马输出其值。
(5)中要求必须等左子树全部判断完之后再判断对应的右子树,这其实也是前序遍历的特性所致。为了满足(3)中的要求,我们之前说将每一个当前结点依次入栈,后面判断右子树时再根据需要出栈,其实这个过程在栈“先进后出”特性的影响下,已经可以满足(5)中的要求了。因为上层结点一定比下层结点先入栈,这就使得上层结点比下层结点后出栈,而要想判断右子树必须先将其根结点出栈,然后通过tempNode.rightChild得到右子树,继而进行右子树判断,所以下层结点先出栈就可以满足下层结点先进行右子树判断,即先完成“根 左 右”;上层结点后出栈就可以满足上层结点后进行右子树判断,即后完成“根 左 右”。
我们将上述栈实现的过程进行一个总结:
- 第一步,得到当前结点,不空则立马输出
- 第二步,将该当前结点入栈
- 第三步,如果该当前结点的左子树不空,则通过tempNode = tempNode.leftChild得到新的当前结点,然后返回第一步
- 第四步,如果该当前结点的左子树为空,则下一步立马判断该当前结点的右子树,具体来说就是将此时的栈顶元素出栈(因为如果该当前结点的左子树为空时,是直接从第二步跳到第四步,所以此时的栈顶元素就是即将判断的这个右子树的根结点),再通过tempNode = tempNode.rightChild将出栈元素的右子树作为新的当前结点,然后返回第一步
用文字描述果然还是有点复杂来着,下面我们就用代码呈现这个过程,如下:
/**
*********************
* Pre-order visit with stack.
*********************
*/
public void preOrderVisitWithStack() {
ObjectStack tempStack = new ObjectStack();
BinaryCharTree tempNode = this;
while(!tempStack.isEmpty() || tempNode != null) {
if(tempNode != null) {
System.out.print("" + tempNode.value + " ");
tempStack.push(tempNode);
tempNode = tempNode.leftChild;
} else {
tempNode = (BinaryCharTree)tempStack.pop();
tempNode = tempNode.rightChild;
} // Of if
} // Of while
} // Of preOrderVisitWithStack
可以发现,栈迭代实现前序遍历与中序遍历的区别其实仅在于输出语句的位置不同。
二、栈实现后序遍历
讲栈实现后序遍历之前,我们先来看看前序遍历与后序遍历的互换:
在日撸Java三百行(day21:二叉树的深度遍历的递归实现)中我们说过由于二叉树的结构特性,其遍历顺序其实就可以看作是根结点、左子树、右子树这三者的一个先后访问顺序,根据数学中的排列组合知识,我们可以知道一共应该有6种顺序,
具体包括(1)根 左 右(2)左 根 右(3)左 右 根(4)根 右 左(5)右 根 左(6)右 左 根,不难发现前三种就是我们最常用的前中后序遍历,因为人类总是习惯于“先左后右”,而后三种其实就是将前三种进行了“左右交换”。
在这6种顺序中,如果将前序遍历“根 左 右”的左右子树交换,就可以得到(4)根 右 左,再逆序即可得到“左 右 根”,也就是后序遍历。至此,我们也就完成了从前序遍历到后序遍历的转换。
下面,我们就来用栈实现后序遍历:
- 第一步,完全拷贝前序遍历的代码,只需要将左右子树交换即可,也就是将前序遍历中所有的tempNode.leftChild改成tempNode.rightChild,所有的tempNode.rightChild改成tempNode.leftChild
- 第二步,逆序输出。要完成逆序输出,需要我们再创建一个“输出栈”,将原来前序遍历中的输出语句全部改成当前结点入栈“输出栈”(该过程可以使得原来在前序遍历中最先输出的元素,会最先进入输出栈,也就位于输出栈的栈底,后续出栈时就会最后才输出),然后再将该“输出栈”中的元素依次输出即可完成逆序输出,也就完成了后序遍历
具体的代码如下:
/**
*********************
* Post-order visit with stack.
*********************
*/
public void postOrderVisitWithStack() {
ObjectStack tempStack = new ObjectStack();
BinaryCharTree tempNode = this;
ObjectStack tempOutputStack = new ObjectStack();
while (!tempStack.isEmpty() || tempNode != null) {
if (tempNode != null) {
//Store for output.
tempOutputStack.push(new Character(tempNode.value));
tempStack.push(tempNode);
tempNode = tempNode.rightChild;
} else {
tempNode = (BinaryCharTree) tempStack.pop();
tempNode = tempNode.leftChild;
} // Of if
} // Of while
//Now reverse output.
while (!tempOutputStack.isEmpty()) {
System.out.print("" + tempOutputStack.pop() + " ");
}//Of while
}// Of postOrderVisitWithStack
三、完整的程序代码
完整的程序代码:
package datastructure.tree;
import datastructure.*;
import java.util.Arrays;
/**
* Binary tree with char type elements.
*
*@auther Xin Lin 3101540094@qq.com.
*/
public class BinaryCharTree {
/**
* The value
*/
char value;
/**
* The left child
*/
BinaryCharTree leftChild;
/**
* The right child
*/
BinaryCharTree rightChild;
/**
*********************
* The first constructor.
*
* @param paraName The value.
*********************
*/
public BinaryCharTree(char paraName) {
value = paraName;
leftChild = null;
rightChild = null;
} // Of constructor
/**
*********************
* Manually construct a tree. Only for testing.
*********************
*/
public static BinaryCharTree manualConstructTree() {
// Step 1. Construct a tree with only one node.
BinaryCharTree resultTree = new BinaryCharTree('a');
// Step 2. Construct all Nodes. The first node is the root.
// BinaryCharTree tempTreeA = resultTree.root;
BinaryCharTree tempTreeB = new BinaryCharTree('b');
BinaryCharTree tempTreeC = new BinaryCharTree('c');
BinaryCharTree tempTreeD = new BinaryCharTree('d');
BinaryCharTree tempTreeE = new BinaryCharTree('e');
BinaryCharTree tempTreeF = new BinaryCharTree('f');
BinaryCharTree tempTreeG = new BinaryCharTree('g');
// Step 3. Link all Nodes.
resultTree.leftChild = tempTreeB;
resultTree.rightChild = tempTreeC;
tempTreeB.rightChild = tempTreeD;
tempTreeC.leftChild = tempTreeE;
tempTreeD.leftChild = tempTreeF;
tempTreeD.rightChild = tempTreeG;
return resultTree;
} // Of manualConstructTree
/**
*********************
* Pre-order visit.
*********************
*/
public void preOrderVisit() {
System.out.print("" + value + " ");
if(leftChild != null) {
leftChild.preOrderVisit();
} // Of if
if(rightChild != null) {
rightChild.preOrderVisit();
} // Of if
} // Of preOrderVisit
/**
*********************
* In-order visit.
*********************
*/
public void inOrderVisit() {
if(leftChild != null) {
leftChild.inOrderVisit();
} // Of if
System.out.print("" + value + " ");
if(rightChild != null) {
rightChild.inOrderVisit();
} // Of if
} // Of inOrderVisit
/**
*********************
* Post-order visit.
*********************
*/
public void postOrderVisit() {
if(leftChild != null) {
leftChild.postOrderVisit();
} // Of if
if(rightChild != null) {
rightChild.postOrderVisit();
} // Of if
System.out.print("" + value + " ");
} // Of postOrderVisit
/**
*********************
* Get the depth of the binary char tree.
*
* @return The depth.
*********************
*/
public int getDepth() {
if((leftChild == null) && (rightChild == null)) {
return 1;
} // Of if
// The depth of the left child.
int tempLeftDepth = 0;
if(leftChild != null) {
tempLeftDepth = leftChild.getDepth();
} // Of if
// The depth of the right child.
int tempRightDepth = 0;
if(rightChild != null) {
tempRightDepth = rightChild.getDepth();
} // Of if
if(tempLeftDepth >= tempRightDepth) {
return tempLeftDepth + 1;
} else {
return tempRightDepth + 1;
} // Of if
} // Of getDepth
/**
*********************
* Get the number of nodes of the binary char tree.
*
* @return The number of nodes.
*********************
*/
public int getNumNodes() {
if((leftChild == null) && (rightChild == null)) {
return 1;
} // Of if
// The number of nodes of the left child.
int tempLeftNodes = 0;
if(leftChild != null) {
tempLeftNodes = leftChild.getNumNodes();
} // Of if
// The number of nodes of the right child.
int tempRightNodes = 0;
if(rightChild != null) {
tempRightNodes = rightChild.getNumNodes();
} // Of if
// The total number of nodes.
return tempLeftNodes + tempRightNodes + 1;
} // Of getNumNodes
/**
* The values of nodes according to breadth first traversal.
*/
char[] valuesArray;
/**
* The indices in the complete binary tree.
*/
int[] indicesArray;
/**
********************
* Convert the tree to data arrays, including a char array and an int array.
* The results are stored in two member variables.
*
* @see #valuesArray
* @see #indicesArray
*********************
*/
public void toDataArrays() {
//Initialize arrays.
int tempLength = getNumNodes();
valuesArray = new char[tempLength];
indicesArray = new int[tempLength];
int i = 0;
//Traverse and convert at the same time.
CircleObjectQueue tempQueue = new CircleObjectQueue();
tempQueue.enqueue(this);
CircleIntQueue tempIntQueue = new CircleIntQueue();
tempIntQueue.enqueue(0);
BinaryCharTree tempTree = (BinaryCharTree) tempQueue.dequeue();
int tempIndex = tempIntQueue.dequeue();
while (tempTree != null) {
valuesArray[i] = tempTree.value;
indicesArray[i] = tempIndex;
i++;
if (tempTree.leftChild != null) {
tempQueue.enqueue(tempTree.leftChild);
tempIntQueue.enqueue(tempIndex * 2 + 1);
} // Of if
if (tempTree.rightChild != null) {
tempQueue.enqueue(tempTree.rightChild);
tempIntQueue.enqueue(tempIndex * 2 + 2);
} // Of i
tempTree = (BinaryCharTree) tempQueue.dequeue();
tempIndex = tempIntQueue.dequeue();
} // Of while
} // Of toDataArrays
/**
********************
* Convert the tree to data arrays, including a char array and an int array.
* The results are stored in two member variables.
*
* @see #valuesArray
* @see #indicesArray
*********************
*/
public void toDataArraysObjectQueue() {
//Initialize arrays.
int tempLength = getNumNodes();
valuesArray = new char[tempLength];
indicesArray = new int[tempLength];
int i = 0;
//Traverse and convert at the same time.
CircleObjectQueue tempQueue = new CircleObjectQueue();
tempQueue.enqueue(this);
CircleObjectQueue tempIntQueue = new CircleObjectQueue();
Integer tempIndexInteger = Integer.valueOf(0);
tempIntQueue.enqueue(tempIndexInteger);
BinaryCharTree tempTree = (BinaryCharTree) tempQueue.dequeue();
int tempIndex = ((Integer)tempIntQueue.dequeue()).intValue();
System.out.println("tempIndex = " + tempIndex);
while (tempTree != null) {
valuesArray[i] = tempTree.value;
indicesArray[i] = tempIndex;
i++;
if (tempTree.leftChild != null) {
tempQueue.enqueue(tempTree.leftChild);
tempIntQueue.enqueue(Integer.valueOf(tempIndex * 2 + 1));
} // Of if
if (tempTree.leftChild != null) {
tempQueue.enqueue(tempTree.leftChild);
tempIntQueue.enqueue(Integer.valueOf(tempIndex * 2 + 2));
} // Of if
tempTree = (BinaryCharTree) tempQueue.dequeue();
if (tempTree == null) {
break;
} // Of if
tempIndex = ((Integer)tempIntQueue.dequeue()).intValue();
} // Of while
} // Of toDataArraysObjectQueue
/**
*********************
* The second constructor. The parameters must be correct since no validity
* check is undertaken.
*
* @param paraDataArray The array for data.
* @param paraIndicesArray The array for indices.
*********************
*/
public BinaryCharTree(char[] paraDataArray, int[] paraIndicesArray) {
// Step 1. Use a sequential list to store all nodes.
int tempNumNodes = paraDataArray.length;
BinaryCharTree[] tempAllNodes = new BinaryCharTree[tempNumNodes];
for(int i = 0; i < tempNumNodes; i++) {
tempAllNodes[i] = new BinaryCharTree(paraDataArray[i]);
} // Of for i
// Step 2. Link all nodes.
for(int i = 1; i < tempNumNodes; i++) {
for(int j = 0; j < i; j++) {
System.out.println("Indices " + paraIndicesArray[j] + " vs. " + paraIndicesArray[i]);
if(paraIndicesArray[i] == paraIndicesArray[j] * 2 + 1) {
tempAllNodes[j].leftChild = tempAllNodes[i];
System.out.println("Linking " + j + " with " + i);
break;
} // Of if
if(paraIndicesArray[i] == paraIndicesArray[j] * 2 + 2) {
tempAllNodes[j].rightChild = tempAllNodes[i];
System.out.println("Linking " + j + " with " + i);
break;
} // Of if
} // Of for j
} // Of for i
// Step 3. The root is the first node.
value = tempAllNodes[0].value;
leftChild = tempAllNodes[0].leftChild;
rightChild = tempAllNodes[0].rightChild;
} // Of the the second constructor
/**
*********************
* In-order visit with stack.
*********************
*/
public void inOrderVisitWithStack() {
ObjectStack tempStack = new ObjectStack();
BinaryCharTree tempNode = this;
while(!tempStack.isEmpty() || tempNode != null) {
if(tempNode != null) {
tempStack.push(tempNode);
tempNode = tempNode.leftChild;
} else {
tempNode = (BinaryCharTree)tempStack.pop();
System.out.print("" + tempNode.value + " ");
tempNode = tempNode.rightChild;
} // Of if
} // Of while
} // Of inOrderVisitWithStack
/**
*********************
* Pre-order visit with stack.
*********************
*/
public void preOrderVisitWithStack() {
ObjectStack tempStack = new ObjectStack();
BinaryCharTree tempNode = this;
while(!tempStack.isEmpty() || tempNode != null) {
if(tempNode != null) {
System.out.print("" + tempNode.value + " ");
tempStack.push(tempNode);
tempNode = tempNode.leftChild;
} else {
tempNode = (BinaryCharTree)tempStack.pop();
tempNode = tempNode.rightChild;
} // Of if
} // Of while
} // Of preOrderVisitWithStack
/**
*********************
* Post-order visit with stack.
*********************
*/
public void postOrderVisitWithStack() {
ObjectStack tempStack = new ObjectStack();
BinaryCharTree tempNode = this;
ObjectStack tempOutputStack = new ObjectStack();
while (!tempStack.isEmpty() || tempNode != null) {
if (tempNode != null) {
//Store for output.
tempOutputStack.push(new Character(tempNode.value));
tempStack.push(tempNode);
tempNode = tempNode.rightChild;
} else {
tempNode = (BinaryCharTree) tempStack.pop();
tempNode = tempNode.leftChild;
} // Of if
} // Of while
//Now reverse output.
while (!tempOutputStack.isEmpty()) {
System.out.print("" + tempOutputStack.pop() + " ");
}//Of while
}// Of postOrderVisitWithStack
/**
*********************
* The entrance of the program.
*
* @param args Not used now.
*********************
*/
public static void main(String args[]) {
BinaryCharTree tempTree = manualConstructTree();
System.out.println("\r\nPreorder visit:");
tempTree.preOrderVisit();
System.out.println("\r\nIn-order visit:");
tempTree.inOrderVisit();
System.out.println("\r\nPost-order visit:");
tempTree.postOrderVisit();
System.out.println("\r\n\r\nThe depth is: " + tempTree.getDepth());
System.out.println("The number of nodes is: " + tempTree.getNumNodes());
tempTree.toDataArrays();
System.out.println("The values are: " + Arrays.toString(tempTree.valuesArray));
System.out.println("The indices are: " + Arrays.toString(tempTree.indicesArray));
tempTree.toDataArraysObjectQueue();
System.out.println("Only object queue.");
System.out.println("The values are: " + Arrays.toString(tempTree.valuesArray));
System.out.println("The indices are: " + Arrays.toString(tempTree.indicesArray));
char[] tempCharArray = {'A', 'B', 'C', 'D', 'E', 'F'};
int[] tempIndices = {0, 1, 2, 4, 5, 12};
BinaryCharTree tempTree2 = new BinaryCharTree(tempCharArray, tempIndices);
System.out.println("\r\nPreorder visit:");
tempTree2.preOrderVisit();
System.out.println("\r\nIn-order visit:");
tempTree2.inOrderVisit();
System.out.println("\r\nPost-order visit:");
tempTree2.postOrderVisit();
System.out.println("\r\nIn-order visit with stack:");
tempTree2.inOrderVisitWithStack();
System.out.println("\r\nPre-order visit with stack:");
tempTree2.preOrderVisitWithStack();
System.out.println("\r\nPost-order visit with stack:");
tempTree2.postOrderVisitWithStack();
}// Of main
} // Of class BinaryCharTree
运行结果:
显然,我们今天用栈迭代实现的前序遍历、后序遍历,与之前使用递归实现的前序遍历、后序遍历,其结果是一样的。
总结
今天的主要内容就是利用栈迭代实现二叉树的前序遍历和后序遍历,其实就是昨日内容的一个延续。由于有了昨天分析准备和相关代码的基础,今天进行起来就没有那么困难了,夸张一点说,经过这两天栈实现二叉树遍历的运用,我感觉我的迭代思维得到了升华…
除了递归与迭代的转换,今天我们还介绍了一种非常重要也非常好用的思想——等价替换,即将一个复杂又难以解决的问题,替换为一个等价且比较容易解决的问题。在高中阶段学习数学时,我的数学老师就特别喜欢用等价替换,每次都能把一个巨难的数学题转换成一个简单的基础题;而今天,面对栈实现后序遍历的问题,也是通过等价替换,将复杂的操作转化成了比较简单的过程,对此,我只想说,等价替换真的重要又好用,不过等价替换难就难在如何才能灵活的实现这种等价。