树的表示方式

树的表示方式

树的结构是⼀种非线性的存储结构。存储的是具有⼀对多的关系的数据元素的集合。

树的概念

image-20221230144219953

图中可以看见一个使用树形结构存储的一个集合,这个集合就是{A,B,C…}。对于数据A来说,和数据B、C、D有关系。对于数据B来说,和E,F,G有关系。这就是一对多的关系。

我们将一对多的关系的集合中的数据元素按照图中的形式进行存储,整个存储形状在逻辑结果上面看,类似于实际生活中倒着的树,所以就将这种结构称之为树形结构。

树的结点

结点∶使用树结构存储的每一个数据元素都被称为"结点"。例如图中的A就是一个结点。

根结点:有一个特殊的结点,这个结点没有前驱,我们将这种结点称之为根结点。

父结点(双亲结点)、子结点和兄弟结点︰对于ABCD四个结点来说,A就是BCD的父结点,也称之为双亲结点。而BCD都是A的子结点,也称之为孩子结点。对于BCD来说,因为他们都有同一个爹,所以它们互相称之为兄弟结点。

叶子结点∶如果一个结点没有任何子结点,那么此结点就称之为叶子结点。

结点的度︰结点拥有的子树的个数,就称之为结点的度。

树的度:在各个结点当中,度的最大值。为树的度。

树的深度或者高度︰结点的层次从根结点开始定义起,根为第一层,根的孩子为第二层。依次类推。

树的存储结构

双亲表示法

顺序表表示形式

image-20221230151640793

image-20221230151650694

//Node结点
/**
* @author Turing
* @version 1.0.0
* @Description TODO
**/
public class Node {
 //树种存放的数据
 int data;
 //该结点的父节点在数组中的下标位置
 int parent;
    
 public Node(int data,int index){
     this.data = data;
     this.parent = index;
 }
 public int getData() {
	 return data;
 }
 public void setData(int data) {
	 this.data = data;
 }
 public int getParent() {
 	return parent;
 }
 public void setParent(int parent) {
 	this.parent = parent;
 }
}
//双亲表示法
/**
* @author Turing
* @version 1.0.0
* @Description TODO
**/
public class TreeParentDemo {
     Node node[];
     int size;//当前元素个数
     int maxSize;//当前最大元素个数
     public TreeParentDemo(int temp){
     node = new Node[temp];
     this.maxSize = temp;
     this.size = 0;
 }
 /**
 * 构建跟结点
 * @param data 根节点的数据
 */
 public void insert(int data){
     //已有根节点 ?法继续构建根节点
     if(size > 1){
    	 //提示
     }
     Node node = new Node(data,-1);
     this.node[size] = node;
     size++;
 }
    /**
 * 插入子节点
 * @param data 待插入子节点的数据
 * @param index 该子节点插入到哪个结点下面
 */
    public void insertSon(int data,int index){
 //是否有这个父节点 如果没有该父节点 那就 提示一下
 
     Node node = new Node(data,index);
     this.node[size] = node;
     size++;
 }
 public void show(){
     for (int i = 0; i < size;i++){
         System.out.println("数据:" + node[i].getData() + "父级结点为:" +node[i].getParent());
         }
     }
}
//测试数据
/**
* @author Turing
* @version 1.0.0
* @Description TODO
**/
public class Test {
     public static void main(String[] args) {
         TreeParentDemo demo = new TreeParentDemo(10);
         demo.insert(1);
         demo.insertSon(2,0);
         demo.insertSon(3,0);
         demo.insertSon(4,0);
         demo.insertSon(7,1);
         demo.show();
     }
}
优缺点说明

由于根结点是没有双亲的,所以我们约定根结点的位置域设置为-1,这也就意味着,我们所有的结点都存有它双亲的位置。这样的存储结构,我们可以根据结点的parent指针很容易找到它的双亲结点,所用的时间复杂度为O(1),直到parent为-1时,表示找到了树结点的根。可如果我们要知道结点的孩子是什么,对不起,请遍历整个结构才行。

这真是麻烦,能不能改进一下呢?当然可以。我们增加一个结点最左边孩子的域,不妨叫它长子域,这样就可以很容易得到结点的孩子。如果没有孩子的结点,这个长子域就设置为-1。

对于有0个或1个孩子结点来说,这样的结构是解决了要找结点孩子的问题了。甚至是有2个孩子,知道了长子是谁,另一个当然就是次子了。另外一个问题场景,我们很关注各兄弟之间的关系,双亲表示法无法体现这样的关系,那我们怎么办?嗯,可以增加一个右兄弟域来体现兄弟关系,也就是说,每一个结点如果它存在右兄弟,则记录下右兄弟的下标。同样的,如果右兄弟不存在,则赋值为-1。

但如果结点的孩子很多,超过了2个。我们又关注结点的双亲、又关注结点的孩子、还关注结点的兄弟,而且对时间遍历要求还比较高,那么我们还可以把此结构扩展为有双亲域、长子域、再有右兄弟域。存储结构的设计是一个非常灵活的过程。一个存储结构设计得是否合理,取决于基于该存储结构的运算是否适合、是否方便,时间复杂度好不好等。

孩子表示法

image-20221230152536881

image-20221230152547955

孩子兄弟表示法

image-20221230152615461

image-20221230152642446

//结点
/**
* @author Turing
* @version 1.0.0
* @Description TODO
**/
public class Node {
     //数据
     int data;
     //孩子指针域
     Node child;
     //兄弟指针域
     Node sibling;
     
     public Node(int data){
    	 this.data = data;
     }
}
/**
* @author Turing
* @version 1.0.0
* @Description TODO
**/
public class TreeCP {
     Node root;
     //新增一个临时结点 方便接收定位的数据
     Node tempNode;
     
     public TreeCP(int data){
    	 root = new Node(data);

 }
 /**
 * 在指定的位置添加结点
 * @param data 
 */
 public void insert(int data,int temp){
     //定位到该节点
     getNode(root,temp);
     Node node = tempNode;
     if (node == null){
     	System.out.println("没有结点");
     } else{
    	 if (node.child == null){
     		node.child = new Node(data);
     	  }else {
             Node tempNode = node.child;
             Node newNode = new Node(data);
             newNode.sibling = tempNode.sibling;
             tempNode.sibling = newNode;
    	  }
     }
 }
 public void getNode(Node node,int temp){
     if (node.data == temp){
    	 tempNode = node;
     }
     //如果我的兄弟结点?空,则取出元素
     if (node.sibling != null){
     	getNode(node.sibling,temp);
     }
     if (node.child != null){
		 getNode(node.child,temp);
		 }
	 }
}
//测试
/**
* @author Turing
* @version 1.0.0
* @Description TODO
**/
public class Test {
         public static void main(String[] args) {
         TreeCP treeCP = new TreeCP(1);
         treeCP.insert(2,1);
         treeCP.insert(3,1);
         treeCP.insert(4,2);
         System.out.println(treeCP.root.child.sibling.data);
     }
}

这种表示法,给查找某个结点的某个孩子带来了方便,只需要通过firstchild找到此结点的长子,然后再通过长子结点的rightsib找到它的二弟,接着一直下去,直到找到具体的孩子。当然,如果想找某个结点的双亲,这个表示法也是有做陷的,那怎么办呢?

对,如果真的有必要,完全可以再增加一个parent指针域来解决快速查找双亲的问题,这里就不再细谈了。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Tian Meng

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值