【数据结构应用题】树的应用

树的应用

树的性质

总结树的度、树高、结点数等属性之间的联系

1.树的结点总数等于所有结点的度数之和加1。
2.T的第 i i i i ≥ 1 i\ge1 i1)层最多有 k i − 1 k^{i-1} ki1个结点。
3.高度为h的T中最多有( k h − 1 ) k^{h}-1) kh1)/( k − 1 k-1 k1)个结点( k k k为树的度)。
4.T的最小高度为: h h h= ⌈ l o g k ( n ( k − 1 ) + 1 ) ⌉ \left \lceil log_{k}(n(k-1)+1)\right \rceil logk(n(k1)+1)=
⌊ l o g k n ( k − 1 ) ⌋ + 1 \left \lfloor log_{k}n(k-1)\right \rfloor+1 logkn(k1)+1
5.T的最大高度为 n − k + 1 n-k+1 nk+1( k k k为树的度)。

树(森林)的定义和画图

写代码:使用“双亲表示法”定义顺序存储的树(以及森林)

#define Max_Tree_Size 150   //树的最多结点数
typedef int Elemtype;		//树结点的数据类型
typedef struct PTNode		//树的结点定义(结点结构)
{
	Elemtype data;			//结点数据元素
	int parent;				//双亲位置
}PTNode;
typedef struct PTree				//树的类型表示(树结构)
{
	PTNode nodes[Max_Tree_Size];		//结点数组
	int r,n;							//根的位置和结点数
}PTree;

写代码:使用“孩子表示法”定义链式存储的树(以及森林)

#define Max_Tree_Size 150   //树的最多结点数
typedef int Elemtype;		//树结点的数据类型
typedef struct CTNode		//孩子结点定义
{
	int Child;
	struct CTNode* next;		
}*Childptr;
typedef struct CTBox				//表头结构
{
	Elemtype data;
	Childptr firstchild;			//孩子链表的表头指针
}CTBox;
typedef struct CTree				//树的类型表示
{
	CTBox nodes[Max_Tree_Size];		//结点数组
	int n,r;							//结点数和根的位置
}CTree;

写代码:使用“孩子兄弟表示法”定义链式存储的树(以及森林)

typedef int Elemtype;		//树结点的数据类型
typedef struct CSNode		//孩子结点定义
{
	Elemtype data;							//数据域
	struct CSNode* firstchild,*rightsibling;	//指针域(存储第一个孩子结点的地址和该结点右兄弟的地址)	
}CSNode,*CSTree;
写代码:求以孩子兄弟表示法存储的森林的叶结点数
type struct CSNode	//孩子结点定义
{
	Elemtype data;								//数据域
	struct CSNode *firstchild,*rightsibling;	//指针域
}CSNode,*CSTree;
int Leaves(CSTree t)				//计算以孩子兄弟表示法存储的森林的叶子结点数
{
	if(t == NULL)
		return 0;					//树空返回0
	if(t->firstchild == NULL)		//若结点无左孩子,则该结点必是叶子结点
		return 1+Leaves(t->rightsibling);	//返回叶子结点和其右兄弟子树中的叶子结点树
	else
		return Leaves(t->firstchild) + Leaves(t->rightsibling);		//孩子子树和兄弟子树中叶子子树之和
}
写代码:以孩子兄弟表示法为存储结构设计递归算法求数的深度
int Height(CSTree bt)				//递归求以孩子兄弟表示法的树的深度
{
	int hc,hs;
	if(bt == NULL)
		return 0;
	else						//高度取子女高度+1与兄弟子树高度更大者
	{
	hc = Height(bt->firstchild);		//第一子女树高
	hs = Height(bt->rightsibling);		//兄弟树高
	if(hc + 1 >hs)
		return hc+1;
	else
		return hs;
	}
}

对比“树的孩子表示法存储”vs“图的邻接矩阵存储”vs“散列表的拉链法”vs“基数排序”

自己动手创造,画一个结点总数不少于10的树,并画出对应“双亲表示法”、“孩子表示法”、“孩子兄弟表示法”三种数据结构示意图(至少包含三棵树的森林)

