最小生成树
定义:如果带权图G(V,E)有子图G’(V’,E’)是一棵树且满足V=V’,则称G‘是G的一颗生成树,其中边权之和最小的称为最小生成树。
(带权图: 如果给图 G 的每一条边都赋一个值, 则图 G 称为带 权图, 无权图可以视为边权均为 1 的带权图)
(树: 如果图 G 中没有环, 则称 G 是一棵树)
(n 个结点的树必定有 n-1 条边)
Kruskal算法
时间复杂度: O(ElogE)
算法
1、将所有边按照边权排序, 边集 S = ∅;
2、从小到大依次将边加入 S 中, 使得 S 中不出现环;
3 、|S| = n - 1 或所有边遍历完毕后, 算法停止.
使用并查集来检查 S 中是否有环:
处于同一连通分量的结点只用一个代表元来表示;
代表元不同的点属于不同的连通分量, 否则属于同一连通分量;
在 S 中已经属于同一连通分量的点之间不加边, 即可保证无环.
模板
struct edge{
int u,v,w;
}e[maxm];
void addedge(int u,int v,int w){
e[tot].u=u;e[tot].v=v;e[tot++].w=w;//e[tot].w=w;tot++;
}
bool cmp(edge a,edge b){
return a.w<b.w;
}
int find(int x){//并查集
if(fa[x]==-1) return x;
else return fa[x]==find(fa[x]);
}
int Kruskal(int n){
memset(fa,-1,sizeof(fa));
sort(e,e+tot,cmp);
int cnt(0),ans(0);
for(int i=0;i<tot;i++){
int u=edge[i].u,v=edge[i].v,w=edge[i].w;
int t1=find(u);t2=find(v);
if(t1!=t2){
ans+=w;fa[t1]=t2;cnt++;
}
if(cnt==n-1) break; //确定树
}
if(cnt<n-1) return -1;
else return ans;//最小权
}
示例(题目来源)
N个点M条边的无向连通图,每条边有一个权值,求该图的最小生成树。
Input
第1行:2个数N,M中间用空格分隔,N为点的数量,M为边的数量。(2 <= N <= 1000, 1 <= M <= 50000)
第2 - M + 1行:每行3个数S E W,分别表示M条边的2个顶点及权值。(1 <= S, E <= N,1 <= W <= 10000)
Output
输出最小生成树的所有边的权值之和。
Input示例
9 14
1 2 4
2 3 8
3 4 7
4 5 9
5 6 10
6 7 2
7 8 1
8 9 7
2 8 11
3 9 2
7 9 6
3 6 4
4 6 14
1 8 8
Output示例
37
代码
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#define maxm 100
using namespace std;
int tot(0);
int fa[maxm];
struct edge{
int u,v,w;
}e[maxm];
void addedge(int u,int v,int w){
e[tot].u=u;e[tot].v=v;e[tot++].w=w;//e[tot].w=w;tot++;
}
bool cmp(edge a,edge b){
return a.w<b.w;
}
int find(int x){//并查集
if(fa[x]==-1) return x;
else return fa[x]==find(fa[x]);
}
int Kruskal(int n){
memset(fa,-1,sizeof(fa));
sort(e,e+tot,cmp);
int cnt(0),ans(0);
for(int i=0;i<tot;i++){
int u=e[i].u,v=e[i].v,w=e[i].w;
int t1=find(u),t2=find(v);
if(t1!=t2){
ans+=w;fa[t1]=t2;cnt++;
}
if(cnt==n-1) break; //确定树
}
if(cnt<n-1) return -1;
else return ans;//最小权
}
int main()
{
int n,m;
cin>>n>>m;
for(int i=1;i<=m;i++)
{
int u,v,w;
cin>>u>>v>>w;
addedge(u,v,w);
}
cout<<Kruskal(n);
return 0;
}
Prim 算法
时间复杂度: O(V ^2)
算法:
1、设有点集 S = {v0} ⊂ V, 边集 E’= ∅,V0∈V;
2、 取 V \ S 中距离 S 最近的点 v , 加入 S 中, 并将该最短边加入 E’中;
3、 当 |S| = n 时, 算法停止;
4、 若 E ’ 的权为 ∞, 则原图不连通, 否则E‘为最小生成树.
点到点集的距离:
模板
int lowc[maxn];//各点到点集的最短距离
int cost[maxn][maxn];
int Prim(int n){
int ans(0);
memset(vis,false,sizeof(vis));//点集S标记
vis[0]=true;
for(int i=1;i<n;i++) lowc[i]=cost[0][i];//耗费矩阵
for(int i=1;i<n;i++){
int minc=inf,p=-1;
for(int j=0;j<n;j++)
if(!vis[j]&& minc >lowc[j]){
minc=lowc[j];p=j;
}
if(minc==inf) return -1;//权为∞,不连通
ans+=minc;vis[p]=true;
for(int j=0;j<n;j++)
if(!vis[j]&& lowc[j]>cost[p][j])
lowc[j]=cost[p][j];
}
return ans;
}
示例
题目同上,代码如下
#include <iostream>
#include <algorithm>
#include <cstring>
#define maxn 100
#define inf 0x3f3f3f
using namespace std;
int lowc[maxn];
int cost[maxn][maxn];
bool vis[maxn];
int Prim(int n)
{
int ans(0);
memset(vis,false,sizeof(vis));
vis[0]=true;
for(int i=1;i<n;i++) lowc[i]=cost[0][i];
for(int i=1;i<n;i++){
int minc=inf,p=-1;
for(int j=0;j<n;j++)
if(!vis[j]&&minc>lowc[j])
{minc=lowc[j];p=j;}
if(minc==inf) return -1;
ans+=minc;vis[p]=true;
for(int j=0;j<n;j++)
if(!vis[j]&&lowc[j]>cost[p][j])
lowc[j]=cost[p][j];
}
return ans;
}
int main()
{
int n,m;
cin>>n>>m;
memset(cost,inf,sizeof(cost));
for(int i=0;i<m;i++)
{
int u,v,w;
cin>>u>>v>>w;
cost[u-1][v-1]=w;
cost[v-1][u-1]=w;
}
cout<<Prim(n);
return 0;
}