听骚话讲作者
[手动滑稽]
其实早就该写这篇博客啦。
图的最小生成树
一个有 n 个结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有 n 个结点,并且有保持图连通的最少的边。最小生成树可以用kruskal(克鲁斯卡尔)算法或Prim(普里姆)算法求出。
换句话说,就是使原图变成一棵树并且要求这棵树中保留的边最小。(变树的过程需要删除边)
如下图,红线为此图的一个最小生成树:
浅谈克鲁斯卡尔算法
克鲁斯卡尔算法是一种用来寻找最小生成树的算法。在剩下的所有未选取的边中,找最小边,如果和已选取的边构成回路,则放弃,选取次小边。
根据克鲁斯卡尔算法定义,我们要解决以下问题:
1.选择多少条这样的边?
2.使用什么思想来解决?(贪心,动态规划,数论,分治,递归,递推。。。。。。)
对于第一个问题,我们很容易可以得到答案。假设有n个点,那么必然保留n-1条边,只有这样才满足树的性质。
满足了树的性质,我们还要满足“最小”的性质。
在克鲁斯卡尔算法中,我们选择使用贪心法来解决。
我们为了取用最小的边,所以对所有的边进行排序,并从最小的开始选取。
为此,我们需要一个存储边以及边权的表,如下图:
struct e{
int u,v,order,s;
e(){u=v=order=0;return;}
bool operator < (const e &x)const{
if(s<x.s) return 1;
return 0;
}
}edge[maxm];
这里使用了运算符重载,因为我们要的只是对边权排序,而其他所有和边有关的变量一起被排序。
对这种捆绑排序和运算符重载不理解的可以戳这里:戳我手动滑稽一下
使用下面这个函数有助于你往边表里添加边,当然你可以不写这个函数。
void addedge(int u,int v,int s,int order){ //这不是邻接表!
edge[++js].u=u;
edge[js].v=v;
edge[js].s=s;
edge[js].order=order;
return;
}
这看起来很像邻接表,但是这绝对不是邻接表。这里没有链表的象征——后继指针next
如果你对我上述的话有质疑,那你一定要看这篇——邻接表存图法
这绝对不会浪费你人生的十分钟
接下来我们优美的排个序:
sort(edge+1,edge+s+1);
然而,当我们从最小的开始选取时,选完最小的两边——(1,2)和(1,3)时,有可能就会选到上图中标蓝色的边。而一旦选取了这个边,就会构成回路,这样就不满足树的性质了。
为了防止其形成回路,我们需要使用一个数据结构——并查集
每当我们加入一条边时,便把这个边的两个端点加入并查集。然后后续选择边的时候,需要把即将添加的边的两个端点进行查找,如果不在同一个并查集里头,就可以加入该边,否则就跳过这条边而选择下一条次小的。
流程这样描述:
for(int i=1;i<=s;i++){
if(time==p-1) break;
if(get(edge[i].u)!=get(edge[i].v)){
merge(edge[i].u,edge[i].v);
cout<<edge[i].u<<" link with "<<edge[i].v<<endl;
time++;
}
}
其中if(time==p-1) break; 就是当已经选择了p-1条边时(假设p为总点数)自动退出循环。
if(get(edge[i].u)!=get(edge[i].v)) 这个就是判断是否形成环路的,通过这层判断就代表不在同一个并查集里。
我们这里用cout<<edge[i].u<<" link with "<<edge[i].v<<endl; 来输出我们被选中的边。time是用于统计被选中的边数。
这样,克鲁斯卡尔基本上就搞定了
代码示例
样例输入 | 样例输出 |
---|---|
6 10 | 1 link with 2 |
1 2 1 | 1 link with 3 |
1 3 1 | 2 link with 5 |
2 3 2 | 4 link with 5 |
2 4 3 | 4 link with 6 |
2 5 2 | |
3 5 2 | |
3 4 3 | |
4 5 2 | |
4 6 3 | |
5 6 3 |
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxm=10001;
int p,s;
int a,b,c;
struct e{
int u,v,order,s;
e(){u=v=order=0;return;}
bool operator < (const e &x)const{
if(s<x.s) return 1;
return 0;
}
}edge[maxm];
int js;
void addedge(int u,int v,int s,int order){ //这不是邻接表!
edge[++js].u=u;
edge[js].v=v;
edge[js].s=s;
edge[js].order=order;
return;
}
int fa[maxm];
int get(int x){
if(fa[x]==x) return x;
return fa[x]=get(fa[x]);
}
void merge(int x,int y){
x=get(x);y=get(y);
if(x!=y) fa[y]=x;
return;
}
int time;
void kruskal(){
sort(edge+1,edge+s+1);
for(int i=1;i<=s;i++){
if(time==p-1) break;
if(get(edge[i].u)!=get(edge[i].v)){
merge(edge[i].u,edge[i].v);
cout<<edge[i].u<<" link with "<<edge[i].v<<endl;
time++;
}
}
return;
}
int main(){
std::ios::sync_with_stdio(false);
cin>>p>>s;
for(int i=1;i<=s;i++){
cin>>a>>b>>c;
addedge(a,b,c,i);
}for(int i=1;i<=p;i++)
fa[i]=i;
kruskal();
return 0;
}