树(Tree)结构是一种描述非线性层次关系的的数据结构,树中有一个根结点,根节点下分布着一些互不交叉的子集合(子树)
- 在一个树结构中,有且只有一个根结点,根结点没有直接前驱;
- 每个结点有且只有一个直接前驱;
- 每个结点可以有多个直接后继;
二叉树:
在树结构中,二叉树是最简单的一种形式,对树结构的研究主要是二叉树,二叉树最多只有两个子结点,即左子树与右子树;正因有左右之分,所以二叉树是有序树;二叉树可分为满二叉树和完全二叉树;
在数据结构中,完全二叉树是主要的研究对象,如果二叉树中包含n个结点,假设这些结点都按顺序存储,那么,对于任意一个结点来说,具有如下性质:
- 如果m!=1,则结点m的父节点编号为m/2;
- 如果2*m<=n,则结点m的左子树根结点的编号为2*m;若2*m>n,则无左子树;
- 如果2*m+1<=n,则结点m的右子树根结点的编号为2*m+1;若2*m+1>n,则无右子树;
上述性质只针对于二叉树的顺序结构,但顺序存储结构的缺点是浪费存储空间,只适合满二叉树的存储;对大部分二叉树来讲,链式存储结构才是最适合的;
二叉树的链式存储:与线性结构的链式存储类似,二叉树的链式存储结构包括结点元素以及分别指向左子树和右子树的引用,当然,为了计算方便,也可以保存一个该节点的父节点的引用;
二叉树的链式结构设计步骤为:
- 定义二叉树链式存储结构(BTType)
- 初始化树结构(btInit)
- 计算树的深度(btDepth)
- 添加结点(查找父节点——>添加至左(右)结点)(btAddNode)
- 查找结点(btFindNode)
- 遍历二叉树(btGetAllNode)
- 删除结点需分多种情况,这里不做讨论,可参考https://blog.csdn.net/claroja/article/details/54617344
代码实现:
一,定义结点
public class Data {//定义结点
String name;
int age;
}
二,二叉树链式结构实现
public class BTree {
BTType rootNode;// 定义全局头引用
class BTType {// 定义二叉树链式结构
Data data = new Data();
BTType leftNode;
BTType rightNode;
BTType fatherNode;
}
void btInit(Scanner input) {// 初始化树结构,因为之后的方法用到了递归,所以根节点必须在初始化结构的时候就给出
if ((rootNode = new BTType()) != null) {
System.out.println("输入根节点:姓名:");
String name=input.next();
System.out.println("输入根节点:年龄:");
int age=input.nextInt();
Data d=new Data();
d.name=name;
d.age=age;
rootNode.data = d;
rootNode.leftNode = null;
rootNode.rightNode = null;
rootNode.fatherNode = null;
}
}
int btDepth(BTType root) {// 计算树深度,因为用到了递归,所以必须将根结点以参数传入
int leftdep;
int rightdep;
if(root==null){//如果没有根节点,深度为零
return 0;
}else{
leftdep=btDepth(root.leftNode);//递归计算左子树的深度(最后一个结点返回0)
rightdep=btDepth(root.rightNode);//递归计算右子树的深度(最后一个结点返回0)
if(leftdep>rightdep){//当递归完成后,判断左右子树深度大小
return leftdep+1;
}else{
return rightdep+1;
}
}
}
int btAddNode(Data d, Scanner input) {// 添加结点
BTType btemp, ftemp;//分别保存新结点和父结点
boolean nodeisfull = false;//定义父节点的左右结点是否为空
int i;//用于保存用户输入的子节点序号(1(添加到左子树),2(添加到右子树))
// 保存结点数据
if ((btemp = new BTType()) != null) {
btemp.data = d;
btemp.leftNode = null;
btemp.rightNode = null;
btemp.fatherNode=null;
}
//树根结点是否为空
if (rootNode == null) {
rootNode= btemp;
System.out.print("数据已保存到根节点!");
return 1;
}
// 查找父节点
do {
System.out.println("请输入父节点:");
String name = input.next();
Data df = new Data();
df.name = name;
ftemp = btFindNode(rootNode, df);//调用了查找结点的方法
if (ftemp == null) {
System.out.println("父节点不存在!");
nodeisfull=true;//父节点不存在,设置nodeisfull=true,保证用户可以重新输入父节点
}else{
if (ftemp.leftNode != null && ftemp.rightNode != null) {
System.out.print("子节点已满!");
nodeisfull = true;//子节点已满,设置nodeisfull=true,保证用户可以重新输入父节点
}else{
btemp.fatherNode=ftemp;
nodeisfull=false;//父节点存在且左右子结点未满,设置nodeisfull=false,退出循坏进行下一步
}
}
} while (nodeisfull);
// 确定目标子树是否为空
do {
System.out.println("1(添加到左子树),2(添加到右子树)");
i = input.nextInt();
switch (i) {
case 1:
if (ftemp.leftNode != null) {
System.out.println("左结点不为空");
break;
} else {
ftemp.leftNode = btemp;// 左结点为空,添加数据到左结点
return 1;//退出do...while循环
}
case 2:
if (ftemp.rightNode != null) {
System.out.println("右结点不为空");
break;
} else {
ftemp.rightNode = btemp;// 右结点为空,添加数据到右结点
return 1;//退出do...while循环
}
default:
System.out.println("输入序号不正确");
}
} while (i != 1 || i != 2);
return 0;
}
BTType btFindNode(BTType root, Data d) {// 查找结点,因为用到了递归,所以必须将根结点以参数传入
BTType btemp;//用于保存每次递归查找的临时结点
if (root== null) {//根结点为空,查找失败
return null;
} else {
if (root.data.name.equals(d.name)) {
return root;
} else {
if ((btemp = btFindNode(root.leftNode, d)) != null) {
return btemp;
}//当递归到最后一个root.leftNode返回null时,还未找到数据,递归停止,进行下一步
if ((btemp = btFindNode(root.rightNode, d)) != null) {
return btemp;
}
}
}
return null;
}
int btRemoveNode(Data d) {// 删除结点
/*树的删除操作有点复杂,有很多种情况
* 1.被删除的结点是叶子结点
* 2.被删除的结点只有左子树节点
* 3.被删除的结点只有右子树结点
* 4.被山删除的结点同时存在左右子树结点
* 所以在删除的时候,这几种请况必须都考虑进去
* */
return 1;
}
void btGetAllNode(BTType root) {// 遍历二叉树,因为用到了递归,所以必须将根结点以参数传入
if(root!=null){//先序遍历
System.out.println("姓名:"+root.data.name);
btGetAllNode(root.leftNode);
btGetAllNode(root.rightNode);
}
}
}
三,运行测试
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
BTree dt = new BTree();
dt.btInit(in);
while (true) {
System.out.println("请输入:0(退出),1(添加),3(查找),4(树深度),5(遍历)");
int i = in.nextInt();
switch (i) {
case 0:
return;
case 1:
Data d = new Data();
System.out.print("添加姓名:");
String name = in.next();
System.out.print("添加年龄:");
int age = in.nextInt();
if (name == null || age == 0) {
System.out.println("信息不能为空");
break;
}
d.age = age;
d.name = name;
int temp1 = dt.btAddNode(d,in);
if (temp1 == 0) {
System.out.println("添加失败");
break;
}
System.out.println("添加成功");
break;
case 3:
System.out.println("你要查找的结点:");
String name3 = in.next();
if (name3 == null) {
System.out.println("信息不能为空");
break;
}
Data d3 = new Data();
d3.name=name3;
BTType btt= dt.btFindNode(dt.rootNode, d3);
if(btt!=null){
System.out.println("姓名:"+btt.data.name+"|"+"年龄:"+btt.data.age);
}else{
System.out.println("查找失败");
}
break;
case 4:
System.out.println("树的深度:"+dt.btDepth(dt.rootNode));
break;
case 5:
dt.btGetAllNode(dt.rootNode);
break;
default:
break;
}
}
}