数据结构课程设计(四)---最小生成树

1、任务简述:
利用普利姆算法和克鲁斯卡尔算法实现最小生成树问题

要求:
(1).自行建立图的数据文件,第一行是顶点个数,然后依次是顶点名,接下来是边,用float表示边的权值;
(2).以邻接表或者邻接矩阵表示图皆可,显示输出原图(按照邻接表的样式);
(3).分别利用prim和kruscal算法实现最小生成树(最小生成树用邻接表或邻接矩阵表示均可)。
(4).输出最小生成树(按照邻接表的样式);
(5).比较这两种算法
2、算法描述:
数据结构:
typedef struct arc //邻接表的节点
{
int index; //编号
float weight; //权重
struct arc *next; //指向下一个节点
}AR;

typedef struct MyGraph//用了邻接矩阵和邻接表两种方式来存边:prim算法用邻接表,kruscal算法用邻接矩阵
{
int type;//0表示无向网,1表示有向网
int arcnum; //点的个数
int vexnum; //边的个数
char **vexname; //点的名字
AR *N; //邻接表
float **A;//邻接矩阵动态数组
}GH;

prim算法:
1).输入:一个加权连通图,其中顶点集合为V,边集合为E;
2).初始化:Vnew = {x},其中x为集合V中的任一节点(起始点),Enew = {},为空;
3).重复下列操作,直到Vnew = V:
a.在集合E中选取权值最小的边<u, v>,其中u为集合Vnew中的元素,而v不在Vnew集合当中,并且v∈V(如果存在有多条满足前述条件即具有相同权值的边,则可任意选取其中之一);
b.将v加入集合Vnew中,将<u, v>边加入集合Enew中;
4).输出:使用集合Vnew和Enew来描述所得到的最小生成树。

kruscal算法:
1.新建图G,G中拥有原图中相同的节点,但没有边;
2.将原图中所有的边按权值从小到大排序;
3.从权值最小的边开始,如果这条边连接的两个节点于图G中不在同一个连通分量中,则添加这条边到图G中;
4.重复3,直至图G中所有的节点都在同一个连通分量中。

本题我分别运用两个不同的算法编写了不同的代码,发现,只有当每条边的权重不一样时,两种算法算出的结果一定是相同的,但是如果有相同的权重,那么可能两种算法得到的最小生成树是不一样的。

3、源代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define maxx 9999

typedef struct arc  //邻接表的节点 
{
	int index;    //编号 
	float weight;   //权重 
	struct arc *next;   //指向下一个节点 
}AR;

typedef struct MyGraph//用了邻接矩阵和邻接表两种方式来存边:prim算法用邻接表,kruscal算法用邻接矩阵 
{
	int type;//0表示无向网,1表示有向网
	int arcnum; //点的个数 
	int vexnum; //边的个数 
	char **vexname;  //点的名字 
	AR *N;  //邻接表 
	float **A;//邻接矩阵动态数组
}GH;

void DFS(GH *G,int *visit,int index)//深度优先搜索 
{
	AR *p;
	visit[index]=1;
	p=G->N[index].next;
	while(p)
	{
		if(visit[p->index]==0)
			DFS(G,visit,p->index);
		p=p->next;
	}
}

int isconnect(GH *G)//判断是否连通 
{
	int i;
	int ans;//连通分支
	ans=0; 
	int *visit=(int *)malloc(sizeof(int)*G->vexnum);//标记是否已经经过这个节点 
	memset(visit,0,sizeof(int)*G->vexnum);
	for(i=1;i<G->vexnum;i++)
	{
		if(!visit[i])
		{
			DFS(G,visit,i);
			ans++;
		}
	}
	free(visit);
	if(ans==1)
		return 1;  //连通 
	else
		return 0;  //不联通
}

