数据结构(超详细讲解!!)第二十五节 树与森林

1.树的存储结构        

和线性表一样,树可以用顺序和链式两种存储结构。    

树的顺序存储结构适合树中结点比较“满”的情况。根据树的非线性结构特点,常用链式存储方式来表示树。树常用的存储方法有:双亲表示法、孩子表示法和孩子兄弟表示法。    

1.双亲存储表示法    

用一维数组来存放一棵树的所有结点,每个结点除了存放本身信息外,还要存放其双亲在数组中的位置。 每个结点有两个域:

data---存放结点的信息;

parent---存放该结点双亲在数组中的位置

特点:求结点的双亲很容易,但求结点的孩子需要遍历整个向量。

存储结构:

//存储结构
#define MaxTreeSize 100  	/* 定义数组空间的大小*/
#define ElemType  int 	     /* ElemType表示树中结点的数据类型,这里为int */
typedef struct PTreeNode
{	ElemType data;       	/* 结点数据 */
	int parent;         /* 双亲指示器,指示结点的双亲在数组中的位置 */
} PTreeNode;
typedef struct PTree
{	PTreeNode nodes[MaxTreeSize]; 
	int  r,n;	  /* r表示根结点在数组中的位置,n表示树中结点总数*/
} Ptree;

查找双亲:

//查找双亲
int NodeLocate(PTree *t, ElemType e)/* 在树中查找结点e ,返回其在数组中的位置*/
{	int i = 0;			/* 在链表中查找结点e */ 
	while (i<t -> n && t->nodes[i].data != e) 	i++;      
	if (i < t-> n) 	return i;
	else 		return (-1);
}
ElemType ParentLocate (PTree *t, ElemType e)
{	int i;
	i = NodeLocat(t, e); /* 查找结点e在数组中的位置 */
	if(i == -1)			/* 树中无此结点 */
	{	printf("no this node!");  return 0;  }
	else if(t->nodes[i].parent==-1)	/* 结点e是树的根结点 */ 
	{	printf("this is the root, no parent!"); return 0;  }
	else		/* 返回结点e的双亲*/
		return( t->nodes[nodes[i].parent].data );
}

2.孩子链表表示法    

这是树的链式存储结构。每个结点的孩子用单链表存储,称为孩子链表。  

n个结点可以有n个孩子链表(叶结点的孩子链表为空表)。  

n个孩子链表的头指针用一个向量表示。

特点:与双亲相反,求孩子易,求双亲难。

存储结构:


#define ElemType  char 	/* ElemType 是树结点元素类型,这里定义为char*/
#define MaxTreeSize 20
typedef struct CTNode        	/* 孩子链表中的结点结构 */
{	int child;              	/* child域存放结点在一维数组中的位置 */
	struct CTNode *next;
}*ChildPtr; 			/* ChildPtr为指向孩子结点的指针 */ 
typedef struct              		/* 指向孩子链表结点的头指针结构 */ 
{	ElemType data;     	/* data域存放结点的本身信息*/
	ChildPtr firstchild;   	/* firstchild域存放第一个孩子的指针*/
}CTBox;
typedef struct 			/* 孩子链表结构 */
{	CTBox nodes[MaxTreeSize];
	int n, r;       	/*n域存放树的结点数,r域存放根结点的位置*/
} CTree;

可以把双亲表示法和孩子链表表示法结合起来。

相应地上述指向孩子链表结点的头指针结构修改如下:

3.孩子兄弟表示法    

用二叉链表作为树的存储结构,每个结点的左链域指向该结点的第一个孩子,右链域指向下一个兄弟结点。    

由于结点中的两个指针指示的分别为“孩子”和“兄弟”,故称为“孩子-兄弟链表”。这种结构也称为二叉链表。

特点:双亲只管长子 长子连接兄弟

存储结构:

