数据结构大作业——树(和广义表)

数据结构大作业——树(和广义表)

以广义表形式输入一棵树,然后以合适的比例将这棵树展示出来

效果图
(如何构造一个广义表已经略去)
对于广义表化的树,我们采用的树节点类似二叉链表形式的存储。

首先设计结点内容

typedef char ElemType;				//数据项类型
typedef struct tnode 				//定义结点类型 
{      ElemType data;					//结点的值————由广义表给出
		int width;						//子叶宽度————设计算法求子叶宽度
		int height;						//结点高度————初始化时设置结点的高度
		int pos;						//结点位置————设计算法求结点位置
		int ifbro=1;					//是否有兄长————根据兄弟指针的内容判断是否有兄长结点
        struct tnode *hp; 	 			//指向兄弟————初始化时进行赋值
        struct tnode *vp; 	 			//指向孩子结点————初始化时进行赋值
} TSBNode;							//树节点

针对结点内容设计对应的求值算法

子叶宽度:即孩子结点的总宽度——每个叶子结点占1个宽度,求取每层的结点所占宽度。

int TWidth(TSBNode* &tree)	//计算子叶宽度 
{
	int width=0;			//初始化为0 
	TSBNode *treebro;		//孩子们的长兄结点 
	
	if(tree->vp!=NULL)		//有子女时计算子女总的宽度 
	{
		treebro=tree->vp;
		while(treebro!=NULL)
		{
			width+=TWidth(treebro);
							//将子女数量转化为子叶宽度 
			treebro=treebro->hp;
							//循环终止条件的一部分 
		}
	}
	else					//这就到了叶子结点了
	return 1;				
	return width;			//返回该结点的子叶宽度 
}

得到计算子叶宽度的算法之后,需要为每一个结点赋值。

void Twidth(TSBNode* &tree)		//为结点子叶宽度赋值 
{
	tree->width=TWidth(tree);
	if(tree->hp!=NULL)
	{
		Twidth(tree->hp);		//将函数推向兄弟结点 
	}
	if(tree->vp!=NULL)
	{
		Twidth(tree->vp);		//将函数推向子女结点 
	}
	return;
}

至此,调用Twidth函数会为输入的树每一个结点进行宽度赋值。

结点高度:可以变相认为是每个结点所在的层数

结点的位置:设计输出时,用位置变量标记每一个结点在效果图上的位置距离左端位置有多少个单位长度。

void  LocateTreep(TSBNode* &tree,int fpos,int fwidth,int flag)		//计算一个结点应放置位置 
{
	if(flag)															//表示从父结点继承pos 
	tree->pos=fpos-(fwidth/2+1)+(tree->width/2+1);
	else																//表示从兄长结点继承pos 
	tree->pos=fpos+(fwidth/2)+(tree->width/2+1);
}

关于为什么函数中会出现fwidth/2+1和fwidth/2这样“奇怪的长度”,可以动手画制一棵树,观察层与层之间树结点的分布,以及层内兄弟结点的分布。
接下来为每一个结点赋上位置信息:

void  LocateTree(TSBNode* &tree)									//实现各个结点的定位 
{
	if(tree->height==1)					//如果是根节点 
	tree->pos=tree->width/2+1;
		if(tree->hp!=NULL)				//将函数推向兄弟结点 
	{
		LocateTreep(tree->hp,tree->pos,tree->width,0);
		LocateTree(tree->hp);
	}
		if(tree->vp!=NULL)				//将函数推向孩子结点 
	{
		LocateTreep(tree->vp,tree->pos,tree->width,1);
		LocateTree(tree->vp);
	}
}

构造树——在从广义表形式看树的时候我们可以获得很多信息:结点的值域、节点的高度、结点是否有孩子、结点是否有兄弟以及结点是否为长兄。

接下来的构造实际上融合了多个算法,只是求取这些内容的过程中很多循环过程、判断过程是相似的,故结合在一起作为一个大的构造函数。
读者可以适当将函数进行拆分,得到多个不同的函数适用于求取不同的量值。

