生成树
定义:如果连通图G的一个子图是一棵包含G所有顶点的树
则称该子图为G的生成树
生成树是一棵树嘛,所以树上任两点之间有且只有一条通路。
这样就免去了环。
一个图的生成树可能会有很多,生成一棵树很简单
随便找一个顶点暴搜就可以
不同的顶点选择和不同的搜索方式都会导致最后的树不同
最小生成树
定义:连通图G的所有生成树中,边权之和最小的生成树就是图G的最小生成树
构造最小生成树最常用的方法有两种
一是Prim,二是Kruskal,如下
Prim
任意在图G中取一个点P
然后看 【以点P为一个端点的边 】中, 哪一个边权最小,把这个点与P点结合成一个集合
这个集合将会是我们的最小生成树
这时候我们的树里已经有两个点了
接着,我们要找到距离这个集合最近的一个点
也就是把这个集合当做一个整体,这个整体向外放射的所有边中有一个边的边权最小
那条边连接的点就是下一个要“收入囊中”的点
不断地重复这一步骤直到所有点都在集合里
我们的最小生成树就建完了
这种算法的时间复杂度为O(n^2)
下附代码(数组模拟邻接表)
void prim(){
int i, j, k;
int goal;
int mn;
memset(intree, 0, sizeof(intree));
memset(distt, INF, sizeof(distt));
distt[1] = 0;
for(i = 1; i <= n; i++){
//以下为寻找到集合最近的点
mn = INF;
goal = -1;
for(j = 1; j <= n; j++){
if(intree[j])continue;
if(distt[j] < mn){
mn = distt[j];
goal = j;
}
}
intree[goal] = 1;
//cout << mn << endl;
ans += mn;
if(goal == -1){
printf("orz\n");
return ;
}
//以下循环为点到集合距离的维护
k = head[goal];
while(k != -1){
if(w[k] < distt[v[k]]){
distt[v[k]] = w[k];
}
k = next[k];
}
}
printf("%d\n", ans);
}
int i, j, k;
int goal;
int mn;
memset(intree, 0, sizeof(intree));
memset(distt, INF, sizeof(distt));
distt[1] = 0;
for(i = 1; i <= n; i++){
//以下为寻找到集合最近的点
mn = INF;
goal = -1;
for(j = 1; j <= n; j++){
if(intree[j])continue;
if(distt[j] < mn){
mn = distt[j];
goal = j;
}
}
intree[goal] = 1;
//cout << mn << endl;
ans += mn;
if(goal == -1){
printf("orz\n");
return ;
}
//以下循环为点到集合距离的维护
k = head[goal];
while(k != -1){
if(w[k] < distt[v[k]]){
distt[v[k]] = w[k];
}
k = next[k];
}
}
printf("%d\n", ans);
}
Kruskal
虽然比起这个算法,prim就略显繁琐了,但我还是更喜欢prim~
大致思路就是每次取边权最小的边,这些散的边共端点便合成一个一棵树
合成一棵树的时候会用到并查集了
最后聚合成的大树就是最小生成树
虽然不喜欢,但这个算法也是要写的
因为在图不是联通的, 要建多棵树的时候
kruskal比prim要方便一些(比如2013noip货车运输)
下附代码(洛谷3366板题):
int find(int x){
if(fa[x] == x)
return x;
else
return fa[x] = find(fa[x]);
}
bool cmp(node x, node y){
return x.wei < y.wei;
}
void init(){
int i, j;
int x, y, z;
scanf("%d%d", &n, &m);
for(i = 1; i <= n; i++){
fa[i] = i;
}
for(i = 1; i <= m; i++){
scanf("%d%d%d", &x, &y, &z);
cnt ++;
edge[cnt].u = x;
edge[cnt].v = y;
edge[cnt].wei = z;
}
sort(edge + 1, edge + 1 + cnt, cmp);
}
void kruskal(){
int i, j;
int ans = 0;
for(i = 1; i <= m; i++){
if(find(edge[i].u ) != find(edge[i].v )){
fa[find(edge[i].u)] = edge[i].v;
ans += edge[i].wei;
}
}
int temp = find(1);
for(i = 2; i <= n; i++){
if(find(i) != temp){
printf("orz");
return ;
}
}
printf("%d", ans);
}
int main() {
init();
kruskal();
return 0;
}