typedef struct CSNode
{	ElemType data;
	struct CSNode *firstchild, *nextsibling;
}CSNode;

  孩子兄弟存储结构的最大优点是可以方便地实现树和二叉树的相互转换和树的各种操作。如果在结点中增设一个parent域,那么查找结点的双亲也很方便。

2.森林的存储结构

1、森林的定义    

零棵或有限棵互不相交的树的集合称为森林。

2、森林的存储结构        

双亲表示法、孩子表示法和孩子兄弟表示法。

(1)森林的双亲表示法。

和树的双亲表示法一样,用一个一维数组A来存储森林,每个元素有两个域:结点的数据和其双亲在数组的位置。只是其中有多个元素的双亲域值为-1,它们对应于森林各棵树的根结点。在具体存储时,可用增设一个一维数组B存储各棵树的树根在数组A中的位置。

(2)森林的孩子表示法。

它将森林中每个结点的孩子用单链表连接起来,再用一大小为n(结点个数)的一维数组A存储每个结点信息,包括结点本身的数据信息和指向第一个孩子的指针。孩子结点有两个域:数据域为此结点在一维数组中位置,指针域为指向下一个兄弟(即结点双亲的下一个孩子)的指针。具体存储时也可以增设一个一维数组B存储各棵树的树根在数组A中的位置。

(3)森林的孩子兄弟表示法。

此表示法以二叉链表作为树的存储结构,链表中的两个链域firstchild和nextsibling分别指向此结点的第一个孩子结点和下一个兄弟结点。这时,可将森林中各棵树的树根看成是兄弟,因此,在存储时,将森林中第二棵树树根作为第一棵树树根的兄弟,即第一棵树树根的nextsibling指向第二棵树树根,如此下去。

3.树和森林的基本操作

1.树和森林与二叉树的相互转换

树与二叉树均可用二叉链表作为存储结构,因此给定一棵树,用二叉链表存储,可唯一对应一棵二叉树,反之亦然。

1) 树转换为二叉树

转换方法是:  

①加线:在树中各兄弟(堂兄弟除外)之间加一根连线。  

②去线:对树中的每个结点,只保留其与第一个孩子结点之间的连线,删去其与其它孩子结点之间的连线。    

③旋转:以树的根结点为轴心,将整棵树顺时针旋转一定的角度,使之结构层次分明。    

特点:根无右子树    

可以证明,树做这样的转换所构成的二叉树是唯一的。 下图给出了将这棵树树转换为二叉树的转换过程示意。

反过来,要将一棵二叉树转换为树,也是经过三步:    

① 加线:将树中的每个结点和其左孩子结点的右孩子以及右孩子的右孩子加线相连。    

② 去线:去掉树中的每个结点和右孩子的连线。  

 ③ 旋转:以树的根结点为轴心,将整棵树逆时针旋转一定的角度,使之结构层次分明,就得到旋转后的树。

2)森林与二叉树的相互转换

一棵树转换为一棵二叉树后,其根结点的右子树为空。

在二叉树转换为树时,所取二叉树的根结点的右子树也为空。

如果二叉树根结点的右子树不空,根据前面的转换可知,二叉树中结点的右孩子是结点的兄弟。

那么对于根结点有右孩子的二叉树,根结点的右孩子以及右孩子的右孩子都是兄弟,这时,转换出来的结果就是森林。

反过来,森林将转换成根结点有右子树的二叉树。

森林与二叉树的相互转换还是采用加线、去线、旋转的规则进行转换,转换过程和树与二叉树的相互转换规则类似。

森林转换为二叉树:

森林是若干棵树的集合。

树可以转换为二叉树, 森林同样也可以转换为二叉树。 因此,森林也可以方便地用孩子兄弟链表表示。森林转换为二叉树的方法如下:      

(1) 将森林中的每棵树转换成相应的二叉树。      

(2)第一棵二叉树不动,从第二棵二叉树开始,依次把后一棵二叉树的根结点作为前一棵二叉树根结点的右孩子, 当所有二叉树连在一起后,所得到的二叉树就是由森林转换得到的二叉树。

