C语言-最小生成树(Prim算法)

  1. 查找函数(LocateVex查找坐标)
  2. 构建无向网(Undirected Network)
  3. 输出邻接矩阵(print)
  4. 最小值函数(minimal)
  5. 普里姆算法(MiniSpanTree_Prim)

最小代价生成树背景

  • 假设在n个城市之间建立铁路网,在每两个城市之间都可设置一条线路,每条线路对应要付出的代价不同,n个城市之间最多可生成(n2-n)/2条线路,我们如何设计线路才能用最少的路径(最少要n-1条)最低的成本架设完这些铁路线路?
  • 即使我们用最少的线路,即n-1条线路来贯穿n个城市,结果不唯一,付出的总代价也不唯一,我们将每一个结果称作生成树,而总花费最少的我们称作最小代价生成树(Minimum Cost Spanning Tree),简称最小生成树。

MST性质及理解

  • MST性质定义:设G=(V,E)是一个连通网,U是顶点集V的一个真子集。若(u,v)是G中一条具有最小权值的边,其中u∈U,v∈V-U,则一定存在G的一棵最小生成树包括此边(u,v)。
  • 直白的意思就是说:整个图(网)G中权值最小的边一定会是最小生成树的边。
  • 如何理解呢?其实我们可以将问题简化一点,假设图G有3个顶点三条边,AB、AC和BC,权值分别为3、4、5,那么此时在这个简单图中的最小生成树肯定会包含权值为3的AB。因为A结点在选择AB或AC的时候会选权值小代价低的那个。
  • 那么这下我们也就明白了,其实这个MST也就是——贪心算法,选择当前情况下的最优解,即对应了MST中选择代价最小的。当顶点A在选择的时候,只能选择A-B或A-C,那么A-B的代价更小,故选择A-B。这就是MST性质。
  • 普里姆算法(Prim)克鲁斯卡尔算法(Kruskal) 均是基于MST性质。
    在这里插入图片描述

最小生成树算法:

  1. 普里姆算法(Prim):对顶点操作,在最小生成树的顶点集U待处理顶点集V-U中,不断地寻找最短边(代价最小变),找到后将对应顶点加入集合U,直到所有顶点均处理完毕(V-U里没有剩余顶点)
  2. 克鲁斯卡尔算法(Kruskal):对操作,每次选取一条最短边,如果不会和当前最小生成树构成(回路),将此最短边加入最小生成树中。当选取了n-1(顶点数-1)条边,或找出了所有符合条件的不成环边最小生成树生成完毕。

普里姆算法:

  • 普里姆算法其实是在U和V-U两个阵营中不停的找一条最短的(代价最低的)可连通的边,然后将该边附着的在V-U阵营中的顶点加入U阵营

在这里插入图片描述

如何用程序实现Prim算法

  1. 首先我们需要一个结构体数组:最短路径数组shortedge来存储当前各个顶点之间的最短路径信息,其中的adjvex用于存储最短边的邻接点,lowcost是其对应权值,也就是当前最小的代价。
typedef struct//最短路径数组结构体(候选最短边) 
{
	VertexType adjvex;//候选最短边的邻接点 
	int lowcost;//候选最短边的权值 
}ShortEdge;

2.1 对起始点start处理:
① 对shortedge数组写入初始化信息
② 将起始点放入集合U中,即 shortedge[k].lowcost=0;//lowcost为0表示该顶点属于U集合
2.2 对后续顶点进行处理:
① 通过minimal函数找到最小路径所对应的的顶点
② 输出最小路径信息
③ 将此路径对应的顶点放入集合U中(将其对应的lowcost改为0)
④ 更新shortedge数组(集合U中加入新的顶点,阵营U中有可能生成新的最小路径到达阵营V-U中)

void MiniSpanTree_Prim(MGraph *G,VertexType start)
{ 
	int i,j,k;
	ShortEdge shortedge[VertexMax];
	
	//1.处理起始点start 
	k=LocateVex(G,start);
	for(i=0;i<G->vexnum;i++)
	{
		shortedge[i].adjvex=start;
		shortedge[i].lowcost=G->AdjMatrix[k][i];
	}
	shortedge[k].lowcost=0;//lowcost为0表示该顶点属于U集合 
	
	//2.处理后续结点 
	for(i=0;i<G->vexnum-1;i++)//对集合U,去找最短路径的顶点 
	{
		k=minimal(G,shortedge);//找最短路径的顶点 
	
	    printf("%c->%c,%d\n",shortedge[k].adjvex,G->Vertex[k],shortedge[k].lowcost);//输出找到的最短路径顶,及路径权值 
	    shortedge[k].lowcost=0;//将找到的最短路径顶点加入集合U中 
	    
	    for(j=0;j<G->vexnum;j++)//U中加入了新节点,可能出现新的最短路径,故更新shortedge数组 
	    {
	    	if(G->AdjMatrix[k][j]<shortedge[j].lowcost)//有更短路径出现时,将其替换进shortedge数组 
	    	{
	    		shortedge[j].lowcost=G->AdjMatrix[k][j];
	    		shortedge[j].adjvex=G->Vertex[k];
			}
		}
	    
	 } 
}
  1. 求最小值的函数(minimal):只需要在当前shortage数组中比较出lowcost最小的元素,返回它的下标loc即可在Vertex数组中找到该元素。

完整源代码

#include <stdio.h>
#include <stdlib.h>
#define VertexMax 20 //最大顶点数为100
#define MaxInt 32767 //表示最大整数,表示 ∞ 

typedef char VertexType; //每个顶点数据类型为字符型 