树:
在这里插入图片描述
双亲表示法:
在这里插入图片描述
孩子表示法:
在这里插入图片描述
孩子兄弟表示法:
将树表示成二叉树:
在这里插入图片描述
孩子兄弟表示法示意图:
在这里插入图片描述

二叉树

二叉树的性质

总结二叉树的度、树高、结点数等属性之间的联系

1.二叉树的第k(k>=1)层最多有 2 k − 1 2^{k-1} 2k1个结点。
2.高度为h的二叉树中最多有 2 h − 1 2^{h}-1 2h1个结点。
3.对任意一棵二叉树T,其叶子结点数等于度为二的结点数加1,即 n 0 = n 2 + 1 n_{0}=n_{2}+1 n0=n2+1
4.具有 n ( n ≥ 1 ) n(n\ge1) n(n1)个结点的完全二叉树的高度 h h h满足 h h h= ⌈ l o g 2 ( n + 1 ) ⌉ \left\lceil log_{2}(n+1)\right\rceil log2(n+1)= ⌊ l o g 2 n ⌋ + 1 \left \lfloor log_{2}n \right \rfloor +1 log2n+1
5.完全二叉树中度为1的结点的数目只能是0或1。
6.完全二叉树中叶子结点只可能在最下面两层出现。
7.二叉树的最大高度为 n n n

二叉树的顺序存储和基本操作实现

写代码:定义顺序存储的二叉树(数组实现,树的结点从数组下标0开始存储)

#define Max_Tree_Size 150					//二叉树的最大结点数
typedef int TElemtype;
typedef int Statues;
typedef TElemtype SqBiTree[Max_Tree_Size];	//0号单元存储根结点
void InitSqBiTree(SqBiTree& T) {			//创建一个空的二叉树
	for (int i = 0; i < Max_Tree_Size; i++) {
		T[i] = 0;
	}
}
Statues SetNodes(SqBiTree& T,int i ,TElemtype e) {	//给结点i赋值
	if (T == NULL)return 0;
	if (i >= Max_Tree_Size)return 0;
	T[i] = e;
	return 1;
}
Statues SetLeftchild(SqBiTree& T, int i, TElemtype e) {		//给结点i的左孩子赋值
	if (T == NULL)return 0;
	if (i * 2 >= Max_Tree_Size)return 0;
	T[i * 2] = e;
	return 1;
}
Statues SerRightchild(SqBiTree& T, int i, TElemtype e) {	//给结点i的右孩子赋值
	if (T == NULL)return 0;
	if (i * 2 + 1 >= Max_Tree_Size)return 0;
	T[i * 2 + 1] = e;
	return 1;
}

写代码:定义顺序存储的二叉树(数组实现,树的结点从数组下标1开始存储)

#define Max_Size_Tree 150
typedef int TElemtype;
typedef struct SqBiTree{
	TElemtype *elem;	//元素存储空间基址,建表时按实际长度分配,0号单元留空
	int LSqBiTree;		//表示树的数组最大长度
}SqBiTree;

写代码:运用非递归算法实现先/中/后序遍历

先序遍历:

void PreOrder(BiTNode *T){
	if(T == NULL) return;			//特殊情况处理
	Stack STK = initStack();		//等价于递归栈
	push(STK,T);
	while(!stackEmpty(STK)){
		T = pop(STK);
		visit(T);					//访问当前结点<==>本层递归处理
		if(T->rchild != NULL)	
			push(STK,T->rchild);	//右孩子先入栈<==>后递归处理右子树
		if(T->lchild != NULL)
			push(STK,T->lchild);	//左孩子后入栈<==>先递归处理左子树
	}
}

中序遍历:

