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

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.mzph.cn/news/178759.shtml

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

相关文章

【Linux进阶之路】进程间通信

文章目录 一、原理二、方式1.管道1.1匿名管道1.1.1通信原理1.1.2接口使用 1.2命名管道 2.共享内存2.1原理2.2接口使用 3.消息队列原理 4.信号量引入原理 总结 一、原理 进程间的通信是什么&#xff1f;解释&#xff1a; 简单理解就是&#xff0c;不同进程之间进行数据的输入输出…

Typora+PicGo+Minio搭建博客图床

文章目录 TyporaPicGoMinio搭建博客图床前言什么是图床?为什么需要图床?准备工作一、Typora二、Picgo1. 下载Picgo2. 下载node.js3. 下载minio插件 三、服务器端配置1. 添加端口到安全组2. 使用Docker安装minio3. 配置minio image-20231127175530696四、minio插件配置五、Typ…

C++ day37 贪心算法 单调递增的数字 监控二叉树

题目1&#xff1a;738 单调递增的数字 题目链接&#xff1a;单调递增的数字 对题目的理解 返回小于或等于n的最大数字&#xff0c;且数字是单调递增&#xff08;单调递增数字的定义&#xff1a;每个相邻位上的数字满足x<y&#xff09; 贪心算法 注意本题的遍历顺序是从…

【带头学C++】----- 八、C++面向对象编程 ---- 8.1 面向对象编程概述

目录 8.1 面向对象编程概述 8.1.1 面向对象概念&#xff08;OOP&#xff09; 8.1.2 面向过程概念 8.2 :: 作用域运算符 8.3 命名空间namespace 8.3.1 C的命名空间 namespace 8.3.2 命名空间 namespace使用语法 1. 创建一个命名空间&#xff1a; 2. 命名空间只能在全局…

WIN10 x86环境部署ARM虚拟机(银河麒麟)

我们经常使用的是x86架构的cpu&#xff0c;而对于不同cpu架构的arm架构的操作系统&#xff0c;我们可以通过QEMU模拟器来进行模拟一个arm环境 1、部署前的准备 arm的镜像&#xff1a; 以此镜像为例&#xff1a;Kylin-Server-10-SP2-aarch64-Release-Build09-20210524.iso QE…

Co-DETR:DETRs与协同混合分配训练论文学习笔记

论文地址&#xff1a;https://arxiv.org/pdf/2211.12860.pdf 代码地址&#xff1a; GitHub - Sense-X/Co-DETR: [ICCV 2023] DETRs with Collaborative Hybrid Assignments Training 摘要 作者提出了一种新的协同混合任务训练方案&#xff0c;即Co-DETR&#xff0c;以从多种标…

人力资源管理后台 === 组织架构

目录 1.组织架构-树组件应用 2.组织架构-树组件自定义结构 3.组织架构-获取组织架构数据 4.组织架构-递归转化树形结构 5.组织架构-添加子部门-新建弹层组件 6.组织架构-添加子部门-表单结构 7.组织架构-添加子部门-表单基本校验 8.组织架构-添加子部门-表单业务校验 9…

什么是计算机病毒?

计算机病毒 1. 定义2. 计算机病毒的特点3. 计算机病毒的常见类型和攻击方式4. 如何防御计算机病毒 1. 定义 计算机病毒是计算机程序编制者在计算机程序中插入的破坏计算机功能或者破坏数据&#xff0c;影响计算机使用并且能够自我复制的一组计算机指令或程序代码。因其特点与生…

Redis面试题:分片集群相关问题

目录 面试官&#xff1a;redis的分片集群有什么作用 面试官&#xff1a;Redis分片集群中数据是怎么存储和读取的&#xff1f; 面试官&#xff1a;redis的分片集群有什么作用 候选人&#xff1a;分片集群主要解决的是&#xff0c;海量数据存储的问题&#xff0c;集群中有多个m…

0004Java程序设计-ssm基于微信小程序的校园第二课堂

文章目录 摘 要目录系统设计开发环境 编程技术交流、源码分享、模板分享、网课分享 企鹅&#x1f427;裙&#xff1a;776871563 摘 要 随着信息技术和网络技术的飞速发展&#xff0c;人类已进入全新信息化时代&#xff0c;传统管理技术已无法高效&#xff0c;便捷地管理信息。…

