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

news2024/9/24 13:19:09

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);
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Toast UI Editor上传图片到Flask

Toast UI Editor国内文档几乎搜不到&#xff0c;国外文档也写得不是特别项目&#xff0c;没有太多举例的demo。一开始选择使用这个就是因为UI好看。不过看看源码把思路滤清了。 他会给把图片转成Base64&#xff0c;到时候发表单直接丢过去就行了&#xff0c;blob这个参数能拿到…

05 Nacos实战:集成Nacos实现分布式配置中心实现配置动态刷新

上一节介绍了Nacos注册中心的功能,这节介绍下木谷博客中如何使用Nacos作为分布式配置中心。 在第二节搭建项目并运行中讲到创建mugu_nacos_config这个数据库,其中已经包含了木谷博客所需的全部配置,在nacos中也可以查看到,如下: 引入Nacos作为配置中心很简单,步骤如下:…

vue3中的customRef创建一个自定义的 ref对象

customRef 创建一个自定义的 ref&#xff0c;并对其依赖项跟踪和更新触发进行显式控制 小案例: 自定义 ref 实现 debounce <template><div style"font-size: 14px;"><input v-model"text" placeholder"搜索关键字"/><…

flask 上传文件

from flask import Flask, request, render_template,redirect, url_for from werkzeug.utils import secure_filename import os from flask import send_from_directory # send_from_directory可以从目录加载文件app Flask(__name__)#UPLOAD_FOLDER media # 注意&#xff…

【TiDB】TiDB离线方式部署

目录 1 下载TiDB离线组件包 2 安装TiUP 3 合并离线包 4 TIDB 软件和硬件环境建议配置 5 TiDB环境与系统配置检查 6 生成集群初始化配置文件模板 7 执行部署命令 1 检查就能存在的潜在风险 2 手动修复风险 3 部署 TiDB 集群 8 查看TIUP管理的集群情况 9 检查部署的…

[⑥ADRV902x]: 软件系统初始化流程学习

前言 本篇博客主要记录ADRV902x参考软件中对ADRV902x系统的初始化流程&#xff0c;使用API函数来实现transceiver的配置&#xff0c;校准和控制等。官方将整个系统初始化称之为multichip synchronization initialization (MCS) sequence&#xff0c;主要分成PreMcsInit&#x…

数字电源为什么一般用DSP控制,而不能用普通的单片机?

数字电源为什么一般用DSP控制&#xff0c;而不能用普通的单片机&#xff1f; 首先你要清楚&#xff0c;数字电源需要一个芯片具备什么功能&#xff1f; 1 能发PWM波 &#xff0c;并且具备保护关断功能&#xff1b; 电源对PWM发波 要求很高&#xff0c;精度要ns级甚至ps级的&…

易宝OA系统ExecuteSqlForSingle接口SQL注入漏洞复现 [附POC]

文章目录 易宝OA系统ExecuteSqlForSingle接口SQL注入漏洞复现 [附POC]0x01 前言0x02 漏洞描述0x03 影响版本0x04 漏洞环境0x05 漏洞复现1.访问漏洞环境2.构造POC3.复现 易宝OA系统ExecuteSqlForSingle接口SQL注入漏洞复现 [附POC] 0x01 前言 免责声明&#xff1a;请勿利用文章…

2023极客大挑战-AGRT战队wp

目录 RE Shiftjmp 点击就送的逆向题 幸运数字 ​编辑 砍树 小黄鸭 flower-or-tea mySelf 是男人就来扎针 听说cpp很难&#xff1f; Easymath 寻找初音未来 Rainbow 浪漫至死不渝 ezandroid Pwn nc_pwntools password ret2text write1 ret2libc ezpwn wr…

MicroPython STM32F4 RTC功能使用介绍

MicroPython STM32F4 RTC功能使用介绍 &#x1f516;STM32和ESP32 RTC功能差不多&#xff0c;相关篇《MicroPython ESP32 RTC功能使用介绍》&#x1f4cc;固件刷可参考前面一篇《STM32刷Micropython固件参考指南》&#x1f33f; 相关篇《Micropython STM32F4入门点灯》&#x1…

