图 求最小生成树 Prim Kruskral算法
-
Prim 算法(和Dijkstra算法极像)
- 寻找一个到最小生成树集合距离最近的点
- 把这个点加入到最小生树集合中
- 再用刚刚加入的点去更新集合到集合外的点的最短距离 重复步骤2 (共迭代n(顶点个数)次)
//时间复杂度 O(V^2) 适合稠密图 用邻接矩阵 #include<iostream> using namespace std; const int INF=0x3f3f3f; const int MAX_V = 600; int d[MAX_V],G[MAX_V][MAX_V]; bool used[MAX_V]; int Prim(); int n,m; int main() { fill(G[0],G[0]+MAX_V*MAX_V,INF);//邻接矩阵一定要初始化 scanf("%d%d",&n,&m); for(int i=0;i<m;i++) { int a,b,w; scanf("%d%d%d",&a,&b,&w); G[a][b]=G[b][a]=min(G[a][b],w); //有重边选择小的 } int t=Prim(); if(t==INF) printf("impossible"); else printf("%d",t); } int Prim() { fill(used,used+MAX_V,false); //初始化 fill(d,d+MAX_V,INF); int res=0; //记录最小生成树的总权值 for(int i=0;i<n;i++) { int v=-1; for(int j=1;j<=n;j++){ if(!used[j]&&(v==-1||d[v]>d[j])) v=j; } if(i&&d[v]==INF) return INF; //不是第一次迭代距离集合最近的距离为INF 说明没有最小生成树 图不连通 if(i) res+=d[v]; //第一次迭代只是加入一个点 没有加入一条边所以第一次迭代执行 used[v]=true; for(int j=1;j<=n;j++) { //更新集合到集合外的点的最小距离 d[j] = min(d[j],G[v][j]); } } return res; }
-
Kruskal算法
- 先用sort把所有边按权重从小到大排序
- 再遍历每一条边,如果一条边的两点不连通(用并查集结构查看每条边的两个顶点是否在一个集合中,不在)就把这条边加入到最小生成树中(同时把两个顶点合并在一个集合中)
//时间复杂度O(E log E) #include<iostream> #include<algorithm> using namespace std; const int N=2e6; int p[N]; //利用并查集 判断边的两点是否在一个集合 不再就加入一个集合(最小生成树) struct edge{ //存取所有边 int a,b,w; }; edge es[N]; bool cmp(edge a,edge b); int find(int x); bool cmp(edge a,edge b){ return a.w<b.w; } int find(int x){ if(p[x]==x) return x; return p[x]=find(p[x]); } int n,m; int main() { scanf("%d%d",&n,&m); for(int i=0;i<m;i++) { int a,b,w; scanf("%d%d%d",&a,&b,&w); es[i]={a,b,w}; } sort(es,es+m,cmp); //把边按权值从小到大排序 for(int i=0;i<=n;i++){ //初始化并查集 p[i]=i; } int res=0,cnt=0; //cnt为边数 res为最小生成树总权重 for(int i=0;i<m;i++){ //遍历每一条边 int a=find(es[i].a); int b=find(es[i].b); if(a!=b){ //边的两点不在一个集合 p[a]=b; res+=es[i].w; cnt++; } } if(cnt<n-1) printf("impossible"); //边数不够n-1条说明没有最小生成树 else printf("%d",res); }