南航数据结构上机作业6---树的常用操作

一、调试成功程序及说明
1、
题目:
1、利用递归算法输出根节点到所有叶子节点的路径;
算法思想:
用一个数组path[N]来存储他的祖先,即根节点到该节点的路径,然后调用allpath()函数,判断该节点是不是叶子节点,是的话就输出,否则继续向下(孩子)走。
运行结果:
在这里插入图片描述
结果分析:
运行正确。
附源程序

void allpath(TR *T,char *path,int m)
{
	TR *p;
	int i;
	if(T)
	{
		if(!(T->fir))   //该节点为叶子,那么就输出当前的路径,因为path里面存的都是他的祖先 
		{
			path[m] = T->data;
			printf("Step :");
			for(i=0;i<m;i++)
				printf("%c-->",path[i]);
			printf("%c\n",path[m]);   //为了输出好看,所以把最后一个拿出来了 
		}
		else    //开始寻找以T为根节点的子树的所有路径
		{
			path[m]=T->data;   
			m++;
			p = T->fir;   //先找第一个孩子 
			while(p)
			{
				allpath(p,path,m);   
				p = p->sib;   //然后寻找他的所有兄弟 
			}
		}
	}
}

void getpath1(TR *T)//递归算法输出根节点到所有叶子节点的路径
{
	int m=0;
	char path[N];
	allpath(T,path,m);
}

2、
题目:
2、利用非递归算法输出根节点到所有叶子节点的路径;
算法思想:
法一:
运用后序遍历(即孩子—兄弟树的中序遍历),判断当前栈顶元素是否为叶子,是的话,就输出栈的所有元素(不是弹栈,只弹栈顶)
法二:
运用层次遍历,加上一个front[N]来记录父亲节点,然后对队列遍历一次即可,判断里面的元素是不是叶子节点,是的话,就按照front[N]来输出路径,这样做的好处在于,可以很方便的找出最长/最短的路径。(输出的路径的长度也是从小到大的)
运行结果:
在这里插入图片描述
结果分析:
运行正确。
附源程序:
法一:

void getpath2(TR *T)//非递归算法---后序---输出根节点到所有叶子节点的路径
{
	TR *t[N],*p;
	int top = -1,count=0,i;
	p = T;
	while(top>=0||p)
	{
		while(p)
		{
			top++;
			t[top] = p;
			p = p->fir;
		}
		p = t[top];
		top--;
		if(p->fir==NULL)
		{
			count++;
			printf("Step %d:",count);
			for(i=0;i<=top;i++)
				printf("%c-->",t[i]->data);
			printf("%c\n",p->data);
		}
		p = p->sib;
	}
}

法二:

void getpath3(TR *T)//非递归算法---层次---输出根节点到所有叶子节点的路径
{
	TR *t[N],*p;
	int front[N],count=0,i,tmp; 
	int head=0,rear=0;
	t[rear] = T;
	front[rear] = -1;
	rear++;
	while(rear>head)
	{
		p = t[head];
		p = p->fir;
		while(p)
		{
			t[rear] = p;
			front[rear] = head;
			rear++;
			p = p->sib;
		}
		head++;
	}
	printf("路线长度从小到大排序为:\n");
	for(i=0;i<rear;i++)
	{
		if(!(t[i]->fir))
		{
			count++;
			printf("Step %d:%c",count,t[i]->data);
			tmp = front[i];
			while(tmp>=0)
			{
				printf("<--%c",t[tmp]->data);
				tmp = front[tmp];
			}
			printf("\n");
		}
	}
} 

3、
题目:
3、利用递归算法统计数中叶子节点的数量;
算法思想:
判断当前节点是不是叶子节点,是的话就返回1,否则继续选找以该节点为根节点的子树(可以递归了),找出他所有的叶子节点的个数,并且用m来存储。
运行结果:
在这里插入图片描述
结果分析:
运行正确
附源程序:

int leaftree(TR *T)//递归计算并返回树的叶子节点的数量
{
	TR *p;
	int m=0;
	if(T)     //T为真进入 
	{
		if(!(T->fir))   //找到叶子 
			return 1;   //返回1,让最终的m加1 
		else   //否则找完以T为根节点的分支
		{
			p = T->fir;  //p赋值为T的第一个孩子,开始寻找T的第一个孩子为根节点的叶子数量
			while(p)
			{
				m = m+leaftree(p);
				p = p->sib;     //寻找T的其他孩子(即T的第一个孩子的兄弟)为根节点的叶子数量 
			}      //结束,说明以T为根节点的子树已经找完。 
		}
	}
	return m;  //返回最后该(子)树的所有叶子。 
}

4、
题目:
计算树的宽度(包含节点数最多的那一层节点数量);
算法思想:
我们用一个数组height记录高度,即当前节点的所在高度,width记录每一层的节点个数,然后因为height是单调有界的,只需要一次遍历,对相应的width进行++操作就行了(width[height[i]]++),这个灵感来自于原来的一道题;数字范围为1-100000的正数。这样可以有效的降低时间复杂度。
运行结果:
在这里插入图片描述
结果分析:
运行正确
附源程序