int findvex(GH *G,char *s)//找点对应的编号 
{
	int i;
	for(i=0;i<G->vexnum;i++)
	{
		if(strcmp(s,G->vexname[i])==0)
			return i;
	}
	printf("Error!\n");
	exit(0);
}

void creatgraph(GH *G)//创建图 
{
	FILE *fp;
	int i,j,mg,n,k;
	char s1[20],s2[20];
	AR *p;
	fp=fopen("081810221zlh.txt","rb");//只读文件 
	if(!fp)
	{
		printf("Can not open file!\n");
		exit(0);
	}
	fscanf(fp,"%d",&n);//读入点的个数 
	G->vexnum=n; 
	printf("图为有向图--1还是无向图--0:"); //手动输入图是有向图还是无向图 
	scanf("%d",&k);
	G->type=k;   
	G->N=(AR *)malloc(n*sizeof(AR));
	G->A=(float **)malloc(n*sizeof(int *));//为邻接矩阵分配n个行指针
	G->vexname=(char **)malloc(n*sizeof(char *));//为姓名矩阵分配n个行指针
	G->arcnum=0;  //边的个数初始化为0 
	for(i=0;i<n;i++) //初始化 
	{
		fscanf(fp,"%s",s1); //读入节点名字 
		G->vexname[i]=(char *)malloc(strlen(s1)*sizeof(char));
		strcpy(G->vexname[i],s1); 
		G->N[i].next=NULL;	//邻接表指针初始化指向空指针 
		G->A[i]=(float *)malloc(n*sizeof(int));
		for(j=0;j<n;j++)
			G->A[i][j]=0;//对第i行的元素初始化,初始值为0
	}
	while(fscanf(fp,"%s%s%d",s1,s2,&mg)!=EOF)//读入边 
	{
		i=findvex(G,s1);   //边的两个顶点之一 
		j=findvex(G,s2);   //边的两个顶点之一 
		(G->arcnum)++;     //边的数量加一 
		p=(AR *)malloc(sizeof(AR));
		p->index=j; //i作为起点,j作为终点    
		p->weight=mg;//j的权重为i到j的权重 
		p->next=G->N[i].next;//把节点p插入到邻接表里(起点是i) 
		G->N[i].next=p;
		G->A[i][j]=mg;        //对邻接矩阵的对应元素赋值
		if(G->type==0)        //G为无向图,则要反着再来一次,和上面一样 
		{
			p=(AR *)malloc(sizeof(AR));
			p->index=i;
			G->A[j][i]=mg;  
			p->weight=mg;
			p->next=G->N[j].next;
			G->N[j].next=p;
		}
	}
	fclose(fp);//关闭文件	
}

void showgraph(GH *G)//用邻接表显示图 
{
	int i;
	float sum=0.;
	AR *p;
	for(i=0;i<G->vexnum;i++)
	{
		printf("\n%s",G->vexname[i]);
		p=G->N[i].next;
		while(p)
		{
			sum=sum+p->weight;			
			printf("--%s(%.2f) ",G->vexname[p->index],p->weight);//控制输出的位数,保持美观 
			p=p->next;
		}		
	}
	printf("\n图的权值为:%f\n\n",sum);
}

int findmin(float *a,int n)//找到数组中最小的正值对应的编号 
{
	int min,i,j;
	min=maxx;
	j=0;
	for(i=0;i<n;i++)//比较排序的思路,但是不能假设第一个为min,防止第一个为0 
	{
		if((a[i]!=0)&&a[i]<min)
		{
			min=a[i];
			j=i;
		}
	}
	return j;
}