二叉树还原为树或森林:

树和森林都可以转换为二叉树,二者的不同是:

树转换成的二叉树, 其根结点必然无右孩子。

而森林转换后的二叉树,其根结点有右孩子。

将一棵二叉树还原为树或森林,具体方法如下:      

(1) 若某结点是其双亲的左孩子,则把该结点的右孩子、 右孩子的右孩子……都与该结点的双亲结点用线连起来。      

(2) 删掉原二叉树中所有双亲结点与右孩子结点的连线。      

(3) 整理由(1)、(2)两步所得到的树或森林, 使之结构层次分明。

2、树和森林的遍历

(1)树的遍历    

① 先序(根)遍历:访问树的根结点,再依次先序遍历根的每棵子树(从左到右)。    

② 后序(根)遍历:依次后序遍历根的每棵子树(从左到右),最后访问根结点。    

③ 层次遍历:从上到下、从左至右访问树的每一个结点。

先序遍历序列为ABCEFGD

后序遍历序列为BEFGCDA

层次遍历序列为ABCDEFG

当以二叉链表作为树的存储结构时,树的先根遍历和后根遍历分别对应二叉树的先序遍历和中序遍历算法。

结论:

①树后序遍历相当于对应二叉树的中序遍历;

②树没有中序遍历,因为子树无左右之分。

练习:写出下面树的先根,后根和按层次遍历序列。

(2)森林的遍历    

① 先序遍历。若森林非空,按下述规则遍历:

访问森林中第一棵树的根结点。

先序遍历第一棵树根结点的子树森林。

先序遍历除去第一棵树后剩余树构成的森林。    

② 后序遍历。若森林非空,按下述规则遍历:

后序遍历森林中第一棵树根结点的子树森林。

访问第一棵树的根结点。

 后序遍历除去第一棵树后剩余树构成的森林。

由森林转换成二叉树时,其第一棵树的子树森林转换成左子树,剩余树构成的森林转换成右子树。因此,森林的先序遍历和后序遍历即为其对应二叉树的先序遍历序列和中序遍历序列。

4.哈夫曼树

最优二叉树,也称哈夫曼(Haffman)树,是指对于一组带有确定权值的叶结点,构造的具有最小带权路径长度的二叉树。

1. 路径和路径长度

路径是指从一个结点到另一个结点之间的分支序列

路径长度是指从一个结点到另一个结点所经过的分支数目

树的路径长度PL定义为: 从树根到每一个结点的路径长度之和。

2. 结点的权和带权路径长度

在实际的应用中,人们常常给树的每个结点赋予一个具有某种实际意义的实数,我们称该实数为这个结点的权。

在树形结构中,我们把从树根到某一结点的路径长度与该结点的权的乘积,叫做该结点的带权路径长度。

3. 树的带权路径长度        

树的带权路径长度为树中所有叶子结点的带权路径长度之和,通常记为

其中n为叶子结点的个数,wi为第i个叶子结点的权值,li为第i个叶子结点的路径长度。

WPL(a)=7×2+5×2+2×2+4×2=36

WPL(b)=4×2+7×3+5×3+2×1=46

WPL(c)=7×1+5×2+2×3+4×3=35

  一棵树的路径长度为0, 结点至多只有1个(根);        

路径长度为1, 结点至多只有2个(两个孩子);        

路径长度为2, 结点至多只有4个;        

依此类推,路径长度为k, 结点至多只有2k个, 所以n个结点二叉树其路径长度至少等于如图所示序列的前n项之和。

 4.构造哈夫曼算法的步骤  

(1)  用给定的n个权值{w1, w2,  …, wn}对应的n个结点构成n棵二叉树的森林F={T1, T2,  …, Tn},其中每一棵二叉树Ti(1≤i≤n)都只有一个权值为wi的根结点,其左、右子树为空。          

(2) 在森林F中选择两棵根结点权值最小的二叉树,作为一棵新二叉树的左、右子树,标记新二叉树的根结点权值为其左右子树的根结点权值之和。