int weidth(TR *T)//输出树的宽度(包含节点数量最多的那一层的节点数量即为树的宽度)
{
	TR *t[N],*p;
	int head=0,rear=0,i,max;  //max记录点数量最多的那一层的节点数量
	int height[N],width[N]={0};   //height记录高度,即当前节点的所在高度,width记录每一层的节点个数 
	height[rear] = 1;         
	t[rear] = T;
	rear++;    //入队 
	while(rear>head)  //层次遍历 
	{
		p = t[head];
		p = p->fir;
		while(p)
		{
			height[rear] = height[head]+1;   //记录该节点的高度 
			t[rear] = p;
			rear++;
			p = p->sib;
		}
		head++;    
	}
	for(i=0;i<rear;i++)     //记录每一层的节点个数 
		width[height[i]]++;
	max = width[1];          //寻找最大的 
	for(i = 2;i<height[rear-1];i++)   //注意到height里面的元素的单调性,就可以轻松判断count的元素个数
		if(width[i]>max)
			max = width[i]; 
	return max;
}

5、
题目:
在指定位置插入一个节点(选做);
算法思想:
首先先运用后序遍历找到插入点的位置(这里运用层次也可以,那么和上面的选找路径一样,再加一个front[N]来存储父节点的位置),然后对i分类讨论,0或者>0,i=0的话,就插入在p的fir,和p->fir->sip之间,否则就插在p1和p1->sip之间,这里的p1只需要一次简单的遍历就可以找到了。但是要记住,最后插入节点S后,S->fir要赋值为NULL。
运行结果:
在这里插入图片描述
结果分析:
运行正确
附源程序

void insdata(TR *T,char subroot,int i,char data)
{
	TR *t[N],*p=T,*p1,*p2,*S;  //p为插入的位置的父节点subroot,p1为插入的位置的上一个兄弟,p2为插入的位置的下一个兄弟,S为插入的节点
	int top = -1;
	int flag = 0;  //判断插入位置是否存在
	if(T)
	{     
		while(top>=0||p)    //后序遍历,选找插入的位置 
		{
			while(p)
			{
				top++;
				t[top] = p;
				p = p->fir;
			}
			p = t[top];
			top--;
			if(p->data==subroot)
			{
				flag = 1;
				break;
			}
			p = p->sib;
		}
		if(flag==1)       //说明插入位置存在 
		{
			S = (TR *)malloc(sizeof(TR));   //动态分配空间 
			S->data = data;
			p1=p;
			p2=p1->fir;
			if(i==0)   //分情况,是第一孩子,还是兄弟,这里的代码不一样的,一个是fir,一个是sip,无法代码合并 
			{
				p->fir = S;
				S->sib = p2;
			}
			else
			{
				while(i>0)   //找是插在第几个分支的后面 
				{
					i--;
					p1 = p2;
					p2 = p2->sib;
					if(p2==NULL)  //如果i大于该结点的分支总数,则就在该结点的最后一个分支的后边做插入 
						break;
				}
				p1->sib = S;
				S->sib = p2;
			}
			S->fir = NULL;   //这个不能丢,要给S的第一个孩子赋值为NULL 
		}
	}
}

6、
题目:
销毁以指定节点为根节点的子树(选做);算法思想:
运行结果:
在这里插入图片描述
结果分析:
运行正确
附源程序

TR *delsubtree(TR *T,char s)//销毁以包含数据s为根节点的子树,并返回删除后树的根节点指向
{
	TR *t[N],*p,*p1,*p2;
	int top = -1;
	p = T;
	while(top>=0||p)
	{
		while(p)
		{
			top++;
			t[top] = p;
			p = p->fir;
		}
		p = t[top];
		top--;
		if(p->data==s)
			break;
		p = p->sib;
	}
	p1 = t[top];
	p2 = p1->fir;
	if(p2==p)
		t[top]->fir=p->sib;
	else
	{
		while(p2)
		{
			if(p2==p)
				break;
			p1 = p2;
			p2 = p2->sib;
		}
		p1->sib = p->sib;
	}
	deltree(p);
	return T;
}

二、小结
大概1个多小时写完了,因为之前练习了老师上课的代码tree.c很多次,除了第五个忘了写S->fir=NULL外,别的都是一遍过(写完直接编译,运行正确),感觉这次题目很简单,但是我不会掉以轻心,后面还会继续练习(因为今天练习迷宫的时候,我发现我只记得用栈和队列来写了,对于递归的有点忘了)。
很多操作都用到了后序/层次遍历,感觉这两种遍历方式很常用。以及感觉有一个模板,就是,递归的,都有这个样子的代码:

p = T->fir;  //有时更改为p->fir->sib
While(p)
{
递归函数(p,...);
P = p->sib;
}

感觉有点像套路。

  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值