TSBNode CreatTree(TSBNode* &tree,char *str)				//构造树 
{
	TSBNode *St[MAXSIZE],	*Fake_St_top[MAXSIZE],	*p=NULL;
//		  双亲结点			兄弟结点					备用指针结点 
	int top=-1,k,j=0;									//记录层数top;分类标志k;循环变量j; 
	char ch;											//当前广义表指示变量ch; 
	ch=str[j];
	tree=NULL;											//基底树 	 
	while (ch!='\0')
	{
		switch(ch)
		{
			case'(':top++; St[top]=p; k=1; break;		//构造该树的孩子树
			case')':top--; break;						//向上一层回到双亲结点
			case',':k=2;break;							//构造该树的兄弟结点
			default:p=(TSBNode *)malloc(sizeof(TSBNode));			//开始构造结点
				p->data=ch;								//设置值域
				p->hp=NULL;								//设置兄弟指针
				p->vp=NULL;								//设置孩子指针
				p->height=top+2; 						//设置高度(个人习惯将根节点记为第1层,以此类推)
				if (tree==NULL)		//从根结点构造开始
				 {
					tree=p;		
					tree->ifbro=0;
				}
				else									//很重要的部分(建议搭配画图理解)
				{
					switch(k)
					{	
						case 1:	St[top]->vp=p;											//将该结点与双亲结点联系
								St[top]->vp->ifbro=0; 									//该结点作为长兄结点,没有兄长!																
								Fake_St_top[top]=p; break;								//指针后移 
						case 2: Fake_St_top[top]->hp=p;									//将该结点与兄长相联系
								Fake_St_top[top]=p; break; 	 							//指针后移	
					}
				}
		}
		j++;											//继续处理广义表的下一位符号												
		ch=str[j];										//更新变量(即装入下一位符号)
	 } 
	//这两个函数可以在main函数里调用,为了构造树时的完整性,直接选择了在此处调用。
	 Twidth(tree); 									//定宽
	 LocateTree(tree);								//定位
}

为什么这里并没有设return语句呢?首先这里采用了比较高级一些的TSBNode* &tree——&符号的用法直接使tree作为了返回型参数。那为什么TSBNode换成void就会失败呢?我还未做进一步的学习……(心虚,大佬们路过看到的话就帮我解释在评论去吧,小弟先谢过……

这就有一棵内容齐全、形式规整的链表形式存储的树了。

合适的方法输出树

从例图,我们要知道输出合适的树,关键是:结点的位置!结点的位置需要有结点的高度和结点的位置,结点的高度已经求出来了,结点的位置我们也有了,如何输出是一门学问。
在此我采用了树的层次遍历的方法输出,但并不仅仅输出值域内容,还有穿插的制表符来划清各结点之间的位置关系。

void DispTree(TSBNode* &tree)  							//显示构造树		
{
//**************************************入队部分******************************************
	TSBNode *Qu[MAXSIZE],*treebro;						//采用循环队列方式存储————为了层次遍历 
														//treebro并不是长兄指针————仅仅是指向兄弟指针
	int front=0,rear=0,op=0;							//front是队首指针;rear是队尾指针;op				
    if (tree!=NULL) 									//拒绝空树
    rear++;												//队尾指针移动————准备存放数据
	Qu[rear]=tree;										//层次遍历————第一层
    while (rear!=front)
    {
    	front++; 
		if (Qu[front]->vp!=NULL)						//如果有孩子结点,将孩子结点一串入队	
		{	
			treebro=Qu[front]->vp;						//兄弟指针就位
			while(treebro!=NULL)
			{ 
				rear=(rear+1)%MAXSIZE;
				Qu[rear]=treebro;
				treebro=treebro->hp;					//兄弟指针后移
			}
		}
	} 
	rear++;												//后移一位,为了构成下面的循环终止条件
//***************************************出队部分****************************************
	front=1;										
	while (rear!=front)									
	{
	
	
		if(Qu[front]->height>Qu[front-1]->height)		//判断是否应该换层
		{
			printf("\n\n");								//层间距问题,个人审美偏好啦~
			op=0;
		}
														//到达pos之前循环输出制表符以求合适比例
		while(op<Qu[front]->pos)
		{
			printf("\t");
			op++;
		}
		if(Qu[front]->ifbro==0)							//没有兄长结点应该冠以“{”号界定层范围
		printf("\b{");
		printf("%c",Qu[front]->data);
		if(Qu[front]->hp==NULL)							//没有兄弟结点应该冠以“}”号界定层范围
		printf("}");
		front++;										//循环终止条件									
		}
}

最后的主函数就只需要调用几个函数即可:

int main()
{ 
//****************************************接受数据
	printf("请输入广义表形式表示的树:\n"); 
	char s[MAXSIZE]="\0";
	scanf("%s",s);
//****************************************完成目的
	char*str=s;
	TSBNode* tree;
	CreatTree(tree,str);			//构造树
	DispTree(tree);					//输出数
	return 0;
}
																							To be continued……
发布了7 篇原创文章 · 获赞 2 · 访问量 2099

猜你喜欢

转载自blog.csdn.net/weixin_44486547/article/details/105471731