最小生成樹的常用演算法模板
阿新 • • 發佈:2020-07-26
關於最小生成樹的話,其實很早之前就接觸了,當時也寫了一篇關於最小生成樹的文章,但一直沒有好好刷題。
最小生成樹的演算法沒有其他演算法那麼複雜,演算法思想比較簡單,程式碼也比較容易。
常見的最小生成樹演算法由Kruskal
演算法和Prim
演算法。
1.Kruskal演算法 -- 時間複雜度\(O(m * log m)\)
演算法思想:
- 建立一個並查集,每個點構成一個集合;
- 將邊進行從小到大進行排序,依次掃描邊edge(u,v,w);
- 如果u和v 屬於同一個集合,那麼跳過這輪迴圈;
- 如果不屬於同一個集合,則把u、v合併為同一個集合;
- 當集合中的頂點數為n或者掃遍所有邊edge,則構成最小生成樹。
#include<iostream> #include<algorithm> #include<cstdio> using namespace std; const int manx=1e5+5; //對應頂點數目 const int mamx=1e5+5; //對應邊的數目 int n,m,u,v,total=1; struct edge{ int start,to; long long val; }bian[mamx]; int a[manx]; long long ans; int find(int x) //並查集 { if(a[x]==x) return x; else return a[x]=find(a[x]); } bool cmp(edge x,edge y) { return x.val<y.val; } inline void kruskal() { for(int i=1;i<=m;i++) { u=find(bian[i].start); v=find(bian[i].to); if(u==v) continue; //如果兩個點存在於同一個集合則跳過迴圈 ans+=bian[i].val; a[u]=v; total++; if(total==n-1) break; } } int main() { scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) //並查集的初始化,祖先為自己 a[i]=i; for(int i=1;i<=m;i++) scanf("%d%d%d",&bian[i].start,&bian[i].to,&bian[i].val); sort(bian+1,bian+1+m,cmp); kruskal(); cout<<ans; return 0; }
2.Prim演算法
Prim演算法跟最短路中的Dijkstra 演算法思想相近,有興趣的可以瞭解一下:
https://www.cnblogs.com/RioTian/p/12597634.html
演算法思想:
1.把1作為起點加入最小生成樹集合S;
2.在未訪問過的集合T中找出一點距離集合S最近的點,並在T中將其剔除,移入S中;
3.重複2步驟,直到所有點加入S。
樸素演算法 時間複雜度 \(O(n^2)\)
const int inf=2147483647; int a[manx][manx], d[manx], n, m, ans; bool vis[manx]; void prim(){ for(int i=1;i<=n;i++) d[i]=inf,vis[i]=0,a[i][i]=0; //初始化各陣列 d[1]=0; //1作為起點 for(int i=1;i<n;i++) //重複n-1次 { int x=0; for(int j=1;j<=n;j++) if( !vis[j] && (x==0||d[j]<d[x]) ) //尋找未訪問過且離集合S最近的點 x=j; vis[x]=1; for(int y=1;y<=n;y++) //第一輪d[y]中由起點可到達的點得到更新 d[y]=min( d[y ], a[x][y]) //這裡與Dij不同,因為Dij求的是兩點間的距離,而這裡是點到集合距離 } }
Prim演算法使用堆優化可達到與Kruscal一樣的複雜度 \(O(m * log m)\)
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
const int manx=5e3+5;
const int mamx=1e5+5;
int k,n,m,cnt,sum,a,b,c;
int head[manx],dis[manx],vis[manx];
struct node{
int v,w,next;
}e[mamx];
typedef pair<int,int> p;
priority_queue<p,vector<p>,greater<p> >q; //堆優化
void add(int u, int v, int w) //鏈式前向星建圖
{
e[++k].v=v;
e[k].w=w;
e[k].next=head[u];
head[u]=k;
}
int main()
{
memset(dis,127,sizeof(dis));
memset(head,-1,sizeof(head));
scanf("%d%d",&n,&m);
for(R i=1;i<=m;i++)
{
scanf("%d%d%d",&a,&b,&c);
add(a,b,c);
//無向圖 add(b,a,c);
}
dis[1]=0; //一般將1作為最小生成樹擴充套件的起點
q.push(make_pair(0,1));
while(!q.empty() && cnt<n) //仔細觀察可以發現堆優化的Prim也和堆優化的Dij的程式碼實現有很多相似之處,這是因為兩者都是基於貪心的演算法
{
int d=q.top().first,u=q.top().second;
q.pop();
if(vis[u]) continue;
cnt++; //計算S集合中頂點的個數
sum+=d; //計算最小權值和
vis[u]=1;
for(R i=head[u];i;i=e[i].next)
if(e[i].w<dis[e[i].v])
dis[e[i].v]=e[i].w,q.push(make_pair(dis[e[i].v],e[i].v));
}
printf("%d",sum);
}
儘管堆優化,但不如直接使用Kruskal演算法更加方便,因此,稀疏圖用Kruskal,稠密圖用Prim 。