(3) 从F中删除被选中的那两棵二叉树, 同时把新构成的二叉树加入到森林F中。    

(4) 重复(2)、(3)操作, 直到森林中只含有一棵二叉树为止, 此时得到的这棵二叉树就是哈夫曼树。

5.哈夫曼编码算法

根据哈夫曼树的构造过程可知,哈夫曼树中没有度为1的结点,因此,一棵具有n个叶子结点的哈夫曼树共有2n-1个结点,可以把它们存储在一个大小为2n-1的一维数组中。为了在编码、译码时能立即找到它的双亲和孩子,同时,在建立哈夫曼树时,需要选择权值最小的两棵树,因此,每个结点还需要有结点的权重信息、双亲和左右孩子信息。

# define MAXBIT 10
# define MAXVALUE 1000	
typedef struct HNode	 /*定义结点结构*/ 					
{	int weight;
	int parent , lchild , rchild;
}HNode;

void HuffmanCoding(HNode *HT,HCode *HC , int *w , int n)
{           /* w存储n个字符的权值,构造哈夫曼树HT,并求出n个字符的哈夫曼编码HC*/
    	int m, m1, m2, x1, x2, i, j, start; 	HNode *p; char* cd;
	if (n <= 1) return;
	m= 2*n-1;		/*哈夫曼树的构造*/
	HT = (HNode *)malloc (m*sizeof(HNode) );
	for(p = HT, i = 0; i < n; ++ i, ++ p, ++w)/*初始化叶子结点信息*/
	{	p->weight = *w;	p->lchild = -1;
		 p->rchild = -1; p->parent = -1;  
	}
	for( ; i < m; ++ i, ++ p)   		/*初始化分支结点信息*/
	{	p->weight = 0; p->lchild = -1; 
		p->rchild = -1; p->parent = -1;
	}
for( i = n; i < m; ++ i) 	/*构造哈夫曼树 */
	{   	m1 = m2 = MAXVALUE;
		x1 = x2 = 0;     /*寻找parent为-1且权值最小的两棵子树*/
	 	for(j = 0; j < i; ++ j)	
		{	if(HT[j].parent == -1 && HT[j].weight < m1)
			{m2 = m1; x2 = x1; m1 = HT[j].weight; x1 = j; }
			else if(HT[j].parent == -1 && HT[j].weight < m2)
			{	m2 = HT[j].weight; x2 = j;   }
		}
		/*合并成一棵新的子树*/
		HT[x1].parent = i; HT[x2].parent = i;
		HT[i].lchild = x1; HT[i].rchild = x2;
		HT[i].weight = m1 + m2;
	}
/*字符编码*/
	HC = (HCode *) malloc (n*sizeof(HCode) );
	cd = (char *) malloc(n*sizeof(char));
	cd[n-1] = ’\0’;
	for(i = 0; i < n; ++ i)  /* 从叶子到根逆向求每个字符的哈夫曼编码 */
	{	start= n-1;
		for(c = i, f = HT[i].parent; f != -1; c = f, f = HT[f].parent)
			if(HT[f].lchild = c)	cd[--start] = ’0’;
			else		cd[--start] = ’1’;
		HC[i] = ( char*) mallic(n-start)*sizeof(char));	
		/*为第i个字符编码分配空间*/
		strcpy(HC[i], &cd[start]);  /*从cd复制编码串到HC[i] */
	}
	free(cd);
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【LeetCode刷题-链表】--86.分隔链表

86.分隔链表 /*** Definition for singly-linked list.* public class ListNode {* int val;* ListNode next;* ListNode() {}* ListNode(int val) { this.val val; }* ListNode(int val, ListNode next) { this.val val; this.next next; }* }*/ class…

SpringSecurity+JWT实现权限控制以及安全认证