typedef struct//邻接矩阵结构体 
{
	VertexType Vertex[VertexMax];//存放顶点元素的一维数组 
	int AdjMatrix[VertexMax][VertexMax];//邻接矩阵二维数组 
	int vexnum,arcnum;//图的顶点数和边数 
}MGraph;

typedef struct//辅助数组结构体(候选最短边) 
{
	VertexType adjvex;//候选最短边的邻接点 
	int lowcost;//候选最短边的权值 
}ShortEdge;

int LocateVex(MGraph *G,VertexType v)//查找元素v在一维数组 Vertex[] 中的下标,并返回下标 
{
	int i;
	
	for(i=0;i<G->vexnum;i++)
	{
		if(v==G->Vertex[i])
		{
			return i; 
		} 
	 } 
	 
	 printf("No Such Vertex!\n");
	 return -1;
}

void CreateUDN(MGraph *G)//构建无向网(Undirected Network)
{
	int i,j;
	//1.输入顶点数和边数
	printf("输入顶点个数和边数:\n");
	printf("顶点数 n="); 
	scanf("%d",&G->vexnum);
	printf("边  数 e="); 
	scanf("%d",&G->arcnum);
	printf("\n"); 
	
	printf("\n");
	
	//2.输入顶点元素 
	printf("输入顶点元素(无需空格隔开):");
	scanf("%s",G->Vertex);
	printf("\n");
	//3.矩阵初始化
	for(i=0;i<G->vexnum;i++) 
	 for(j=0;j<G->vexnum;j++)
	    {
	    	G->AdjMatrix[i][j]=MaxInt;
		}
	
	 //4.构建邻接矩阵
	 int n,m;
	 VertexType v1,v2;
	 int w;//v1->v2的权值 
	 
	 printf("请输入边的信息和权值(例:AB,15):\n");
	 for(i=0;i<G->arcnum;i++)
	 {
	 	printf("输入第%d条边信息及权值:",i+1);
	 	scanf(" %c%c,%d",&v1,&v2,&w);
	 	n=LocateVex(G,v1); //获取v1所对应的在Vertex数组中的坐标 
	 	m=LocateVex(G,v2); //获取v2所对应的在Vertex数组中的坐标
	 	
	 	if(n==-1||m==-1)
		 {
		 	printf("NO This Vertex!\n");
		 	return;
		  } 
	
	   G->AdjMatrix[n][m]=w;
	   G->AdjMatrix[m][n]=w;//无向网仅此处不同 
     } 
}

void print(MGraph G)
{
	int i,j;
	printf("\n-------------------------------");
	printf("\n 邻接矩阵:\n\n"); 	
	
		printf("\t ");
		for(i=0;i<G.vexnum;i++)
		printf("\t%c",G.Vertex[i]);
		printf("\n");
		 
		for(i=0;i<G.vexnum;i++)
	   {
	   	  printf("\t%c",G.Vertex[i]);
	   	  
		  for(j=0;j<G.vexnum;j++)
	    {
	    	
	    	if(G.AdjMatrix[i][j]==MaxInt)
	 	    printf("\t∞");
	 	    else printf("\t%d",G.AdjMatrix[i][j]);
	    }
	      printf("\n");
	   }

	 
}

int minimal(MGraph *G,ShortEdge *shortedge)
{
	int i,j;
	int min,loc;
	
	min=MaxInt;
	for(i=1;i<G->vexnum;i++)
	{
		if(min>shortedge[i].lowcost&&shortedge[i].lowcost!=0)
		{
			min=shortedge[i].lowcost;
			loc=i;
		}
	}
	return loc;
}
 
void MiniSpanTree_Prim(MGraph *G,VertexType start)
{ 
	int i,j,k;
	ShortEdge shortedge[VertexMax];
	
	//1.处理起始点start 
	k=LocateVex(G,start);
	for(i=0;i<G->vexnum;i++)
	{
		shortedge[i].adjvex=start;
		shortedge[i].lowcost=G->AdjMatrix[k][i];
	}
	shortedge[k].lowcost=0;//lowcost为0表示该顶点属于U集合 
	
	//2.处理后续结点 
	for(i=0;i<G->vexnum-1;i++)//对集合U,去找最短路径的顶点 
	{
		k=minimal(G,shortedge);//找最短路径的顶点 
	
	    printf("%c->%c,%d\n",shortedge[k].adjvex,G->Vertex[k],shortedge[k].lowcost);//输出找到的最短路径顶,及路径权值 
	    shortedge[k].lowcost=0;//将找到的最短路径顶点加入集合U中 
	    
	    for(j=0;j<G->vexnum;j++)//U中加入了新节点,可能出现新的最短路径,故更新shortedge数组 
	    {
	    	if(G->AdjMatrix[k][j]<shortedge[j].lowcost)//有更短路径出现时,将其替换进shortedge数组 
	    	{
	    		shortedge[j].lowcost=G->AdjMatrix[k][j];
	    		shortedge[j].adjvex=G->Vertex[k];
			}
		}
	    
	 } 
}


int main() 
{
	VertexType start;
	 
	MGraph G;
	CreateUDN(&G);
	print(G); 
	
	printf("请输入起始点:");
	scanf(" %c",&start);//%c前面有空格 
	MiniSpanTree_Prim(&G,start);
	 
	return 0;
}

执行结果

在这里插入图片描述

普里姆算法时间复杂度分析:

  • 在MiniSpanTree_Prim函数中,主要是后部分的二重循环故时间复杂度为O(n2),其中n为顶点数,故Prim的时间复杂度与顶点数有关。

  • 88
    点赞
  • 453
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 9
    评论
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Attract1206

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值