void prim(GH *G)   //prim算法 
{
	GH *prim;    //用来存放图G的最小生成树 
	AR *a;
	int i,mmin,nex[G->vexnum];      //mmin为图中树外的点中到树里距离最小点的编号,nex表示与每个点相连的点的编号 
	float min[G->vexnum];           //每个点到当前树的距离 
	
	prim=(GH *)malloc(sizeof(GH));  //初始化图prim---开始 
	prim->vexnum=G->vexnum;         //点的个数和原来一样 
	prim->arcnum=prim->vexnum-1;    //最小生成树边的个数为顶点个数-1 
	prim->type=0;       //无向图 
	prim->N=(AR *)malloc(prim->vexnum*sizeof(AR));
	prim->vexname=(char **)malloc(prim->vexnum*sizeof(char *));
	memset(nex,0,prim->vexnum*sizeof(int)); 
	for(i=0;i<prim->vexnum;i++)
	{
		prim->vexname[i]=(char *)malloc(strlen(G->vexname[i])*sizeof(char));
		strcpy(prim->vexname[i],G->vexname[i]);
		prim->N[i].next=NULL;
	}   //初始化图prim---结束 
	
	min[0]=0;                               //先把第一个点放进树中 
	for(i=1;i<prim->vexnum;i++) //初始化min,都赋值为一个较大值 
		min[i]=maxx;
	for(a=G->N[0].next;a;a=a->next)  //现在树里面只有节点0,对那些距离到0更近的点做一次更新,把原来的距离换位到v0的距离 
		min[a->index]=a->weight;
	for(i=1;i<prim->vexnum;i++)      //每次找到离树距离最小的点并放到树中 
	{
		a=(AR *)malloc(sizeof(AR));
		mmin=findmin(min,prim->vexnum);
		a->index=mmin;
		a->weight=min[mmin];
		a->next=prim->N[nex[mmin]].next;
		prim->N[nex[mmin]].next=a;
		a=(AR *)malloc(sizeof(AR));
		a->index=nex[mmin];
		a->weight=min[mmin];
		a->next=prim->N[mmin].next;
		prim->N[mmin].next=a;
		min[mmin]=0;
		for(a=G->N[mmin].next;a;a=a->next)//对min进行更新 
		{
			if(min[a->index]>a->weight)
			{
				nex[a->index]=mmin;
				min[a->index]=a->weight;
			}
		}
	}
	showgraph(prim);//展示prim生成的最小生成树 
}

void findmmin(GH *G,float *mmin)//寻找矩阵中最小的正值及其所在的位置 
{
	int i,j;
	float min=9999;
	for(i=0;i<G->vexnum;i++)
	{
		for(j=0;j<G->vexnum;j++)
		{
			if(G->A[i][j]!=0&&G->A[i][j]<min)
			{
				min=G->A[i][j];
				mmin[0]=i;  //min[0]存储横坐标 
				mmin[1]=j;  //min[1]存储纵坐标 
				mmin[2]=min;//min[2]存储相应的值 
			}
		}
	}
	G->A[(int)(mmin[0])][(int)(mmin[1])]=0.;//float类型 
}

int get(int *pre,int k)//得到该点最原始的祖先 
{
	int a;
	a=k; 
	while(pre[a]!=a)
		a=pre[a];
	return a;
}

void show(GH *G)//用邻接矩阵展示图 
{
	int i,j;
	float sum=0.;
	for(i=0;i<G->vexnum;i++)
	{
		printf("\n%s",G->vexname[i]);
		for(j=0;j<G->vexnum;j++)
		{
			if(G->A[i][j])
			{
				printf("--%s(%.2f)",G->vexname[j],G->A[i][j]);
				sum=sum+G->A[i][j];
			}
		}
	}
	printf("\n图的权值为:%f\n\n",sum);
}

