之前学习了一些最小生成树的知识点,在此做一个小总结。
树是一种特殊的图,具有很多特殊的性质。生成树,则是从一有n个顶点的连通图中选择n-1条边与这n个顶点组成的树。最小生成树定义为所有生成树中边权之和最小者。这里介绍两种算法用来求解最小生成树。
Kruskal算法
在最开始时,我们可以对每一条边进行排序,然后按照从小到大的顺序对这些边进行选择。
最开始并未选择边时,每一个点都属于一个单独的并查集。当选择第一条边后,将出现一个连通块。就这样循环,每一次加入的边都能够将合二为一,直到最后只剩下一个为止。如果合并次数并不等于n-1的话,就证明该图不是连通图。
该算法步骤概括如下:
(1)将所有的边从小到大排序。
(2) 将所有点分别放入各自的并查集中。
(3)选择所有未使用的边中边权最小的。
(4)若该边所连接的两个点已经连通,则舍去,否则合并这两个并查集。
(5)重复第3步到第4步,直到所有点都已经被标记或所有未标记的点都不存在连边与已标记的点直接相连。
因为Kruskal算法的瓶颈在于将所有的边排序,其复杂度为0(mlogm)。以下是代码示例:
#include<bits/stdc++.h>
using namespace std;
int n,m,now,f[5010];
struct edge{
int to,dis,from;
}e[400010];
void add(int from,int to,int dis) {
e[++now].dis=dis;
e[now].from=from;
e[now].to=to;
}
bool cmp(edge a,edge b){
return a.dis<b.dis;
}
int find(int x){
if(f[x]==x)return x;
return f[x]=find(f[x]);
}
int kruskal(){
int ans=0,cnt=0;
for(int i=1;i<=m;i++){
if(find(e[i].from)!=find(e[i].to)){
cnt++;
ans+=e[i].dis;
f[find(e[i].from)]=find(e[i].to);
}
}
if(cnt<n-1)return -1;
return ans;
}
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++)f[i]=i;
for(int i=1;i<=m;i++){
int from,to,dis;
cin>>from>>to>>dis;
add(from,to,dis);
}
sort(e+1,e+m+1,cmp);
int ans=kruskal();
return 0;
}
Prim算法
该算法的大体思路是从起点开始通过贪心算法不断向外扩展当前的树,直到所有顶点都进入生成树中。需要用一个数组来记录到目前为止当前树中的点到其他点的最短边长。其过程概括如下:
(1)将1号点加入当前生成树中并打上标记,同时更新数组dis。
(2)选择所有未标记的点中dis 最小的点。
(3)将该点加入当前生成树中并打上标记。
(4)使用与该点相连的所有连边更新数组dis。
(5)重复第2步到第4步,直到所有点都已经被标记或所有未标记的点都不存在连边与已标记的点直接相连。
当然,如果出现所有未标记的点都不存在连边与已标记的点直接相连的情况,就意味着图不连通,此时也应当结束程序。可以再记录一个变量,代表打上标记的点的数量。如果最后该变量不等于n,则可以直接判定无解。代码如下:
#include<bits/stdc++.h>
using namespace std;
int n,m,now,head[5010];
long long dis[5010];
bool v[5010];
struct edge{
int to,dis,next;
}e[400010];
struct node{
int from,dis;
friend bool operator<(node a,node b){
return a.dis>b.dis;
}
};
void add(int from,int to,int dis){
e[++now].next=head[from];
e[now].to=to;
e[now].dis=dis;
head[from]=now;
return;
}
priority_queue<node>q;
int prim(){
long long cnt=0,ans=0;
memset(dis,0x3f,sizeof(dis));
dis[1]=0;
q.push({1,0});
while(!q.empty()){
int from=q.top().from,ddis=q.top().dis;
q.pop();
if(v[from])continue;
cnt++;
v[from]=1;
ans+=ddis;
for(int i=head[from];i;i=e[i].next){
if(e[i].dis<dis[e[i].to]){
dis[e[i].to]=e[i].dis;
q.push({e[i].to,e[i].dis});
}
}
}
if(cnt<n)return -1;
return ans;
}
int main(){
cin>>n>>m;
for(int i=1;i<=m;i++){
int from,to,dis;
cin>>from>>to>>dis;
add(from,to,dis);
add(to,from,dis);
}
long long ans=prim();
return 0;
}
如果大家有其他想法的,可以补充。
1799

被折叠的 条评论
为什么被折叠?