一.简介 Spring Security 是 Spring家族中的一个安全管理框架。相比与另外一个安全框架Shiro&#xff0c;它提供了更丰富的功能&#xff0c;社区资源也比Shiro丰富。 认证&#xff1a;验证当前访问系统的是不是本系统的用户&#xff0c;并且要确认具体是哪个用户​ 授权&…

虚幻学习笔记3—UI跟随弹窗

一、前言 本文使用的虚幻引擎5.3.2&#xff0c;继点击场景3D物体的两种处理方式的基础完成对3D物体的点击触发后&#xff0c;我们需要制作一个可以弹窗显示该物体信息的UI面板&#xff0c;同时保证弹窗可以跟随物体。另外还讲了一种UI上的悬浮提示跟随弹窗。 二、实现 2.1、创…

Pytorch:torch.utils.data.DataLoader()

如果读者正在从事深度学习的项目&#xff0c;通常大部分时间都花在了处理数据上&#xff0c;而不是神经网络上。因为数据就像是网络的燃料&#xff1a;它越合适&#xff0c;结果就越快、越准确&#xff01;神经网络表现不佳的主要原因之一可能是由于数据不佳或理解不足。因此&a…

ArcGIS Pro、Python、USLE、INVEST模型等多技术融合的生态系统服务构建生态安全格局

近年来&#xff0c;由于社会经济的快速发展和人口增长&#xff0c;社会活动对环境的压力不断增大&#xff0c;人地矛盾加剧。虽然全球各国在生态环境的建设和保护上已取得不少成果&#xff0c;但还是未从根本上转变生态环境的恶化趋势&#xff1b;生态破坏、环境退化、生物多样…

rss服务搭建记录

layout: post title: RSS subtitle: vps搭建RSS服务 date: 2023-11-27 author: Sprint#51264 header-img: img/post-bg-universe.jpg catalog: true tags: - 折腾 文章目录 引言RSShub-dockerRSS-radarFreshrssFluent reader获取fever api配置Fluent Reader同步 结语 引言 一个…

【代码】数据驱动的多离散场景电热综合能源系统分布鲁棒优化算法matlab/yalmip+cplex/gurobi

程序名称&#xff1a;数据驱动的多离散场景电热综合能源系统分布鲁棒优化算法 实现平台&#xff1a;matlab-yalmip-cplex/gurobi 代码简介&#xff1a;数据驱动的分布鲁棒优化算法。考虑四个离散场景&#xff0c;模型采用列与约束生成(CCG)算法进行迭代求解&#xff0c;场景分…

C#键盘钩子(Hook)拦截器的使用

引言 键盘钩子(Hook)是一种机制&#xff0c;允许程序捕获和处理操作系统中的键盘输入。在C#中&#xff0c;我们可以使用键盘钩子来创建一个拦截器&#xff0c;用于拦截特定的键盘事件并执行自定义操作。本文将介绍如何使用C#开发一个键盘钩子拦截器&#xff0c;并给出一些示例代…

苹果手机如何格式化?五个步骤快速掌握!

如果手机出现异常情况&#xff0c;例如运行缓慢、频繁崩溃&#xff0c;又或者想将手机出售、转让给他人&#xff0c;那么将手机格式化可以有助于解决问题。苹果手机如何格式化&#xff1f;本文将为您介绍解决方法&#xff0c;只需要五个步骤就能搞定&#xff0c;帮助您快速掌握…

一维数组,逆序存放并输出【样例输入】20 30 10 50 40 90 80 70【样例输出】70 80 90 40 50 10 30 20

一维数组&#xff0c;逆序存放并输出 【样例输入】 20 30 10 50 40 90 80 70 【样例输出】 70 80 90 40 50 10 30 20 以下是使用C语言编写的将一维数组逆序存放并输出的示例代码&#xff1a; #include <stdio.h>void reverseArray(int arr[], int size) {int start…

Xilinx Zynq-7000系列FPGA多路视频处理:图像缩放+视频拼接显示,提供工程源码和技术支持

