一、概述
最小生成树问题顾名思义,概括来说就是路修的最短。
接下来引入几个一看就明白的定义:
最小生成树相关概念:
带权图:边赋以权值的图称为网或带权图,带权图的生成树也是带权的,生成树T各边的权值总和称为该树的权。
最小生成树(MST):权值最小的生成树。
最小生成树的性质:假设G=(V,E)是一个连通网,U是顶点V的一个非空子集。若(u,v)是一条具有最小权值的边,其中u∈U,v∈V-U,则必存在一棵包含边(u,v)的最小生成树。
完成构造网的最小生成树必须解决下面两个问题:
- 尽可能选取权值小的边,但不能构成回路;
- 选取n-1条恰当的边以连通n个顶点;
prim算法适合稠密图,kruskal算法适合简单图。
二、Prim算法
首先,假设有一棵只包含一个顶点v的树T,然后贪心地选取T和其它顶点之间相连的最小权值的边,并把它加到T中,不断进行这个操作,就可以得到一棵生成树了,可证这是一棵最小生成树
如何查找最小权值的边?假设已经求得的生成树的顶点的集合是X,把X集合和顶点v连接的边的最小权值记为mincost[v],在向X里添加顶点u时,只需要查看和u相连的边就可以了,对于每条边,更新mincost[v]=min(mincost[v],边(u,v)的权值)
如果每次都遍历为包含在X中的点的mincost[v],需要O(|V|2)的时间,不过和Dijkstra一样我们可以使用堆来维护mincost,时间复杂度就是O(|E|log|V|)
#include <cstdio>
#include <queue>
#include <vector>
#include <utility>
using namespace std;
typedef pair<int, int> P;
struct Edge
{
int to, cost;
};
const int MAX_V=5000;
const int INF=0x3f3f3f3f;
int V, E, N;
vector<Edge> G[MAX_V];
int res;
int mincost[MAX_V];
bool used[MAX_V];
void prim()
{
for (int i=0; i<V; i++)
{
mincost[i]=INF;
used[i]=false;
}
mincost[0]=0;
priority_queue<P, vector<P>, greater<P> > que;
que.push(P(0, 0));
while (!que.empty())
{
P p=que.top(); que.pop();
int v=p.second;
if (mincost[v]<p.first) continue;
res+=mincost[v];
N++;
used[v]=true;
for (int i=0; i<G[v].size(); i++)//加入顶点v时只要查看和顶点v相连的边即可
{
if (mincost[G[v][i].to]>G[v][i].cost && !used[G[v][i].to])//更新mincost,入队
{
mincost[G[v][i].to]=G[v][i].cost;
que.push(P(G[v][i].cost, G[v][i].to));
}
}
}
}
int main()
{
scanf("%d %d", &V, &E);
for (int i=0; i<E; i++)
{
int s, t, c;
scanf("%d %d %d", &s, &t, &c);
Edge e;
e.to=t-1; e.cost=c;
G[s-1].push_back(e);
e.to=s-1;
G[t-1].push_back(e);
}
prim();
if (N <V) puts("orz");
else printf("%d
", res);
}
三、kruskal算法
kruskal远离更为简单粗暴,但是需要借助并查集这一知识,来判断是否存在圈。
Kruskal算法按照边的权值的大小从小到大查看一遍,如果不产生圈(重边等也算在内),就把当前这条边加入到生成树中,一共加V-1条边。
如何判断是否产生圈? 假设现在要把连接顶点u和顶点v的边e加入生成树中。如果加入之前u和v不在同一个连通分量里,那么加入e也不会产生圈,反之,如果u和v在同一个连通分量里,那么一定会产生圈,可以用并查集高效地判断是否属于同一个连通分量
Kruskal在边的排序上最费时,算法的复杂度是O(|E|log|V|).
#include <cstdio>
#include <queue>
#include <vector>
#include <algorithm>
#include <cctype>
#define num s-'0'
using namespace std;
struct edge
{
int u;
int v;
int cost;
};
const int MAX_V=5000;
const int MAX_E=200000;
const int INF=0x3f3f3f3f;
int V,E,n;
edge es[MAX_E];
int res;
int par[MAX_V];
int r[MAX_V];
void kruskal();
void init();
int find(int);
void unite(int, int);
bool same(int, int);
void read(int &x){
char s;
x=0;
bool flag=0;
while(!isdigit(s=getchar()))
(s=='-')&&(flag=true);
for(x=num;isdigit(s=getchar());x=x*10+num);
(flag)&&(x=-x);
}
void write(int x)
{
if(x<0)
{
putchar('-');
x=-x;
}
if(x>9)
write(x/10);
putchar(x%10+'0');
}
bool compare(const edge &e1, const edge &e2)
{
return e1.cost<e2.cost;
}
int main()
{
read(V);read(E);
for (int i=0; i<E; i++)
{
read(es[i].u);read(es[i].v);read(es[i].cost);
es[i].u--;es[i].v--;
}
kruskal();
write(res);
putchar('
');
}
void init()
{
for (int i=0; i<V; i++)
{
par[i]=i;
r[i]=0;
}
}
int find(int x)
{
if (par[x]==x) return x;
return par[x]=find(par[x]);
}
void unite(int x, int y)
{
x=find(x);
y=find(y);
if (x==y) return;
if (r[x]<r[y]) par[x]=y;
else
{
par[y]=x;
if (r[x]==r[y]) r[x]++;
}
}
bool same(int x, int y)
{
return (find(x)==find(y));
}
void kruskal()
{
sort(es, es+E, compare);
init();
int n=0;
for (int i=0; i<E; i++)
{
edge e=es[i];
if (!same(e.u, e.v))
{
unite(e.u, e.v);
res+=e.cost;
++n;
}
if (n==V-1) break;
}
}