void kruscal(GH *G)//kruscal算法 
{
	GH *kruscal;//p为新的图,由p来存放生成树的数据 
	int i,j,pre[G->vexnum];//pre指向点的前缀 
	float mmins[3];  //min[0],min[1]表示权值最小的边的两个顶点,min[3]表示权重 
	
	kruscal=(GH *)malloc(sizeof(GH));//初始化 
	kruscal->vexnum=G->vexnum; 
	kruscal->arcnum=kruscal->vexnum-1;
	kruscal->type=0;
	kruscal->A=(float **)malloc(kruscal->vexnum*sizeof(float *));
	kruscal->vexname=(char **)malloc(kruscal->vexnum*sizeof(char *));
	for(i=0;i<kruscal->vexnum;i++)
	{
		pre[i]=i;
		kruscal->vexname[i]=(char *)malloc(strlen(G->vexname[i])*sizeof(char));
		strcpy(kruscal->vexname[i],G->vexname[i]);
		kruscal->A[i]=(float *)malloc(kruscal->vexnum*sizeof(float));
		for(j=0;j<kruscal->vexnum;j++)
			kruscal->A[i][j]=0;
	} //初始化结束 
	
	for(i=1;i<kruscal->vexnum;)//把每一个点连进去 
	{
		findmmin(G,mmins);
		if(get(pre,(int)(mmins[0]))!=get(pre,(int)(mmins[1])))   //如果两个点的祖先不同,则将两个点连起来 
		{
			if(get(pre,(int)(mmins[0]))<get(pre,(int)(mmins[1])))
				pre[get(pre,(int)(mmins[1]))]=get(pre,(int)(mmins[0]));
			else
				pre[get(pre,(int)(mmins[0]))]=get(pre,(int)(mmins[1]));
			kruscal->A[(int)(mmins[0])][(int)(mmins[1])]=mmins[2];//修改相应的邻接矩阵 
			kruscal->A[(int)(mmins[1])][(int)(mmins[0])]=mmins[2];//修改相应的邻接矩阵 
			i++;
		}
	} 
	show(kruscal);//展示kruscal生成的最小生成树 
}

int main()
{
	GH G;
	system("color 1E");
	printf("------------------------------081810221朱林昊------------------------------\n");
	printf("\n------------------------------最小生成树------------------------------\n\n");
	creatgraph(&G);//创建图
	printf("\n------------------------------原图------------------------------\n\n");
	showgraph(&G);//显示图
	printf("\n------------------------------原图显示完毕------------------------------\n");
	if(isconnect(&G))//连通图才有最小生成树 
	{
		printf("\n------------------------------prim算法得到的最小生成树------------------------------\n");
		prim(&G);
		printf("\n------------------------------prim显示完毕------------------------------\n");
		printf("\n------------------------------kruscal算法得到的最小生成树------------------------------\n");
		kruscal(&G);
		printf("\n------------------------------kruscal显示完毕------------------------------\n");
	}
	else
		printf("\n不是连通图,没有最小生成树!!!\n");
	return 0;
}//321行

4、运行结果
原图:
在这里插入图片描述
prim算法:
在这里插入图片描述
Kruscal算法:
在这里插入图片描述
5、总结
性能分析:
prim时间复杂度:O(n^2)
Kruskal时间复杂度:O(eloge)
遇到的问题与解决方法:
对于Kruskal算法:难点在于,选好边以后,需要对目前的图进行一次判断是否有圈的操作,不过这个之前上课讲过了,如果没有,就继续做下一个,如果有圈,那么就寻找除去该条边以外最短的边,直到寻找的边的个数为n-1即可,或者可以用是否连通的函数,直到联通即可。
心得体会:
运行结果正确,输出较为美观,可以发现用最小生成树算出来的树和最短路径得到的树是不一样的,甚至,如果图里面有的边的权重一样的话,那么prim算法和kruscal算法得到的树也是可能不一样的。
存在问题和改进方法:
都是一些经典的算法,只是设计的较为繁琐,使用了邻接矩阵和邻接表来处理,但是网上很多人是通过并查集来实现的,并查集:是一种简单的用途广泛的集合。 并查集是若干个不相交集合,能够实现较快的合并和判断元素所在集合的操作,应用很多,如其求无向图的连通分量个数、最小公共祖先、带限制的作业排序,还有最完美的应用:实现Kruskar算法求最小生成树。

评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值