克鲁斯卡尔算法(Kruskal's Algorithm)
#include<iostream> #include<cmath> #include<cstdio> #include<cstdlib> #include<cstring> #include<algorithm> #define FOR(i,x,y) for(int i=(x);i<=(y);i++) #define DOR(i,x,y) for(int i=(x);i>=(y);i--) #define N 5003 #define M 200003 using namespace std; int n,m; int fa[N]; struct edge { int u,v,w; bool operator<(const edge &_)const { return w<_.w; } void Read() { scanf("%d%d%d",&u,&v,&w); return; } }E[M]; int getfa(int k) { return k==fa[k]?k:fa[k]=getfa(fa[k]); } void Kruskal() { int cnt=0,ans=0; FOR(i,1,m) { int u=getfa(E[i].u),v=getfa(E[i].v); if(u==v)continue; //合并操作一定是对祖先的 fa[u]=v; ans+=E[i].w; //累计答案 cnt++; } if(cnt==n-1)printf("%d\n",ans); else printf("orz\n"); } int main() { scanf("%d%d",&n,&m); FOR(i,1,n)fa[i]=i; //并查集一定要先把每个点的祖先设为本身 FOR(i,1,m)E[i].Read(); sort(E+1,E+1+m); Kruskal(); return 0; }
程序时间复杂度为O(mlogm),就是为边排序的复杂度,而Kruskal函数是O(m)的。
此算法非常容易理解,从短到长不断尝试连边,用并查集判断是否会成环,其中必定连了n-1次
普里姆算法(Prim's Algorithm)
#include<iostream> #include<cmath> #include<cstdio> #include<cstdlib> #include<cstring> #include<algorithm> #define FOR(i,x,y) for(int i=(x);i<=(y);i++) #define DOR(i,x,y) for(int i=(x);i>=(y);i--) #define N 5003 #define M 200003 #define INF 1e9 using namespace std; int n,m; int E[N][N]; int dis[N]; void prim() { int ans=0; FOR(i,1,n)dis[i]=E[1][i]; //设置节点1为初始节点 dis[0]=INF; FOR(i,2,n) { int t=0; FOR(j,1,n) if(dis[j]!=0 && dis[j]<dis[t]) //dis[j]为0表示j节点已经连入 t=j; ans+=dis[t]; //累计答案 if(t==0) //如果无法连接,说明不是连通图 { printf("orz\n"); return; } FOR(j,1,n) if(dis[j]>E[t][j]) //松弛,与dj略有区别 dis[j]=E[t][j]; dis[t]=0; } printf("%d\n",ans); } int main() { scanf("%d%d",&n,&m); FOR(i,1,n)FOR(j,1,n)E[i][j]=INF; FOR(i,1,n)E[i][i]=0; FOR(i,1,m) { int u,v,w; scanf("%d%d%d",&u,&v,&w); E[u][v]=E[v][u]=min(E[u][v],w); } prim(); return 0; }
这个算法和dj还是很相似的,时间复杂度有O(n²)。
每次连入能连入的最短边,然后将图松弛,其中也是连了n-1次。