学习来源:日撸 Java 三百行(21-30天,树与二叉树)_闵帆的博客——CSDN博客
一、树
1. 树的定义
树是n(n>0)个节点的有限集。当结点个数为0时,树称为空树。
任意一棵非空树应当满足:①有且仅有一个特定的称为根的结点;②当n>1时,其余结点可分为m(m>0) 个互不相交的有限集,其中每个集合本身又是一棵树,并称为根的子树。
二、二叉树
1. 二叉树的定义
二叉树是一种树形结构,其特点是每个结点至多只有两棵子树,且二叉树的子树有左右之分,其次序不能颠倒。
2. 二叉树的深度遍历的递归实现
package JavaDay5;
/**
* @author Kexiong Wang
*
* @date 2022年4月19日
*
* 字符型变量二叉树的深度优先遍历递归实现
*/
public class BinaryTree {
//节点值
char value;
//左子树
BinaryTree leftChild;
//右子树
BinaryTree rightChild;
/**
***********
* 构造函数
*
* @param paraValue 节点的值
***********
*/
public BinaryTree(char paraValue) {
value = paraValue;
leftChild = null;
rightChild = null;
}//Of BinaryTree
/**
***********
* 手动创建二叉树
***********
*/
public static BinaryTree manualConstructTree() {
//创建根节点
BinaryTree resultTree = new BinaryTree('a');
//创建除根节点外的其他节点
BinaryTree tempTreeB = new BinaryTree('b');
BinaryTree tempTreeC = new BinaryTree('c');
BinaryTree tempTreeD = new BinaryTree('d');
BinaryTree tempTreeE = new BinaryTree('e');
BinaryTree tempTreeF = new BinaryTree('f');
BinaryTree tempTreeG = new BinaryTree('g');
//链接所有节点
resultTree.leftChild = tempTreeB;
resultTree.rightChild = tempTreeC;
tempTreeB.leftChild = tempTreeD;
tempTreeB.rightChild = tempTreeE;
tempTreeC.leftChild = tempTreeF;
tempTreeC.rightChild = tempTreeG;
return resultTree;
}//Of manualConstructTree
/**
***********
* 先序遍历
***********
*/
public void preOrderVisit() {
System.out.print("" + value + " ");
//若左子树非空,则遍历左子树
if(leftChild != null) {
leftChild.preOrderVisit();
}//Of if
//若右子树非空,则遍历右子树
if(rightChild != null) {
rightChild.preOrderVisit();
}//Of if
}//Of preOrderVisit
/**
***********
* 中序遍历
***********
*/
public void inOrderVisit() {
//若左子树非空,则遍历左子树
if(leftChild != null) {
leftChild.inOrderVisit();
}//Of if
System.out.print("" + value + " ");
//若右子树非空,则遍历右子树
if(rightChild != null) {
rightChild.inOrderVisit();
}//Of if
}//Of inOrderVisit
/**
***********
* 后序遍历
***********
*/
public void postOrderVisit() {
//若左子树非空,则遍历左子树
if(leftChild != null) {
leftChild.postOrderVisit();
}//Of if
//若右子树非空,则遍历右子树
if(rightChild != null) {
rightChild.postOrderVisit();
}//Of if
System.out.print("" + value + " ");
}//Of postOrderVisit
/**
***********
* 求二叉树的高度
*
* @return 二叉树高度,从1开始计数
***********
*/
public int getDepth() {
//叶节点的高度
if((leftChild == null) && (rightChild == null)) {
return 1;
}//Of if
//左子树的高度
int tempLeftDepth = 0;
if(leftChild != null) {
tempLeftDepth = leftChild.getDepth();
}//Of if
//右子树的高度
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
/**
***********
* 求二叉树的节点数
*
* @return 节点数
***********
*/
public int getNumNodes() {
//叶节点
if ((leftChild == null) && (rightChild == null)) {
return 1;
}//Of if
//左子树节点数
int tempLeftNodes = 0;
if(leftChild != null) {
tempLeftNodes = leftChild.getNumNodes();
}//Of if
//右子树节点数
int tempRightNodes = 0;
if(rightChild != null) {
tempRightNodes = rightChild.getNumNodes();
}//Of if
//反回左子树节点加右子树节点加根节点
return tempLeftNodes + tempRightNodes + 1;
}//Of getNumNodes
/**
***********
* 程序入口
*
* @param args 暂未使用
***********
*/
public static void main(String args[]) {
BinaryTree 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 BinaryTree
运行结果
3. 二叉树的存储
二叉树采用顺序存储时,可以用一个数组存储数中节点的值,另一个数组用来存对应节点在完全二叉树中的序号。
使用到的具有通用性的队列结构
package JavaDay5;
/**
* @author Kexiong Wang
*
* @date 2022年4月19日
*
* 通过队列使用层序遍历实现顺序存储
*/
public class CircleObjectQueue {
//最大队长
public static final int MAX_SIZE = 10;
//队列存储空间
Object[] data;
//队头
int head;
//队尾
int tail;
/**
***********
* 构造函数
***********
*/
public CircleObjectQueue() {
data = new Object[MAX_SIZE];
head = 0;
tail = 0;
}//Of BinaryStore
/**
***********
* 入队
*
* @param paraValue 入队节点的值
***********
*/
public void enqueue(Object paraValue) {
//队满,入队失败
if ((tail + 1) % MAX_SIZE == head) {
System.out.println("Queue full!");
return;
}//Of if
data[tail % MAX_SIZE] = paraValue;
tail++;
}//Of enqueue
/**
***********
* 出队
*
* @return 队头节点的值
***********
*/
public Object dequeue() {
if (head == tail) {
System.out.println("The queue is empty");
return null;
}//Of if
Object resultValue = data[head % MAX_SIZE];
head++;
return resultValue;
}//Of dequeue
/**
***********
* 将元素转为字符串
***********
*/
public String toString() {
String resultString = "";
if (head == tail) {
return "The queue is empty";
}//Of if
for(int i = head; i < tail; i++) {
resultString += data[i % MAX_SIZE] + ", ";
}//Of for i
return resultString;
}//Of toString
/**
***********
* 程序入口
*
* @param args 暂未使用
***********
*/
public static void main(String args[]) {
}//Of main
}//Of BinaryStore
在前面二叉树的定义代码中添加存储的代码
//节点在完全二叉树中的序号
int[] indicesArray;
//对应序号节点的值
char[] valuesArray;
/**
***********
* 将二叉树存到对应的两个数组中
***********
*/
public void toDataArrays() {
//表长
int tempLength = getNumNodes();
valuesArray = new char[tempLength];
indicesArray = new int[tempLength];
int i = 0;
//边遍历边存入数组
CircleObjectQueue tempQueue = new CircleObjectQueue();
tempQueue.enqueue(this);
CircleIntQueue tempIntQueue = new CircleIntQueue();
tempIntQueue.enqueue(0);
BinaryTree tempTree = (BinaryTree) 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 = (BinaryTree) tempQueue.dequeue();
tempIndex = tempIntQueue.dequeue();
}//Of while
}//Of toDataArrays
/**
***********
* 程序入口
*
* @param args 暂未使用
***********
*/
public static void main(String args[]) {
BinaryTree 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 BinaryTree
运行结果
4. 二叉树的建立
此过程相当于二叉树的存储的逆过程,由两个分别存放二叉树节点元素及对应元素在完全二叉树中序号的数组来构造二叉树。在二叉树定义的代码中添加另一个带有两个数组参数的构造方法。
/**
***********
* 第二个构造函数
*
* @param paraDataArray 数据数组
* @param paraIndicesArray 序号数组
***********
*/
public BinaryTree(char[] paraDataArray, int[] paraIndicesArray) {
//用数组存元素节点
int tempNumNodes = paraDataArray.length;
BinaryTree[] tempAllNodes = new BinaryTree[tempNumNodes];
for (int i = 0; i < tempNumNodes; i ++) {
tempAllNodes[i] = new BinaryTree(paraDataArray[i]);
}//Of for i
//链接所有节点
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;
} else 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
//根节点
value = tempAllNodes[0].value;
leftChild = tempAllNodes[0].leftChild;
rightChild = tempAllNodes[0].rightChild;
}//Of BinaryTree
/**
***********
* 程序入口
*
* @param args 暂未使用
***********
*/
public static void main(String args[]) {
char[] tempCharArray = {'A','B','C','D','E','F'};
int[] tempIndicesArray = {0,1,2,4,5,6};
BinaryTree tempTree2 = new BinaryTree(tempCharArray,tempIndicesArray);
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
运行结果
5. 具有通用性的栈
package JavaDay5;
/**
* @author Kexiong Wang
*
* @date 2022年4月19日
*/
public class ObjectStack {
//最大栈长
public static final int MAX_DEPTH = 10;
//栈长
int depth;
//存储数据的数组
Object[] data;
/**
***********
* 创建空栈
***********
*/
public ObjectStack() {
depth = 0;
data = new Object[MAX_DEPTH];
}//Of ObjectStack
/**
***********
* 将栈元素转为字符串
***********
*/
public String toString() {
String resultString = "";
for (int i = 0; i < depth; i++) {
resultString += data[i];
}//Of for i
return resultString;
}//Of toString
/**
***********
* 入栈
*
* @param paraObject 入栈元素
* @return 入栈是否成功
***********
*/
public boolean push(Object paraObject) {
if (depth == MAX_DEPTH) {
System.out.println("Stack full!");
return false;
}//Of if
data[depth] = paraObject;
depth++;
return true;
}//Of push
/**
***********
* 出栈
*
* @return 出栈元素
***********
*/
public Object pop() {
if (depth == 0) {
System.out.println("Nothing to pop.");
return '\0';
}//of if
Object resultObject = data[depth - 1];
depth--;
return resultObject;
}//Of pop
/**
***********
* 判空
*
* @return 是否为空
***********
*/
public boolean isEmpty() {
if (depth == 0) {
return true;
}//Of if
return false;
}//Of isEmpty
/**
***********
* 程序入口
*
* @param args 暂未使用
***********
*/
public static void main(String args[]) {
}//Of main
}//Of ObjectStack
6. 二叉树先序、中序、后序遍历的栈实现
二叉树中序遍历的栈实现较为简单,先深度优先遍历左子树,沿途节点都一次入栈,当遍历到空节点时,弹出栈顶元素。若栈顶元素右子树不为空,重复上述操作;若右子树为空,则输出当前元素并再次弹出栈顶元素并检测右子树是否为空,重复以上操作。
在借助栈进行先序遍历时,与中序遍历的区别仅在于输出语句的顺序(先输出元素再入栈)。
进行后序遍历时,考虑先序遍历的顺序为NLR(中左右),将左右子树互换位置,则顺序变为NRL,在用栈将输出顺序翻转,则输出顺序为LRN即后序遍历的顺序。
/**
***********
* 中序遍历栈实现
***********
*/
public void inOrderVisitWithStack() {
ObjectStack tempStack = new ObjectStack();
BinaryTree tempNode = this;
while (!tempStack.isEmpty() || tempNode != null) {
if (tempNode != null) {
tempStack.push(tempNode);
tempNode = tempNode.leftChild;
} else {
tempNode = (BinaryTree) tempStack.pop();
System.out.print("" + tempNode.value + " ");
tempNode = tempNode.rightChild;
}//Of if
}//Of while
}//Of inOrderVisit
/**
***********
* 先序遍历栈实现
***********
*/
public void preOrderVisitWithStack() {
ObjectStack tempStack = new ObjectStack();
BinaryTree tempNode = this;
while (!tempStack.isEmpty() || tempNode != null) {
if (tempNode != null) {
System.out.print("" + tempNode.value + " ");
tempStack.push(tempNode);
tempNode = tempNode.leftChild;
} else {
tempNode = (BinaryTree) tempStack.pop();
tempNode = tempNode.rightChild;
}//Of if
}//Of while
}//Of preOrderVisitWithStack
/**
***********
* 后序遍历栈实现
***********
*/
public void postOrderVisitWithStack() {
ObjectStack tempStack = new ObjectStack();
BinaryTree tempNode = this;
ObjectStack tempOutputStack = new ObjectStack();
while (!tempStack.isEmpty() || tempNode != null) {
if (tempNode != null) {
//存储输出元素
tempOutputStack.push(new Character(tempNode.value));
tempStack.push(tempNode);
tempNode = tempNode.rightChild;
} else {
tempNode = (BinaryTree) tempStack.pop();
tempNode = tempNode.leftChild;
}//Of if
}//Of while
//输出反转
while (!tempOutputStack.isEmpty()) {
System.out.print("" + tempOutputStack.pop() + " ");
}//Of while
}//Of postOrderVisitWithStack
/**
***********
* 程序入口
*
* @param args 暂未使用
***********
*/
public static void main(String args[]) {
char[] tempCharArray = {'A','B','C','D','E','F'};
int[] tempIndicesArray = {0,1,2,4,5,6};
BinaryTree tempTree2 = new BinaryTree(tempCharArray,tempIndicesArray);
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\nPre-order visit with stack:");
tempTree2.preOrderVisitWithStack();
System.out.println("\r\nIn-order visit with stack:");
tempTree2.inOrderVisitWithStack();
System.out.println("\r\nPost-order visit with stack:");
tempTree2.postOrderVisitWithStack();
}//Of main
运行结果
三、总结
学习了树特别是二叉树的概念。结合前面所学的栈和队列的知识,实现了二叉树的顺序存储和链式存储。
结合面向对象的知识实现了通用性的栈和队列数据结构,在使用时不再受输入数据类型的限制,非常方便。
通过栈和队列实现二叉树的先、中、后序的遍历。尤其是在实现后序遍历的过程中,考虑到通过改变所要遍历二叉树的结构来实现后序而不是完全的按照后序来访问原二叉树,极大程度降低了代码的复杂度。