[刷题之旅no30]P3366 【模板】最小生成树prim和kruskal

经过题意
我是这样理解的
就是找到n-1条边,保证
1.没有闭环
2.长度求和最小
3.如果无法生成这个树,那么输出orz
需要解决的就是这三个问题
最小生成树有两个算法:prim和kruskal算法
我打算都练习一下,所以还是要先学算法
kruskal算法实现
1.edge结构体存图bigin,end,dis
2.结构体排序
3.让当前两个结点连通
4.判断是否闭环
5.如果闭环,取消联通,继续
6.如果不闭环,总路径加和
问题:
1.如何表示结点连通情况
2.如何快速判断是否闭环
问题1不用考虑
问题2利用查并集
取出一条边的ab,
如果find(a)==find(b),直接不用管他了
如果不相等,那么把这两个点所在集合并
即:f[find(a)]=find(b)
关于排序直接用我最喜欢的冒泡即可
第一次出现问题,在我的逻辑方面
circle函数是用来判断是否成圈的
如果两个节点的祖先相等,说明环,说明1
好吧,目前算是TLE了
我这用的查并集,而且还压缩路径了,为什么就TLE了呢?我一定要找出来!!
好吧,是排序效率太低了。等我找到一个更合适的排序算法
合着总结一下,就相当于,拿出无序数组中的数字和有序数组最后一个元素进行比较,如果比你大,咱俩就换,我继续和前面的兄弟比较,如何小于等于你我就老老实实呆着
学习一下希尔排序
for(int gap=m/2;gap>0;gap=gap/2)
for(int i=gap+1;i<=m;i++)
int j=i
whlie(j-gap>=0&&arr[j]<arr[j-gap])
swap()
j=j-gap;
漂亮,希尔排序过了,希尔永神!
kruskal:

#include<stdio.h>
typedef struct
{
	int start,end,dis;
}Edge;
Edge edge[200005],temp;
int n,m,f[5005]={0},mindis=0,cnt=0,flag=0;
void scan()
{
	scanf("%d %d",&n,&m);
	int x,y,z;
	for(int i=1;i<=m;i++)
	{
		scanf("%d %d %d",&x,&y,&z);
		edge[i].start=x;
		edge[i].end=y;
		edge[i].dis=z;
	}
}
void initf()
{
	for(int i=1;i<=n;i++)
	{
		f[i]=i;
	}
}
void sort()
{
	for(int gap=m/2;gap>0;gap=gap/2)
	{
		for(int i=gap+1;i<=m;i++)
		{
			for(int j=i;j-gap>0&&edge[j].dis<edge[j-gap].dis;j=j-gap)
			{
				temp=edge[j];
				edge[j]=edge[j-gap];
				edge[j-gap]=temp;
			}
		}
	}
}
void creat()
{
	scan();
	initf();
	sort();
}
int find(int x)
{
	if(x==f[x]) return x;
	else return f[x]=find(f[x]);
}
int circle(Edge ed)
{
	if(find(ed.start)==find(ed.end))
	{
		return 1;
	}
	return 0;
}
int main()
{
	creat();
	//OK
	for(int i=1;i<=m;i++)//遍历每一条边 
	{
		if(!circle(edge[i]))
		{
			f[find(edge[i].end)]=find(edge[i].start);
			mindis=mindis+edge[i].dis;
			cnt++;
		}
		if(cnt==n-1)
		{
			flag=1;
			break;
		}
	}
	if(flag) printf("%d\n",mindis);
	else printf("orz\n");
	return 0;
}

现在用prim实现一下
1.读取点数和边的数量
2.初始化三个数组Pick,mindis,father
3.链式前向星读取
4.随机取一个点作为顶点,进行遍历即可
然后就是无法生成最小生成树的情况:
就是图不连通,也就是说存在孤立点没有边
在读取过程中只要对出现的边中每个点进行记录就可以,看看是否等于n
第一次提交,0分
出现逻辑错误和程序错误,相当严重
prim无向图,需要考虑起点重合,重边等问题
还是相当复杂啊!
看来要花时间看看代码了
前向星遍历时把i当成node编号了
i是结点编号,edge[i].endnode才是结点编号
OK,但是先在还是没有输出正确答案,所以我们要想办法解决重边和重点问题。
重点问题好解决,关键是重边
按照题目含义,重边的解决问题就是去所有重边中的最小值。
那么给出两个点,我只需要找这个边的编号,然后走最小值就可以了?
我觉得倒是不需要解决这样的问题
因为本身我的算法就是取一个点,遍历和其相邻的所有边,更新最小值
即使重边又何妨,还是我的算法思路出现的问题最致命啊!
检查:
1.创建无向图没有发现问题
2.prim算法里面虽然是以1为开始点,但是却没有把pick[1]设置为1
3.寻找遍历最小边的时候,我错误得将结点编号填入了前向星的边下标,实际上这个前向星的下标就是边的编号,和结点编号无关,我好像在这里犯了不少错误了,我估计是因为mindis的下标其实是结点下标,所以导致我想当然了。
我需要好好反思一下了。
改正之后:
两个RE一个WA,还可以接受
看看到底为什么RE了,是?
哦对了,我们储存的是双向边,也就是说,edge的数组应该开成题目要求的2倍
太好了,终于pass了。看来我也要解决一下我的问题了