锐捷:下一代防火墙修改密码

一、背景 刚接到的任务&#xff0c;锐捷行业的一台下一代防火墙的管理密码和管理地址都忘记了&#xff0c;并且命令登陆也设置了密码&#xff0c;只能通过重置的方式来进行管理&#xff0c;配置了。 本案例是一台RG-WALL 1600-S3200。 二、配置思路 1、准备环境 2、收集设备软…

“2024杭州人工智能展览会”加快推进浙江省人工智能产业创新发展

杭州市人民政府加快推进人工智能产业创新发展&#xff0c;贯彻党的二十大精神和新的发展理念&#xff0c;把握人工智能技术演进趋势和创新发展新范式&#xff0c;以促进人工智能与实体经济深度融合为主线&#xff0c;以优质算力普惠供给为基础&#xff0c;到2025年&#xff0c;…

linux用户身份切换su和 sudo

su 切换root&#xff0c;但是&#xff0c;环境变量是之前用户的 可以看到利用su切换&#xff0c;根目录还是pro1的 su - 连同环境一起切换成root&#xff0c;切换后工作目录都不一样了&#xff0c;看输入内容左侧信息&#xff0c;和第一个图片比较 -c仅执行一次命令&#xff0…

【刷题笔记】H指数||数组||二分查找的变体

H指数 1 题目描述 https://leetcode.cn/problems/h-index/ 给你一个整数数组 citations &#xff0c;其中 citations[i] 表示研究者的第 i 篇论文被引用的次数。计算并返回该研究者的 h 指数。 根据维基百科上 h 指数的定义&#xff1a;h 代表“高引用次数” &#xff0c;一…

数据结构与算法之美代码:二分查找2

目录 二分查找的变形问题代码 二分查找的变形问题 代码 package com.athome.search;public class BinarySearchDemo {public static void main(String[] args) {int[] arr {1,3,4,5,6,8,8,8,11,18};int index1 bsearch1(arr, arr.length, 8);int index2 bsearch2(arr, arr.l…

第六节HarmonyOS UIAbility内页面的跳转和数据传递

一、页面跳转 在一个应用包含一个UIAbility的场景下&#xff0c;可以通过新建多个页面来实现和丰富应用的内容。这会涉及到UIAbility内页面的新建以及UIAbility内页面的跳转和数据传递。 打开DevEco Studio&#xff0c;选择一个Empty Ability工程模板&#xff0c;创建一个工程&…

MySQL数据库【一】

博学而笃志&#xff0c;切问而近思 文章目录 数据库简介服务器、数据库以及表的关系连接数据库数据库操作命令创建数据库查看数据库创建语句查看数据库使用数据库修改数据库删除数据库 数据库字符集和校验规则查看系统默认字符集查看系统默认校验规则查看数据库支持的字符集查看…

网工内推 | 外企网工,五险一金,弹性工作,最高30k*14薪

01 金蝶软件&#xff08;中国&#xff09;有限公司 招聘岗位&#xff1a;网络工程师 职责描述&#xff1a; 1、合理规划公司网络&#xff0c;保障网络架构的合理性、可靠性及前瞻性&#xff1b; 2、负责公司网络运维&#xff0c;处理日常运维事件&#xff0c;保障网络的稳定可…

Arch Linux 安装 dwm 窗口管理器

窗口管理器是管理桌面上各种窗口的组件&#xff0c;主要功能有&#xff1a;窗口堆叠方式&#xff0c;窗口移动规则等。大多数人接触到的是堆叠式窗口管理器&#xff0c;一个窗口可以叠放在其他窗口之上&#xff0c;调整窗口的主要方式是鼠标。而dwm&#xff08;Dynamic Window …

Python(八十九)函数的参数的内存分析

❤️ 专栏简介&#xff1a;本专栏记录了我个人从零开始学习Python编程的过程。在这个专栏中&#xff0c;我将分享我在学习Python的过程中的学习笔记、学习路线以及各个知识点。 ☀️ 专栏适用人群 &#xff1a;本专栏适用于希望学习Python编程的初学者和有一定编程基础的人。无…