void GoAlonglchild(BiTNode *T,Stack &STK){
	while(T != NULL){
		STK.push(T);
		T = T->lchild;
}
void InOrder(BiTNode *T){
	Stack STK = initStack();
	GoAlonglchild(T,STK);
	while(!stackEmpty(STK)){
	T = pop(STK);
	visit(T);
	GoAlonglchild(T->rchild);
}

后序遍历:

void PostOrder(){
	if(T ==NULL) return;
	Stack STK = initStack();		//主栈:实现N-R-L递归遍历
	push(STK,T);
	Stack auxSTK = initStack();		//辅助栈用来保存调整顺序后的结点
	while(!stackEmpty(STK)){
		T = pop(STK);
		// 将N-R-L变成L-R-N
		push(auxSTK,T);				//等价于逆置递归顺序
		if(T->lchild !=NULL)
			push(STK,T->lchild);	//左孩子先入栈<==>后递归处理左子树
		if(T->rchild !=NULL)
			push(STK,T->rchild);	//右孩子后入栈<==>先递归处理右子树
	}
	while (!stackEmpty(auxSTK)){
		T = pop(auxSTK);
		visit(T);					//依次出辅助栈并访问
	}
}

哈夫曼树的应用

哈夫曼树的性质

总结哈夫曼树的度、树高、结点数等属性之间的联系

基础哈夫曼树的性质

1.若哈夫曼树中有 n 0 n_{0} n0个叶子结点,则总共有 2 n 0 − 1 2n_{0}-1 2n01个结点,且度为2(构造过程中新建立)的结点数为 n 0 − 1 n_{0}-1 n01
2.哈夫曼树是一棵二叉树,结点的度只有0或2,没有度为1的结点
3.上一层所有结点的权值均大于下一层。

m叉哈夫曼树的性质

1.若 m m m叉哈夫曼树中有 n 0 n_{0} n0个叶子结点,则总共有 ( m n 0 − 1 ) / ( m − 1 ) (mn_{0}-1)/(m-1) (mn01)/(m1)个结点,且度为 m m m(构造过程中新建立)的结点数为 ( n 0 − 1 ) / ( m − 1 ) (n_{0}-1)/(m-1) (n01)/(m1)
2. m m m叉哈夫曼树结点的度只有 0 0 0 m m m

自己动手创造,写10个字符,并给每个字符设置权值,画出构造哈夫曼树的过程

在这里插入图片描述
在这里插入图片描述

用文字描述构造该哈夫曼树的过程

1.首先,根据给定的字符集合和对应的权重,创建一个包含所有字符的叶子结点集合。
2.从叶子结点集合中选择权重最小的两个结点作为左右子节点,创建一个新的父结点,并将父结点的权重设置为左右子节点的权重之和。
3.将新创建的父结点加入到结点集合中,并从结点集合中移除原来的两个子节点
4.重复步骤2、3直到集合中只剩下一个结点,即根结点。该根结点就是哈夫曼树的根结点。
选择结点4、5作为左右结点得到父结点9加入结点集合中,删除结点4、5。
选择结点6、8作为左右结点得到父结点14加入结点集合中,删除结点6、8。
选择结点9、9作为左右结点得到父结点18加入结点集合中,删除结点9、9。
选择结点10、14作为左右结点得到父结点24加入结点集合中,删除结点10、14。
选择结点18、18作为左右结点得到父结点36加入结点集合中,删除结点18、18。
选择结点24、29作为左右结点得到父结点53加入结点集合中,删除结点24、29。
选择结点36、36作为左右结点得到父结点72加入结点集合中,删除结点36、36。
选择结点45、53作为左右结点得到父结点98加入结点集合中,删除结点45、53。
选择结点72、98作为左右结点得到父结点170加入结点集合中,删除结点72、98。
结点集合只剩下结点170,该结点为此哈夫曼树的根结点。

基于你所构造的哈夫曼树,写出10个字符的哈夫曼编码

A:00000;
B:00001;
C:11010;
D:11011;
E:0001;
F:1100;
G:001;
H:111;
I:01;
J:10;
带权路径长度WPL=(权重X编码位数)求和

用文字描述一棵哈夫曼树“译码的过程”(即如何将二进制哈夫曼编码翻译为字符)

1.首先,根据哈夫曼树的构造过程,得到每个字符对应的编码。编码是由0、1组成的二进制序列,根据字符在哈夫曼树的路径确定。
2.从根结点开始,根据输入的编码位逐步向下遍历哈夫曼树。如果当前位是0,则向左子结点移动;如果当前位是1,则向右子结点移动。
3.当遍历到叶子结点时,即找到了一个字符的编码。将该字符输出,并重新回到根结点,继续下一个编码的遍历。
4.重复步骤2、3直至所有输入的编码全部遍历完毕。
通过以上步骤可将输入的编码序列译码为原始的字符序列。由于哈夫曼树的构造过程保证了每个字符的编码是唯一的,因此译码过程是可靠的。

并查集的应用

写代码:定义一个并查集(用长度为n的数组实现)

typedef int Elemtype;
typedef struct UnionFindSet {
	int n;				//总的元素(结点)个数
	Elemtype* parents;	//存储元素parent的数组的首元素指针
}UnionFindSet;
UnionFindSet Initialize(int n) {	//并查集的初始化
	UnionFindSet ufs;
	ufs.n = n;
	for (int i = 0; i < n; i++) {
		ufs.parents[i] = -1;	//初始时,所有结点的parent设置为-1,表示树内只有一个结点
	}
	return ufs;
}

基于上述定义,实现并查集的基本操作——并Union

int Union(UnionFindSet& ufs, Elemtype e, Elemtype t) {
	int eRoot = Find(ufs, e);
	int tRoot = Find(ufs, t);
	if (eRoot == -1 || tRoot == -1)		//判断是否查找成功
		return -1;
	if (eRoot != tRoot)
		ufs.parents[tRoot] = eRoot;		//将y树合并到x树
	return eRoot;
}

基于上述定义,实现并查集的基本操作——查Find

int Find(UnionFindSet& ufs, Elemtype e) {
	if (e >= ufs.n || e < 0)	//该元素在集合中不存在
		return -1;
	while (ufs.parents[e] >= 0)	//parent值>0,表示有父节点
		e = ufs.parents[e];
	return e;
}

自己设计一个例子,并查集初始有10个元素,进行若干次Union操作,画出每一次Union后的样子

在这里插入图片描述

基于上一步得到的并查集,进行若干次Find操作(每次Find会进行“路径压缩”)画出每次Find(路径压缩)后的样子

在这里插入图片描述

二叉排序树、平衡二叉树的应用题潜在考法

自己设计一个例子,给出不少于10个关键字序列,按顺序插入一棵初始为空的二叉排序树,画出每一次插入后的样子

关键字序列{25,20,99,16,11,1,22,26,42,17}
在这里插入图片描述
对该二叉排序树进行中序遍历得到有序关键字序列{1,11,16,17,20,22,25,26,42,99}

基于你设计的例子,计算二叉排序树在查找成功和查找失败时的ASL

在这里插入图片描述

ASLsuc=(1X1+2X2+3X3+4X3+5X1)/10=3.1;(10为○的个数;1、2、3、3、1为○在该层的个数;1、2、3、4、5为○的层数)
ASLunsuc=(2X1+3X3+4X5+5X2)/11=3.73;(11为□的个数;1、3、5、2为□在该层的个数;2、3、4、5为□的父结点所在的层数)

基于你设计的例子,依次删除4个元素,画出每一次删除之后的样子(需要包含四种删除情况——删一个叶子节点、删一个只有左子树的结点、删一个只有右子树的结点、删一个既有左子树又有右子树的结点)

在这里插入图片描述

自己设计一个例子,给出不少于10个关键字序列,按顺序插入一棵初始为空的平衡二叉树,画出每一次插入后的样子

关键字序列{25,20,99,16,11,1,22,26,42,17}
在这里插入图片描述
对该平衡二叉树进行中序遍历得到有序关键字序列{1,11,16,17,20,22,25,26,42,99}

基于你设计的例子,计算平衡二叉树在查找成功和查找失败时的ASL

在这里插入图片描述
ASLsuc=(1X1+2X2+3X3+4X4)/10=3;(10为○的个数;1、2、3、4为○在该层的个数;1、2、3、4为○的层数)
ASLunsuc=(2X1+3X2+4X8)/11=3.73;(11为□的个数;1、2、8为□在该层的个数;2、3、4为□的父结点所在的层数)

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值