#include<stdio.h>
int inf=2147483647;
typedef struct
{
	int endNode,nextEdge,dis;
}Edge;
Edge edge[400010]={0};
int head[5005]={0},pick[5005]={0},father[5005]={0},mindis[5005]={0};
int cnt=0,start,end,dis,n,m,node=1,min,minnode,sum=0,cntnode=0;
void print()
{
	for(int i=1;i<=n;i++)
	{
		printf("i=%d pc=%d fa=%d dis=%d\n",i,pick[i],father[i],mindis[i]);
	}
	for(int i=1;i<=n;i++)
	{
		printf("与%d相连的:\n",i);
		for(int j=head[i];j;j=edge[j].nextEdge)
		{
			printf("点:%d 边:%d\n",edge[j].endNode,edge[j].dis);
		}
	} 
}
void createdge(int from,int to,int dis)
{
	cnt++;
	edge[cnt].nextEdge=head[from];
	edge[cnt].endNode=to;
	edge[cnt].dis=dis;
	head[from]=cnt;
}
void inif()
{
	for(int i=1;i<=n;i++)
	{
		mindis[i]=inf;
	}
}
int creat()
{
	scanf("%d %d",&n,&m);
	for(int i=1;i<=m;i++)
	{
		//这个过程中,我们需要判断是否为连通图 
		scanf("%d %d %d",&start,&end,&dis);
		if(start==end) continue;//起点和终点相等直接跳过 
		if(mindis[start]==0)
		{
			mindis[start]=1;
			cntnode++;
		}
		if(mindis[end]==0)
		{
			mindis[end]=1;
			cntnode++;
		}
		//无向图双向边
		createdge(start,end,dis);
		createdge(end,start,dis);
	}
	if(cntnode>=n)
	{
		inif();//初始化mindis,所以我上面可以用mindis进行判断
		return 1;
	}
	else
	{
		return 0;
	}
}
void prim()
{
	cnt=0;
	int tmp;
	pick[node]=1;
	while(1)
	{
		min=inf;
		//遍历和node相连的结点和他们之间的距离
		for(int i=head[node];i;i=edge[i].nextEdge)//i是边的下标,head[node]即和node相连的第一条边的下标 
		{
			tmp=edge[i].endNode;//和node相连的结点 
//			printf("node=%d\n",tmp);
			if(pick[tmp]==0)//该节点还没有被选中 
			{
				if(mindis[tmp]>edge[i].dis)//比较当前已存在边和当前结点的距离 
				{
					//如果当前的tmp更小,那么更新mindis和father里面的值 
//					printf("min=%d\n",mindis[tmp]);
					mindis[tmp]=edge[i].dis;
					father[tmp]=node;
				}
			}
		}
		//开始找到已经储存的点里面最短的边 
		for(int i=1;i<=n;i++)
		{
			if(pick[i]==0&&mindis[i]!=inf)//从还没有pick的点并且可以到达的边里面挑选 
			{
				if(mindis[i]<min)//如果当前结点边小于min值,更新min值,并记录结点编号 
				{
					min=mindis[i];
					minnode=i;
				}
			}
		}
		//找到所需结点之后,直接选定,并且更新下次要寻找的node为当前的node 
//		printf("对于%d,选择%d\n",node,minnode);
		pick[minnode]=1;
		node=minnode;
		cnt++;
		if(cnt==n)
		{
			break;
		}
//		printf("min=%d\n",min);
		sum=sum+min;
	}
}
int main()
{
	if(creat())
	{
//		print();
		prim();
		printf("%d\n",sum);
	}
	else
	{
		printf("orz\n");
	}
	return 0;
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值