目录
一、分析准备
在日撸Java三百行(day22:二叉树的存储)中,我们学习的是如何将链表二叉树转换为顺序表二叉树进行存储,而今天我们要学习的是逆过程,即通过两个顺序表(包括数据顺序表和下标顺序表)建立链表二叉树。
根据链表的特性,要想建立一个链表二叉树,需要我们创建结点,然后再把所有结点链接起来。显然,在这个过程中,最重要的就是根据结点之间的逻辑关系正确进行链接,所以,我们先来看看树的结点之间的联系。
像下图这种完全二叉树,它们有一个非常良好的性质,就是左子树的下标=其根结点下标 * 2 + 1,右子树下标=其根结点 * 2 + 2。例如:结点b的下标为1,其根结点a的下标为0,二者满足0 * 2 + 1 = 1;结点e的下标为4,其根结点b的下标为1,二者满足1 * 2 + 2 = 4。
而对于非完全二叉树,我们同样可以采用完全二叉树的方式进行编号,即先把这个非完全二叉树转换为对应的完全二叉树,然后再依次编号,这样非完全二叉树的根结点与左右子树的下标关系仍满足上述规律。例如:下图中的结点d下标为4,其左子树f下标为9,二者满足4 * 2 + 1 = 9,其右子树g下标为10,二者满足4 * 2 + 2 = 10。
综上所述,我们得出结论:如果结点B的下标=结点A的下标 * 2 + 1,那么B就是A的左子树;如果结点C的下标=结点A的下标 * 2 + 2,那么C就是A的右子树。
此外,还需要注意以下几点:
- 在二叉树中,随着结点从上往下、从左往右,其下标是逐渐变大的
- 对于任何结点,其前驱结点的下标都比它自身的下标小,也就是说不管是在二叉树中还是在顺序表中,前驱结点的位置都在当前结点之前
- 顺序表中的第一个结点总是二叉树的根结点,其是没有父结点的
二、代码实现
有了上述结论后,我们就可以开始代码模拟了,大体的思路是:创建一个结点顺序表存放所有结点,然后遍历该顺序表,在遍历的过程中,枚举当前遍历结点的所有前驱结点,看它们的下标是否满足我们总结出来的结论,若满足,则说明找到了当前结点的根结点,直接进行链接即可。
1.方法创建
java
/**
*********************
* 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
根据之前的分析,我们需要创建一个方法,以达到输入两个顺序表(数据顺序表和下标顺序表)得到一个链表二叉树的目的,大体步骤如下:
第一步,我们创建一个顺序表tempAllNodes用来存放所有的结点。由于tempAllNodes存放的是结点,而结点是二叉树类BinaryCharTree的对象,因此我们创建的这个tempAllNodes其实就是二叉树类BinaryCharTree的对象的集合,所以定义时使用BinaryCharTree修饰;同时tempAllNodes的最大长度,其实就等于输入的数据顺序表paraDataArray的长度。然后,利用一个for循环,将数据顺序表paraDataArray中的数据元素(即字符),一个一个拷贝到结点顺序表tempAllNodes中。
第二步,链接所有结点。创建一个两层for循环,外层for循环用来遍历整个结点顺序表(我们之前说过顺序表中的第一个结点是二叉树的根结点,所以此处 i 是从1开始的),内层for循环用于枚举当前遍历结点的所有前驱结点(由于前驱结点的位置始终在当前结点之前,所以这里 j < i )。然后我们利用之前总结的结论进行判断,若满足" * 2 + 1"的关系,则链接为左子树;若满足" * 2 + 2"的关系,则链接为右子树。
第三步,将根结点(即顺序表tempAllNodes的第一个结点)赋给二叉树类BinaryCharTree的成员变量。
至此,我们就完成了"通过两个顺序表建立一个链表二叉树"的方法创建。
2.数据测试
接着,我们开始数据测试,并通过前序遍历、中序遍历、后序遍历进行检验,如下:
java
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();
3.完整的程序代码
java
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
/**
*********************
* 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();
}// Of main
} // Of class BinaryCharTree
运行结果
总结
总的来说,今天的代码还是比较简单的,只在之前代码的基础上增加了一个构造方法和相应的数据测试。在具体实施过程中,我觉得最难的就是分清楚下标顺序表的真实索引值和下标顺序表中存放的值(下标顺序表中存放的值,即为结点按照完全二叉树模式进行的编号下标)。
通过今天的学习,我们其实可以发现,不同数据结构之间可以进行灵活的转换,而现实世界中的各种逻辑结构又总是非常复杂,所以当以后我们想要用数据结构去灵活地表示某种复杂的逻辑时,就可以考虑进行可逆的转换。