Java 树与二叉树
学习来源: 日撸 Java 三百行(21-30天,树与二叉树)
一、二叉树的深度遍历的递归实现
- 二叉树的遍历比存储、建立要简单. 所以先“手动”建立一个二叉树来玩.
- 递归算法写起来就是舒服. 前、中、后序的代码差别只有输出语句的位置.
- 不需要额外的节点类, 每棵二叉树 (树) 都可以从自己的根结点找到其它所有节点. 这个需要自悟.
- 获取二叉树的层次、总节点数, 也需要递归. 以后可能要用, 这里就一并写了.
题目一:二叉树的前序遍历
输出:二叉树前序遍历序列
示例:
a b d f g c e
题目二:二叉树的中序遍历
输出:二叉树中序遍历序列
示例:
b f d g a e c
题目二:二叉树的后序遍历
输出:二叉树后序遍历序列
示例:
f g d b e c a
题目三:求二叉树的深度
输出:二叉树深度
The depth is: 4
题目四:求二叉树的结点个数
输出:二叉树结点个数
The number of nodes is : 7
代码如下:
package datastructure.tree;
/**
*
* @author Ling Lin E-mail:linling0.0@foxmail.com
*
* @version 创建时间:2022年4月17日 下午3:37:46
*
*/
public class BinaryCharTree {
// The value in char.
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 the 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.
// BinaryCharTreeNode 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.
*
* @param args
*/
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
*
* @param args
*/
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 tree.
*
* @return The depth. It is 1 if there is only one node, i.e, the root.
*/
public int getDepth() {
// It is a leaf.
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
// The depth should increment by 1.
if (tempLeftDepth >= tempRightDepth) {
return tempLeftDepth + 1;
} else {
return tempRightDepth + 1;
} // Of if
}// Of getDepth
/**
* Get the number of nodes.
*
* @return The number of nodes.
*
*/
public int getNumNodes() {
// It is a leaf.
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 left child.
int tempRightNodes = 0;
if (rightChild != null) {
tempRightNodes = rightChild.getNumNodes();
} // Of if
// The total number of nodes.
return tempLeftNodes + tempRightNodes + 1;
}// Of getNumNodes
/**
* The entrance of the program.
*
* @param args
* Not used now.
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
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());
}// Of main
}// Of BinaryCharTree
运行结果:
二、二叉树的存储
二叉树的存储并非一个简单的问题. 引用 (指针) 是无法存储到文件里面的.
我们可以从完全满二叉树的角度广度优先遍历的角度来考虑这个问题: 每个节点都有一个 name 及其在二叉树中的位置. 令根节点的位置为 0; 则第 2 层节点的位置依次为 1 至 2; 第 3 层节点的位置依次为 3 至 6. 以此类推.
把昨天那个例子所对应的二叉树画出来, 我们有两种方法:
空使用 0 来表示, 可以用一个向量来存储:
[a, b, c, 0, d, e, 0, 0, 0, f, g]
优点: 仅需要一个向量, 简单直接.
缺点: 对于实际的二叉树, 很多子树为空, 导致大量的 0 值.
刘知鑫指出: 应使用压缩存储方式, 即将节点的位置和值均存储. 可表示为两个向量:
[0, 1, 2, 4, 5, 9, 10]
[a, b, c, d, e, f, g]
由于递归程序涉及变量的作用域问题 (额, 这个真的有点头疼), 我们需要使用层次遍历的方式, 获得以上的几个向量. 为此, 需要用到队列. 我们先灌点水, 把队列的代码再写一遍, 但这次装的是对象 (的引用).
代码如下:
package datastructure.queue;
/**
*
* @author Ling Lin E-mail:linling0.0@foxmail.com
*
* @version 创建时间:2022年4月17日 下午4:37:53
*
*/
public class CircleObjectQueue {
// The total space. One space can never be used.
public static final int TOTAL_SPACE = 10;
// The data.
Object[] data;
// The index of the head.
int head;
// The index of the tail.
int tail;
/**
* The constructor
*/
public CircleObjectQueue() {
data = new Object[TOTAL_SPACE];
head = 0;
tail = 0;
}//Of the first constructor
/**
* Enqueue.
*
* @param paraValue
* The value of the new node.
*/
public void enqueue(Object paraValue) {
if ((tail + 1) % TOTAL_SPACE == head) {
System.out.println("Queue full.");
return;
}//Of if
data[tail % TOTAL_SPACE] = paraValue;
tail++;
}//Of enqueue
/*
* Dequeue.
*
* @return The value at the head.
*/
public Object dequeue() {
if (head == tail) {
//System.out.println("No element in the queue");
return null;
}//Of if
Object resultValue = data[head % TOTAL_SPACE];
head++;
return resultValue;
}//Of dequeue
/**
* Overrides the method claimed in Object, the superclass of any class.
*/
@Override
public String toString() {
String resultString = "";
if (head == tail) {
return "empty";
}//Of if
for (int i = head; i < tail; i++) {
resultString += data[i % TOTAL_SPACE] + ", ";
}//Of for i
return resultString;
}//Of toString
/**
* The entrance of the program.
*
* @param args
* Not used now.
*/
public static void main(String args[]) {
CircleObjectQueue tempQueue = new CircleObjectQueue();
}//Of main
}//Of CircleObjectQueue
数据转换的代码很短, 但要理解还是比较花时间.
用了两个队列.
实际上涵盖了广度优先 (或称层次) 遍历.
节点左儿子的编号是其编号 2 倍再加 1; 右儿子则应加 2.
main 前面的保留, 只是为了读者方便与前面的程序对标.
需要 import datastructure.queue.*;
如下:
package datastructure.tree;
import java.util.Arrays;
import datastructure.queue.CircleIntQueue;
import datastructure.queue.CircleObjectQueue;
/**
*
* @author Ling Lin E-mail:linling0.0@foxmail.com
*
* @version 创建时间:2022年4月17日 下午3:37:46
*
*/
public class BinaryCharTree {
// The value in char.
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 the 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.
// BinaryCharTreeNode 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.
*
* @param args
*/
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
*
* @param args
*/
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 tree.
*
* @return The depth. It is 1 if there is only one node, i.e, the root.
*/
public int getDepth() {
// It is a leaf.
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
// The depth should increment by 1.
if (tempLeftDepth >= tempRightDepth) {
return tempLeftDepth + 1;
} else {
return tempRightDepth + 1;
} // Of if
}// Of getDepth
/**
* Get the number of nodes.
*
* @return The number of nodes.
*
*/
public int getNumNodes() {
// It is a leaf.
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 left 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 if
tempTree = (BinaryCharTree) tempQueue.dequeue();
tempIndex = tempIntQueue.dequeue();
}//Of while
}//Of toDataArrays
/**
*********************
* 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));
}// Of main
}// Of class BinaryCharTree
运行结果: