Prim算法
涉及到的几个基础知识
- 生成树: 一个连通图的生成树是它的极小连通子图,在n个顶点的情形下,有n-1条边。生成树是对连通图而言的,是连通图的极小连通子图,包含途中所有顶点,有且仅有n-1条边。非连通图的生成树则组成一个声称森林;若图中有n个顶点,m个连通分量,则生成森林中有n-m条边。
- 图的遍历: 和树的遍历相似,若从图中某顶点出发,访问遍途中每个顶点,且每个顶点仅访问一次,此过程称为图的遍历。图的遍历算法是求解图的连通性问题、拓扑排序和求关键路径等算法的基础。图的常用遍历顺序有两种:深度优先搜索(DFS)和广度优先搜索(BFS),对每种搜索顺序,访问各顶点的顺序也不是唯一的。
- 在一个无向连通图G中,其所有顶点和遍历该图经过的所有边所构成的子图G',称作图G的生成树。一个图可以有多个生成树,从不同的顶点除法,采用不同的遍历顺序,遍历时所经过的边也就不同。
- 最小生成树:在图论中,常常将树定义为一个无回路连通图。对于一个带权的无向连通图,其每个生成树所有边上的权值之和可能不同,我们把所有边上权值之和最小的生成树成为图的最小生成树(MST)。
- MST性质:MST性质:假设G=(V,E)是一个连通网,U是顶点V的一个非空子集。若(u,v)是一条具有最小权值的边,其中u∈U,v∈V-U,则必存在一棵包含边(u,v)的最小生成树。
Prim算法
基本思想:假设G=(V,E)是连通的,TE是G上最小生成树中边的集合。算法从U={u0}(u0∈V)、TE={}开始。重复执行下列操作:
在所有u∈U,v∈V-U的边(u,v)∈E中找一条权值最小的边(u0,v0)并入集合TE中,同时v0并入U,直到V=U为止。
此时,TE中必有n-1条边,T=(V,TE)为G的最小生成树。Prim算法的核心:始终保持TE中的边集构成一棵生成树。
Prim算法举例:
采用的是顶点数为6的无向连通图:
设集合V={A,B,C,D,E,F},即所有顶点的集合。
集合U为最小生成树的结点。
按照Prim算法:
- 将A加入U,此时,U={ A },V-U={ B,C,D,E};
- 与A邻接的顶点有B,C,D。(A,B)、(A,C)、(A,D),权值分别为6、1、5,因而选定(A,C)为最小生成树的一条边;
- 将上一步选定的在V-U中的顶点C加入U,此时,U={A,C}, V-U={ B,D,E,F};
- V-U中与U中顶点组成的边有(A,B)、(A,D)、(C,B)、(C,D)、(C,E)、(C,F),权值分别为6、5、5、5、6、4,因而选定(C,F)为最小生成树的一条边;
- 将F加入U中,此时U={ A,C,F} , V-U={ B, D,E};
- V-U中与U中顶点组成的边有(A,B)、(A,D)、(C,B)、(C,D)、(C,E)、(F,D)、(F,E),权值分别为6、5、5、5、6、2、6,选定(F,D)为最小生成树的一条边;
- 将D加入U中,此时,U={ A,C,F,D}, V-U={ B,E};
- V-U中与U中顶点组成的边有(A,B)、(C,B)、(C,E)、(F,E),权值分别为6、5、6、6,选定(C,B)为最小生成树的一条边。
- 将B加入U中,此时U= {A,C,F ,D,B }, V -U={E };
- V-U中与U中顶点组成的边有(B,E)、(C,E)、(F,E),权值分别为3、6、6,选定(B,E)为最小生成树的一条边。
- 将E加入U中,此时U={A ,C,F,D,B,E},完成MST的生成。
其生成过程图示如下:
下面给出一道题题意:输入n个城镇相互之间的距离,输出将n个城镇连通费用最小的方案中修的最长的路的长度
这个也是最小生成树的题,只不过要求的不是最小价值,而是最小生成树中的最大权值,只需要加个判断
比较最小生成树每条边的大小就行
代码
#include<cstdio> #include<algorithm> #include<cstring> using namespace std; #define N 510 #define inf 0x3f3f3f3f//无穷大 int map[N][N]; //两点间距离 int dis[N]; // 到N点的最短路 int vis[N]; //标记 int n; void prim(int u) { int i,j,k,start,min,max; memset(vis,0,sizeof(vis)); //初始化 for(i=1;i<=n;i++) if(i!=u) dis[i]=map[1][i]; //1到i的距离 vis[1]=true; //加入点1,设为起点 k=0; min=0;max=0; for(i=1;i<n;i++) //n-1条边 { min=inf; //不能直接连接的设为无穷大 for(j=1;j<=n;j++) if(dis[j]<min&&!vis[j]) { min=dis[j]; k=j; //加入点 } if(min>max) max=min; vis[k]=true; for(j=1;j<=n;j++) if(dis[j]>map[k][j]&&!vis[j]) { dis[j]=map[k][j]; } } printf("%d\n",max); } int main() { int T; scanf("%d",&T); while(T--) { scanf("%d",&n); memset(map,inf,sizeof(map));//初始化 for(int i=1;i<=n;i++) //两端点 for(int j=1;j<=n;j++) { scanf("%d",&map[i][j]); } prim(1); } return 0; }
#include<stdio.h>/*这是一个模板*/ #include<stdlib.h> #define MAX 10000 int edge_num; //总边数 int vertex_num; //顶点总数 int sum; //最小生成树的边权之和 int s; //指定的最小生成树的起点 int matrix[100][100]; //定义邻接矩阵 bool visited[100]; //定义标记数组 int l_cost[100]; //定义边的权值 int path[100]; //记录最小生成树的路径 void Prim(int s){ int min; //权值最小 int min_index; //权值最小的下标 sum=0; //初始化权和为0 visited[s]=true; //标记顶点 for(int i=0;i<vertex_num;i++){ l_cost[i]=matrix[s][i]; //遍历数据,初始化起点s的各邻接边权值 path[i]=s; //初始化相应从起点到i点的路径 } for(int i=1;i<vertex_num;i++){ min=MAX; for(int j=0;j<vertex_num;j++){ //找出权值最小 if(visited[j]==false && l_cost[j]<min){ min=l_cost[j]; //min=最小边权值 min_index=j; //记录相应的边终点j } } visited[min_index]=true; //标记找出的结点 sum+=l_cost[min_index]; //更新生成树的权和,加上新找到的结点的边权 for(int j=0;j<vertex_num;j++){ //利用找到的最小下标更新l_cost数组 if(visited[j]==false && matrix[min_index][j]<l_cost[j]){ l_cost[j]=matrix[min_index][j]; path[j]=min_index; } } } } int main(){ int u,v,w; printf("请输入图的顶点数目(不大于100):"); scanf("%d",&vertex_num); printf("请输入边数:"); scanf("%d",&edge_num); for(int i=0;i<vertex_num;i++){ for(int j=0;j<vertex_num;j++){ matrix[i][j]=MAX; //初始化matrix数组(邻接矩阵存储) } } printf("请输入边的信息(起点,终点,边长):\n"); for(int i=0;i<edge_num;i++){ //存入各边权信息进邻接矩阵 scanf("%d%d%d",&u,&v,&w); matrix[u][v]=w; matrix[v][u]=w; } printf("请输入起点(<%d,&vertex_num):"); scanf("%d",&s); Prim(s); printf("最小生成树的边权和为:%d\n",sum); printf("最小生成树的路径为:\n"); for(int i=0;i<vertex_num;i++){ if(i!=s){ printf("%d---%d\n",i,path[i]); } } return 0; }