Prim
- 本质:BFS+贪心,对点进行操作。与最短路Dijkstra算法是"孪生兄弟"。
- 存储结构:链式前向星
- 适用对象:可为负权图,可求最大生成树
- 核心思想:最近的邻接点一定在最小生成树(MST)上,对点的最近邻接点贪心。
- 算法流程:设源点 s s s(任意点),MST已选点集 { S } \set{S} {S}(初始 { S } = ∅ \set{S}=\varnothing {S}=∅);各点当前到已选点集 { S } \set{S} {S}的最小边权 { d i s } \set{dis} {dis}(初始 { d i s } \set{dis} {dis}:非 s : + ∞ s:+\infty s:+∞,表示该点目前不可达; s = 0 s=0 s=0);当前MST方案数组 t r e e tree tree(存储其上个源点,初始化为 − 1 -1 −1表示无方案),待算法结束后极为最终方案;当前轮起点 U U U, U U U邻接点集 { V } \set{V} {V},邻接点权值集 { W } \set{W} {W}。
循环执行下列步骤,直到点集 { S } \set{S} {S}包含所有顶点,或剩余点无法进入 { S } \set{S} {S}:- 考查最近顶点 (贪心):从全体点集中,找出先前不在 { S } \set{S} {S}中(未被考查)且距点集 { S } \set{S} {S}最近 ( d i s dis dis最小)的顶点,作为当前轮起点 U U U(首次即为源点 s s s),将其进入 { S } \set{S} {S}(考查顶点 U U U)
- 扩散邻接点 (BFS扩散):扩散 U U U的邻接点且先前未被考查的顶点 V V V,检查是否能通过 U U U而使其距点集 { S } \set{S} {S}更短( d i s dis dis更小)。若更短,则更新该点 d i s dis dis,并更新路径: d i s [ V ] = m i n ( d i s [ V ] , W ) dis[V]=min(dis[V],W) dis[V]=min(dis[V],W), t r e e [ V ] = U tree[V]=U tree[V]=U
- Prim算法与最短路算法Dijkstra是"孪生兄弟"。与Dijkstra不同之处在于更新操作中,Prim为更新为所有点到 { S } \set{S} {S}整体 的距离(即 d i s [ V ] = m i n ( d i s [ V ] , W ) dis[V]=min(dis[V],W) dis[V]=min(dis[V],W));而Dijkstra为更新所有点到 { S } \set{S} {S}中特定一点 (源点 s s s)的距离(即 d i s [ V ] = m i n ( d i s [ V ] , d i s [ U ] + W ) dis[V]=min(dis[V],dis[U]+W) dis[V]=min(dis[V],dis[U]+W))。
- 复杂度:朴素版 O ( V 2 ) O(V^2) O(V2),二叉堆优化版 O ( V log 2 E ) O(V\log_2E) O(Vlog2E)
cpp
using ll=long long;
using pll=pair<ll,ll>;
const ll INF=__LONG_LONG_MAX__;
ll n,m,s;//点数,边数,源点
struct vertex{
int e=-1;
}v[n];
struct edge{
int u,v,w,n=-1;
}e[m];
注:若无特殊说明,本文默认顶点均为从0起编号。
朴素版
- 选目前 d i s dis dis最小点 U U U的3个条件:该点未被考查,该点 d i s dis dis非无穷,该点 d i s dis dis更小
- 扩散至 U U U邻接点 V V V的3个条件:该邻接点未被考查,边权值 W W W非无穷, V V V通过 U U U而使 d i s dis dis更短
cpp
int prim() {
vector<bool>S(n,0);
vector<ll>dis(n,INF),tree(n,-1);
ll s=0,ans=0;
dis[s]=0;
for (int j=0;j<n;j++){
ll U=-1,minn=INF;
for(ll i=0;i<n;i++)
if(!S[i]&&dis[i]!=INF&&dis[i]<minn) U=i,minn=dis[i];
if(U==-1) return -1;//剩余点均不可达,不存在MST
S[U]=1;
ans+=dis[U];
for (ll i=v[U].e;~i;i=e[i].n){//~i:i!=-1
ll V=e[i].v,W=e[i].w;
if (!S[V]&&W!=INF&&dis[V]>W)//与Dijkstra唯一不同点
dis[V]=min(dis[V],W),tree[V]=U;
}
}
return ans;
}
若求最大生成树: d i s dis dis应初始化为 − ∞ -\infty −∞;找 d i s dis dis最大顶点;更新条件改为 d i s [ V ] < W dis[V]<W dis[V]<W。
二叉堆优化版
优化点:另设顶点的小根堆 Q Q Q,其结构为 { d i s , i n d e x } \set{dis,index} {dis,index},可动态维护当前 d i s dis dis最小点 U U U。
另设计数器 c n t cnt cnt,若 c n t < V cnt<V cnt<V,则证明是非连通图,无法构成MST。
-
选目前 d i s dis dis最小点 U U U的3个条件:该点未被考查,该点 d i s dis dis非无穷,该点 d i s dis dis更小。
-
扩散至 U U U邻接点 V V V的3个条件:该邻接点未被考查,边权值 W W W非无穷, V V V通过 U U U而使 d i s dis dis更短
cpp
int prim() {
vector<ll>dis(n,INF),tree(n,-1);
vector<bool>S(n,0);
priority_queue<pll,vector<pll>,greater<>>pq;
int ans=0,cnt=0,s=0;
dis[s]=0,pq.push({dis[s],s});//dis,index
while(pq.size()){
ll U=pq.top().second;pq.pop();
if(S[U]) continue;
S[U]=1,ans+=dis[U],cnt++;
for(ll i=v[U].e;~i;i=e[i].n){//~i:i!=-1
ll V=e[i].v,W=e[i].w;
if(!S[V]&&W!=INF&&dis[V]>W){//与Dijkstra唯一不同点
dis[V]=W,tree[V]=U;
pq.push({dis[V],V});
}
}
}
if(cnt<n) return -1;
return ans;
}
若求最大生成树: d i s dis dis应初始化为 − ∞ -\infty −∞;维护大根堆;更新条件改为 d i s [ V ] < W dis[V]<W dis[V]<W。