日撸Java三百行(day24:二叉树的建立)

目录

一、分析准备

二、代码实现

1.方法创建

2.数据测试

3.完整的程序代码

总结


一、分析准备

在日撸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.方法创建

    /************************ 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 ifif(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.数据测试

接着,我们开始数据测试,并通过前序遍历、中序遍历、后序遍历进行检验,如下:

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.完整的程序代码

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 ifif(rightChild != null) {rightChild.preOrderVisit();} // Of if} // Of preOrderVisit/************************ In-order visit.**********************/public void inOrderVisit() {if(leftChild != null) {leftChild.inOrderVisit();} // Of ifSystem.out.print("" + value + " ");if(rightChild != null) {rightChild.inOrderVisit();} // Of if} // Of inOrderVisit/************************ Post-order visit.**********************/public void postOrderVisit() {if(leftChild != null) {leftChild.postOrderVisit();} // Of ifif(rightChild != null) {rightChild.postOrderVisit();} // Of ifSystem.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 ifif(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 ifif (tempTree.rightChild != null) {tempQueue.enqueue(tempTree.rightChild);tempIntQueue.enqueue(tempIndex * 2 + 2);} // Of itempTree = (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 ifif (tempTree.leftChild != null) {tempQueue.enqueue(tempTree.leftChild);tempIntQueue.enqueue(Integer.valueOf(tempIndex * 2 + 2));} // Of iftempTree = (BinaryCharTree) tempQueue.dequeue();if (tempTree == null) {break;} // Of iftempIndex = ((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 ifif(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

运行结果

总结

总的来说,今天的代码还是比较简单的,只在之前代码的基础上增加了一个构造方法和相应的数据测试。在具体实施过程中,我觉得最难的就是分清楚下标顺序表的真实索引值和下标顺序表中存放的值(下标顺序表中存放的值,即为结点按照完全二叉树模式进行的编号下标)。

通过今天的学习,我们其实可以发现,不同数据结构之间可以进行灵活的转换,而现实世界中的各种逻辑结构又总是非常复杂,所以当以后我们想要用数据结构去灵活地表示某种复杂的逻辑时,就可以考虑进行可逆的转换。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/401452.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

一套完整的NVR方案与部分NVR录像机GUI源码剖析

一、部分功能展示 1.1 通道管理部分 在NVR系统中&#xff0c;通道管理是核心功能之一。通过通道管理&#xff0c;用户可以对连接的摄像头进行配置和监控。 通道连接使能&#xff1a;用户可以选择开启或关闭特定通道的连接功能&#xff0c;以实现灵活的设备管理。 时间同步&…

Kali Linux 三种网络攻击方法总结(DDoS、CC 和 ARP 欺骗)

一、引言 在当今数字化的时代&#xff0c;网络安全成为了至关重要的议题。了解网络攻击的方法和原理不仅有助于我们增强防范意识&#xff0c;更是网络安全领域专业人员必备的知识。Kali Linux 作为一款专为网络安全专业人员和爱好者设计的操作系统&#xff0c;提供了丰富的工具…

VideoPlayer插件的用法

文章目录 1. 概念介绍2. 使用方法2.1 实现步骤2.2 具体细节 3. 示例代码4. 内容总结 我们在上一章回中介绍了"如何获取文件类型"相关的内容&#xff0c;本章回中将介绍如何播放视频.闲话休提&#xff0c;让我们一起Talk Flutter吧。 1. 概念介绍 播放视频是我们常用…

Windows11下wsl闪退的解决

wsl闪退 1. 原因分析 解释&#xff1a;WSL&#xff08;Windows Subsystem for Linux&#xff09;闪退通常指的是在Windows操作系统中运行的Linux环境突然关闭。这可能是由于多种原因造成的&#xff0c;包括系统资源不足、WSL配置问题、兼容性问题或者是Linux内核的问题。&…

【Python学习-UI界面】PyQt5 小部件13-Slider 拖动条

高级布局管理器&#xff0c;允许通过拖动边界来动态改变子小部件的大小。 Splitter控件提供一个手柄&#xff0c;可以拖动以调整控件的大小 样式如下: 常用方法如下&#xff1a; 序号方法描述1addWidget将小部件添加到拆分器的布局中2indexOf返回布局中小部件的索引3insetW…

MySQL架构与数据库基础

文章目录 一、数据库概念二、数据库的简单概念三、SQL四、MySQL架构 一、数据库概念 数据库是一个以某种由组织的方式存储的数据集合。我们可以把数据库想象称为一个文件柜。此文件柜是一个存放数据的物理位置&#xff0c;不管数据是什么以及如何组织的。数据库本质也需要像文…

EMC学习笔记2——电磁兼容问题分析

分析一个电磁兼容问题一般从三方面入手&#xff0c;分别是骚扰源、敏感源、耦合路径。解决掉其中一个问题&#xff0c;就能解决大部分的电磁兼容问题。 例如&#xff1a;当骚扰源是雷电时&#xff0c;敏感源是电子线路时&#xff0c;我们需要消除的就是耦合电路。 耦合路径就是…

LLM - 微调(Fine-Tuning) Llama3 以及合并微调模型 教程

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://spike.blog.csdn.net/article/details/141218047 在微调 Llama3 大模型时&#xff0c;需要注意一些事项&#xff1a; 合适的预训练模型&#xff1a;不同的预训练模型具有不同的特点和适…

Java 操作 Redis和redis持久化

一、Jedis 我们要使用 Java 来操作 Redis&#xff0c;Jedis 是 Redis 官方推荐的 java连接开发工具&#xff01; 使用Java 操作 Redis 中间件&#xff01; 1.导入对应的依赖 https://mvnrepository.com/artifact/redis.clients/jedis <dependency><groupId>redi…

Keycloak中授权的实现-转载

在Keycloak中实现授权&#xff0c;首先需要了解与授权相关的一些概念。授权&#xff0c;简单地说就是某个&#xff08;些&#xff09;用户或者某个&#xff08;些&#xff09;用户组&#xff08;Policy&#xff09;&#xff0c;是否具有对某个资源&#xff08;Resource&#xf…

CAN总线详解-理论知识部分

目录 CAN总线简介 CAN总线硬件电路 CAN电平标准 CAN收发器 ​编辑 CAN物理层特性 CAN总线帧格式 数据帧 数据帧格式 数据帧发展历史 遥控帧 错误帧 过载帧 帧间隔 位填充 波形实例 CAN总线接收方数据采样 接收方数据采样遇到的问题 位时序 硬同步 再同步 波…

Cesium.js:webGIS领域的翘楚,开源全球地理空间数据可视化框架.

说起数据可视化/数字孪生开发&#xff0c;少不了webGIS&#xff0c;聊起webGIS不得不提大名鼎鼎的Cesium.js框架。 CesiumJS是一个用于创建地理空间应用程序的开源JavaScript库。它提供了丰富的地图和地理空间数据的可视化功能&#xff0c;可以用于构建基于地理位置的3D地图、…

nvm介绍、下载、安装、配置及使用

一、背景 在工作中&#xff0c;我们可能同时在进行2个或者多个不同的项目开发&#xff0c;每个项目的需求不同&#xff0c;进而不同项目必须依赖不同版本的NodeJS运行环境&#xff0c;这种情况下&#xff0c;对于维护多个版本的node将会是一件非常麻烦的事情&#xff0c;nvm就…

go语言源码解读之数据结构堆

概述 堆(heap)&#xff0c;是一种计算中常用的数据结构。本文我们将探讨对的特性、实现细节以及实际应用场景。 基本概念 堆是一种特殊的完全二叉树。堆分为大顶堆与小顶堆。 大顶堆的特点是&#xff0c;父节点的值总是大于或等于其子节点的值。 小顶堆的特点是&#xff0c…

DVWA-IDS测试(特殊版本)

起因 浏览DVWA历史更新记录发现有版本带有IDS插件&#xff0c;可以用于平时没有相关设备等场景演示用&#xff0c;所以开启本次测试。 下载 官方最新版本是移除了IDS插件&#xff0c;原因是“从不使用”&#xff0c;所以需要下载移除该插件之前的版本。 https://github.com/…

Excel中使用SUMIF函数对指定区域满足条件的进行求和

1.使用 SUMIF 函数对 范围 中符合指定条件的值求和。 例如&#xff0c;如果某列中含有数字&#xff0c;你只需对大于 5 的数值求和。 可使用以下公式&#xff1a;SUMIF(B2:B25,">5") 2.将条件应用于一个区域并对其他区域中的对应值求和。 例如&#xff0c;公式 S…

时钟缓冲器的相关知识

时钟缓冲器是比较常用的器件&#xff0c;其主要功能作用有时钟信号复制&#xff0c;时钟信号格式转换&#xff0c;时钟信号电平转换等。我们下面简单了解下&#xff1a; 1.时钟信号复制 例如ICS553芯片&#xff0c;其将单路输入时钟信号复制4份进行输出&#xff0c;输出信号具…

debian 常用命令

1、修改环境变量 /etc/profile export PATH/usr/local/bin:$PATHsource /etc/profile ## 生效临时改变export PATH/usr/local/bin:$PATH或者改变当前用户的vim ~/.bashrcsource ~/.bashrc // 生效 2、清除当前登录的历史操作 history -c 3、解压缩 压缩基本的命令格式 …

SD卡电路设计基础

一、定义 SD卡按尺寸分类可分为三类:标准 SD 卡、Mini SD 卡和 Micro SD 卡。其中Mini SD 卡比较少见&#xff0c;标准 SD 卡因为体积较大主要用在数码相机等对体积要求不严格的地方,我们最常用的是 Micro SD 卡&#xff0c;原名Trans-flash Card (TF 卡)。 Micro SD 作用:一…

天书般的Tree工具类

3.1 JAVA中树形对象的定义 在JAVA中树形结构是通过对象嵌套方式来定义的&#xff0c;如MenuVo对象中有一个子对象subMenus&#xff1a; Data public class MenuVo {private Long id;private Long pId;private String name;private Integer rank0;private List<MenuVo> s…