v-model(双向数据绑定)自动收集数据

v-model 是 Vue 中一个常用的指令&#xff0c;用于实现表单元素与数据的双向绑定。 它的实现原理主要基于以下两个方面&#xff1a; 语法实现&#xff1a; v-model 实际上是 Vue 对 :value 和 input 两个属性的语法糖。当我们在组件中使用 v-model 指令时&#xff0c;Vue 会根…

只需十分钟快速入门Python,快速了解基础内容学习。零基础小白入门适用。

文章目录 简介特点搭建开发环境版本hello world注释文件类型变量常量数据类型运算符和表达式控制语句数组相关函数相关字符串相关文件处理对象和类连接mysql关于Python技术储备一、Python所有方向的学习路线二、Python基础学习视频三、精品Python学习书籍四、Python工具包项目源…

数据在内存中的存储练习题

数据在内存中的存储练习题 文章目录 数据在内存中的存储练习题1. 练习一2.练习二3. 练习三4. 练习四5. 练习五6. 练习六7. 总结 1. 练习一 #include <stdio.h>int main() {char a -1;signed b -1;unsigned char c -1;printf("a %d b %d c %d", a, b, c)…

WebSocket快速入门

WebSocket 借鉴&#xff1a; https://blog.csdn.net/weixin_45747080/article/details/117477006 https://cloud.tencent.com/developer/article/1887095 简介 WebSocket 是一种网络传输协议&#xff0c;可在单个 TCP 连接上进行全双工通信&#xff0c;位于 OSI 模型的应用…

第七节HarmonyOS UIAbility生命周期以及启动模式

一、UIAbility生命周期 为了实现多设备形态上的裁剪和多窗口的可扩展性&#xff0c;系统对组件管理和窗口管理进行了解耦。UIAbility的生命周期包括Create、Foreground、Background、Destroy四个状态&#xff0c;WindowStageCreate和WindowStageDestroy为窗口管理器&#xff08…

27.0/多态/对象向上转型/向下转型/抽象类/抽象方法。

目录 27.1为什么使用多态? 27.1.2什么是多态 27.1.3对象多态 27.1.4多态的使用前提 27.2 向上转型 27.3向下转型 (面试题) 27.4抽象类和抽象方法 特点(面试题): 27.1为什么使用多态? 需求1&#xff1a;动物园让我们实现一个功能&#xff1a; 创建一个狗类 &#xff0c;狗…

【AICFD案例教程】PCB多变量AI预测分析

AICFD是由天洑软件自主研发的通用智能热流体仿真软件&#xff0c;用于高效解决能源动力、船舶海洋、电子设备和车辆运载等领域复杂的流动和传热问题。软件涵盖了从建模、仿真到结果处理完整仿真分析流程&#xff0c;帮助工业企业建立设计、仿真和优化相结合的一体化流程&#x…

qt实现一个安卓测试小工具

qt实现一个安卓测试小工具 最终效果&#xff1a;目录结构源码gui.py 主要是按钮&#xff0c;文本控制代码main.py 主要是逻辑代码gui.spec 是打包使用的adb.ui 最终效果&#xff1a; 目录结构 上面2个是打包的生成的不用管 源码 gui.py 主要是按钮&#xff0c;文本控制代码…

微信小程序开发——开发账号注册与配置

版权声明 本文原创作者&#xff1a;谷哥的小弟作者博客地址&#xff1a;http://blog.csdn.net/lfdfhl 概述 本文的重点在于介绍注册微信小程序开发账号的步骤及其流程。 账号注册 请点击官方网站右上角的 https://mp.weixin.qq.com/ 立即注册&#xff0c;图示如下&#xf…

Python 和 Node.js 之间通信 JSON 数据

更多资料获取 &#x1f4da; 个人网站&#xff1a;ipengtao.com 在实际应用中&#xff0c;不同编程语言之间的通信是常见的需求。Python和Node.js是两个流行且功能强大的编程语言&#xff0c;它们之间使用JSON格式进行数据交换是一种高效和灵活的方式。本文将详细介绍如何在Py…