问题描述:在无向图中找出一个最小生成树,前提是图是连通的。
一、Prim算法
1、思路:贪心,每次更新每个节点的距离,使这个节点的距离最短,类似于dijkstra算法
2、使用条件:无向连通图
3、算法实现:
(1)找到一个起始点,(终点有没有都无所谓),将起点的距离设为0(表示起点到起点的距离是0),
标记每个节点的数组vis初始化为0,pre数组记录最小生成树的边。
(2)更新每个点的最短距离,每次一个节点,与dijkstra不同的是,在更新边的时候,dij是更新起点到这一点的
最短距离,而Prim只更新这一点和它的邻接点之间的距离。
(3)重复(2)操作,直到所有点都被标记了。
4、代码:
#include<iostream> #include<cstdio> using namespace std; const int maxn = 1200; const int INF = 0x3fff; int edge[maxn][maxn],pre[maxn],vis[maxn],dis[maxn],m,n,st,ed; void Init() { for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) edge[i][j]=(i==j?0:INF); for(int i=1;i<=n;i++) pre[i]=0,vis[i]=0,dis[i]=INF; dis[st]=0; } void Prim() { while(1) { int mi=INF,pos=-1; for(int i=1;i<=n;i++) if(vis[i]==0&&dis[i]<mi) { mi=dis[i];pos=i; } if(pos==-1) return ; vis[pos]=1; for(int i=1;i<=n;i++) if(vis[i]==0&&edge[pos][i]<dis[i]&&edge[pos][i]!=INF) { dis[i]=edge[pos][i];pre[i]=pos; } } } int main(void) { int x,y,z; cin>>n>>m; st=1;ed=n; Init(); for(int i=1;i<=m;i++) { cin>>x>>y>>z; edge[x][y]=edge[y][x]=z; } Prim(); int sum=0; for(int i=2;i<=n;i++) cout<<"("<<pre[i]<<","<<i<<")"<<endl,sum+=dis[i]; cout<<sum<<endl; return 0; } /* 6 7 1 2 1 2 3 7 3 4 6 4 5 5 5 6 2 1 6 3 2 5 4 */
5、算法复杂度:O(|V|^2)
二、Kruskal算法
1、思路:并查集&优先队列
2、适用条件:无相连通图
3、算法实现:
(1)建立一个边的集合,建立n个点的数组,将它们初始化为1-n,标记数组初始化为0
(2)输入m条边的信息,按照边的权值大小排序,然后从权值小的边开始合并点集,直到所有点都是一个集合为止。
(3)遍历vis数组,输出合并的边和最终的最小生成树的大小。
4、代码:
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int maxn = 1200; const int INF = 0x3ffff; struct Edge{ int u,v,w; }edge[maxn]; int n,m,a[maxn],vis[maxn]; bool cmp(Edge A,Edge B) { return A.w<B.w; } void Init() { for(int i=1;i<=n;i++) a[i]=i; memset(edge,0,sizeof(edge)); memset(vis,0,sizeof(vis)); } int f(int x) { if(x==a[x]) return a[x]; else return f(a[x]); } void Kruskal() { int sum=0; for(int i=1;i<=m;i++) { int t1=f(edge[i].u),t2=f(edge[i].v); if(a[t2]!=t1) { a[t2]=t1; vis[i]=1; sum+=edge[i].w; } } for(int i=1;i<=m;i++) if(vis[i]==1) printf("(%d,%d) ",edge[i].u,edge[i].v); printf("%d ",sum); } int main(void) { int i,x,y,z; Edge tmp; cin>>n>>m; Init(); for(i=1;i<=m;i++) { cin>>x>>y>>z; tmp.u=x;tmp.v=y;tmp.w=z; edge[i]=tmp; } sort(edge+1,edge+m+1,cmp); Kruskal(); return 0; }
5、复杂度:O(|E|log|V|)。
总结:两种最小生成树的算法都是找最小的边,但是Prim算法是由点找边,Kruskal算法是直接找边,所以Prim算法可以视为“点插法”,
Kruskal算法可以视为“边插法”。