目录 1、前言免责声明 2、相关方案推荐FPGA图像处理方案FPGA图像缩放方案FPGA视频拼接叠加融合方案推荐 3、设计思路详解HLS 图像缩放介绍Video Mixer介绍 4、vivado工程介绍PL 端 FPGA 逻辑设计PS 端 SDK 软件设计 5、工程移植说明vivado版本不一致处理FPGA型号不一致处理其他…

全网日志智能聚合及问题根因分析

1 日志关联分析的挑战 随着各行各业数字化转型的不断深入&#xff0c;网络承载了人们日常生活所需的政务、金融、娱乐等多方面的业务系统&#xff0c;已经成为影响社会稳定运行、关系国计民生的重要基础设施资源。哪怕网络发生及其微小的故障&#xff0c;也可能带来难以估量的…

【预测爆款不用愁,有服饰RFID小助手】

时尚服饰行业库存成本高&#xff0c;数据不精准&#xff0c;爆款服饰一直抓不住&#xff0c;增加库存滞销风险难脱逃&#xff0c;给服饰零售企业带来极大困扰。 帮您提前预测爆款服饰小塔服饰RFID系统 小塔RFID系统作为服饰新零售小助手&#xff0c;通过RFID系统与硬件结合&a…

在Springboot中操作Redis——五大数据类型

在Java中操作Redis Redis的Java客户端 前面我们讲解了Redis的常用命令&#xff0c;这些命令是我们操作Redis的基础&#xff0c;那么我们在java程序中应该如何操作Redis呢&#xff1f;这就需要使用Redis的Java客户端&#xff0c;就如同我们使用JDBC操作MySQL数据库一样。 Red…

重温 re:Invent,分享十年成长:我和 re:Invent的故事

文章目录 前言背景我和re:Invent的交际历届峰会主题2012 突破技术垄断2013 革新数据服务2014 更好用的云服务2015 打通最后一-公里2016 迈向云上数据湖时代2017 重构云计算基础2018 云能力的再进化2019 赋能企业云架构服务2020 推动行业数据库服务的演进2021 无可比拟的云架构2…

pdf文件编辑,[增删改查]

pdf文件是投标文件中必不可少的格式&#xff0c;传统的方式先编辑word格式&#xff0c;最后生成pdf&#xff0c;但是有时候需要直接编辑pdf文件&#xff0c;编辑pdf的工具无疑 “adobe acrobat dc”是最好用的之一了 1.把图片文件添加到pdf指定位置&#xff0c;例如把一张图片添…

API网关

API网关的作用 下图显示了详细信息。 步骤 1 - 客户端向 API 网关发送 HTTP 请求。 步骤 2 - API 网关解析并验证 HTTP 请求中的属性。 步骤 3 - API 网关执行允许列表/拒绝列表检查。 步骤 4 - API 网关与身份提供商对话以进行身份​​验证和授权。 步骤 5 - 将速率限制规…

亚马逊云科技Aurora MySQL在复制性能提升上的不断优化和尝试

前言 Amazon Aurora是亚马逊云科技自研的云原生关系数据库&#xff0c;它在提供和开源数据库MySQL、PostgreSQL的完好兼容性同时&#xff0c;也能够提供和商业数据库媲美的性能和可用性。 Aurora的性能提升不仅包含应用读写吞吐量的提升&#xff0c;也包含复制延迟的降低。一个…

echart 柱状图-bar

业务场景一 效果 业务组件调用代码 <template> <barCom :domId"1" :title"barComProps.title" :xAxisData"barComProps.xAxisData" :yAxisProps"barComProps.yAxisProps" :seriseData"barComProps.serise…

在数据库中进行表内容的修改(MYSQL)

根据表中内容&#xff0c;用命令语句创建数据库&#xff0c;表格&#xff0c;以及插入&#xff0c;修改&#xff0c;删除表格中的内容。 创建数据库&#xff1a;zrzy mysql> create database zrzy; 引用zrzy数据库&#xff1a; mysql> use zrzy; 创建student_info表&…