004.最小生成树算法
最小生成树(MST)
Prim算法
\(e[u]\)存\(u\)点的所有邻边的终点和边权
\(d[u]\)存\(u\)点与圈外邻点的最小距离,\(vis[u]\)标记\(u\)点是否出圈
基础版本
算法流程类似于Dijkstra算法,不断选距离最小的点出圈,直到圈内为空
- 初始化,所有点都在圈(集合)内,即\(vis=0\),\(d[s] = 0\),其他点\(d[i]=\inf(i \ne s)\)
- 每次从圈内选取一个距离最小的点\(u\),打标记出圈
- 对\(u\)的所有邻点的距离执行更新操作
- 重复2,3步操作,直到圈内为空
struct edge{int v,w;};
vector<edge> e[N];
int d[N],vis[N];
bool prim(int s){
for(int i = 0 ; i <= n ; i ++) d[i] = inf;
d[s] = 0;
for(int i = 1 ; i <= n ; i ++){
int u = 0;
for(int j = 1 ; j <= n ; j ++)
if(!vis[j] && d[j] < d[u]) u = j;
vis[u] = 1; //标记u已经出圈
ans += d[u];
if(d[u] != inf) cnt ++;
for(auto ed : e[u]){
int v = ed.v,w = ed.w;
if(d[v] > w) d[v] = w;
}
}
return cnt == n;
}
时间复杂度\(O(n^2)\)
堆优化版
创建一个pair类型的大根堆\(q\{-d[u],u\}\),把距离其负值,距离最小的元素最大,一定在堆顶
- 初始化\(\{0,s\}\)入队,\(d[s] = 0\),其他点\(d[i] = \inf(i\ne s)\)
- 从队头弹出距离最小的点\(u\),若\(u\)扩展过则跳过,否则打标记
- 对\(u\)的所有邻点的距离执行更新操作,把\(\{-d[v],v\}\)压入队
- 重复2,3步操作,直到队列为空
int n,m,s,a,b,c,ans,cnt;
struct edge{int v,w;};
vector<edge> e[N];
int d[N],vis[N];
priority_queue<pair<int,int>> q;
bool prim(int s){
for(int i = 0 ; i <= n ; i ++) d[i] = inf;
d[s] = 0; q.push({0,s});
while(q.size()){
int u = q.top().second; q.pop();
if(vis[u]) continue;
vis[u] = 1; //标记u出队
ans += d[u]; cnt ++;
for(auto ed : e[u]){
int v = ed.v,w = ed.w;
if(d[v] > w){
d[v] = w;
q.push({-d[v],v}); //大根堆
}
}
}
return cnt == n;
}
时间复杂度\(O(m\log m)\)
Kruskal算法
利用并查集求最小生成树
\(e[i]\)存第\(i\)条边的起点、终点与边权
\(fa[x]\)存\(x\)点的父节点
算法流程
- 初始化并查集,把\(n\)个点放在\(n\)个独立的集合
- 将所有的边按边权从小到大排序(贪心思想)
- 按顺序枚举每一条边,如果这条边链接的两个点不在同一集合,就把这条边加入最小生成树,并且合并这两个集合;如果这条边链接的两个点在同一个集合,就跳过
- 重复执行3,直到选取了\(n-1\)条边为止
struct edge{
int u,v,w;
bool operator < (const edge& t) const
{
return w < t.w;
}
}e[N];
int fa[N],ans,cnt;
int find(int x){
if(fa[x] == x) return x;
return fa[x] = find(fa[x]);
}
bool Kruskal(){
sort(e,e+m);
for(int i = 1 ; i <= n ; i ++) fa[i] = i;
for(int i = 0 ; i < m ; i ++){
int x = find(e[i].u);
int y = find(e[i].v);
if(x != y){
fa[x] = y;
ans += e[i].w;
cnt ++;
}
}
return cnt == n-1;
}

浙公网安备 33010602011771号