目录
两大算法介绍:prim普里姆算法&Kruskal克鲁斯卡尔算法
最小生成树是啥?
树是啥?
顾名思义:树,当然要长得像树,当然要满足几个成为树的条件(这些条件看一看有个印象就行了):
1. 每个节点有零个或多个子节点;
就好像树的顶端,有的是枝桠,可以继续有叶子,有的是叶子,不能再有叶子了。
2. 没有父节点的节点称为根节点;
来个图你就懂了(图中,A就是根节点,因为它上面没人了~)
-
对于不是根节点的节点来说,它只能有一个父节点,比如B只有A,H只有C。
-
除了根节点,其他的节点都可以分成小树,就像根上的枝桠,枝桠上的叶子,但是要注意,叶子或者说枝桠之间,不能够连起来,你长你的,我长我的,要是连起来就不是树了,就是图了,就像近亲结婚一样要不得(笑;
「所以树里面不能连起来,就是说树中不能有环」
最小是啥最小?
先看看百度的定义:“一个有 n 个结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有 n 个结点,并且有保持图连通的最少的边。”
是不是懵了?
想理解这个不妨先下去看“应用场景。”
懂了用在哪里,再学就知道为啥了。
用应用场景的例子,n个城市铺设光缆,肯定是越省钱越好,也就是,我要把这些城市连起来,还要花的钱最少,这样说这个例子理解了吧?
花的钱少,对应到树里面,就是一个点到另一个点的距离短,那最小生成树,就是在所有点的各种连法中,选一种花钱最少,也就是距离总和最短的一棵树。
放到树里面,这个距离就叫权值,那不管三七二十一,我只要一个权值距离最短的就好了。
而权值和最短的树,就是最小生成树了。
应用场景:
要在n个城市之间铺设光缆,主要目标是要使这 n 个城市的任意两个之间都可以通信,但铺设光缆的费用很高,且各个城市之间铺设光缆的费用不同,因此另一个目标是要使铺设光缆的总费用最低。这就需要找到带权的最小生成树。
两大算法介绍:prim普里姆算法&Kruskal克鲁斯卡尔算法
该怎么想?
抛开算法不谈,你拿到这个问题(光缆)想怎么解决?
那当然就是把我得到的所有城市之间的距离拿出来
排个序,然后从小的线路开始建,对吧?
巧了,克鲁斯卡尔算法也是这么个思路。
那我们先来看看吧。
克鲁斯卡尔算法:
将一颗颗小树合并成一棵大树,即从权值最小的边开始收录,将两个节点合并到一起,慢慢合并直到所有的小树合并在一起。
步骤:
1.边长排序
2.从小到大添加边长
3.判断(详细看下面)
画个图举个例子:
这个例子里,题目给了我两个点1,2和三条边,分别长为37km,17km,68km
怎么做?
首先按三条边的长度,从小到大进行排序。
然后让2-1(17)这条边相连,
注意!
相连之后要做两个超关键的事情:
(1)判断边数是不是n-1(n是点数也就是2)
(2)判断有没有环(之前说了有环了就是图,不能是数,所以这个一定要注意)
这样做完之后就可以轻松得到答案了。
「注意这些边是没有方向的,所以存的时候两边都要存」
不是很理解?
那再来个例子!
老样子,n是点,m是边。
排序
对每一步进行的操作如下,细细看图即可。
普里姆算法:
让一棵小树慢慢长大,即从一个根节点开始,一个个的添加节点到最小生成树上。添加的过程中,要满足以下几个条件:
1.添加的节点一定是和当前的生成树有相连边的节点
2.一定是离当前树距离最近的节点(贪心)
3.不能有回路
prim算法就是把连入树的一段节点看成一个整体,而没连入这个整体的其他点,与这个整体相连的部分,就是需要讨论的「根据以上条件思考即可」。
例题与思路:
Networking
题意:(去poj搜)和上面说的应用差不多,只是多了“多组样例。”
AC代码:
#include <cstdio>
#include <string>
#include <cstring>
#include <iostream>
#include <algorithm>
#define int long long
using namespace std;
const int INF = 0x3f3f3f3f3f;
const int N = 505;
int a[N][N],dist[N];
bool vis[N];//用来判断走没走过
int n,m,sum;
int u,v,w;
int prime(int pos)
{
dist[pos]=0;
for(int i=1;i<=n;i++)
{
int cur = -1;
for(int j=1;j<=n;j++)
if(!vis[j]&&(cur==-1||dist[j]<dist[cur])) cur = j;
if(dist[cur]>=INF) return INF;
sum += dist[cur];
vis[cur]=true;
for(int k=1;k<=n;k++)
if(!vis[k]) dist[k] = min(dist[k],a[cur][k]);
}
return sum;
}
signed main()
{
while(scanf("%lld",&n)!=EOF){
if(n==0) break;
cin>>m;sum=0;
memset(a, 0x3f, sizeof(a));
memset(dist, 0x3f, sizeof(dist));
memset(vis, false, sizeof(vis));
for(int i=1;i<=m;i++)
{
cin>>u>>v>>w;
a[u][v]=min(a[u][v],w);
a[v][u]=min(a[v][u],w);
}
int val = prime(1);
if(val>=INF) puts("impossible");
else cout<<sum<<endl;
}
return 0;
}
Highways
题意:
同样给我n个点和m条边
只不过这一次不是让你求最小生成树的权值和了,是要求路径!
有几个点需要注意「见代码」
(1)如何读入长度
(2)prim板子预处理
(3)记录之前走的位置
AC代码:
#include <cstdio>
#include <string>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <cmath>
#define int long long
using namespace std;
const int INF = 0x3f3f3f3f3f;
const int N = 1e3+10;
int a[N][N],dist[N],x[N],y[N],pre[N];
bool vis[N];
int n,m,sum=0;
int u,v,w;
//dist[i]数组存还未处理的城市i离已经处理过的城市的最短距离,
//void prime()
//{
// for(int i=1;i<=n;i++)
// {
// pre[i]=1;
// dist[i]=a[1][i];
// }
// dist[1]=-1;
// for(int i=1;i<n;i++)
// {
// int cur = -1;
// for(int j=1;j<=n;j++)
// if(dist[j]!=-1&&(cur==-1||dist[j]<dist[cur])) cur = j;
// if(dist[cur]>=INF) return;
// if(dist[cur]!=0) printf("%lld %lld\n",pre[cur],cur);
// dist[cur]=-1;
// for(int k=1;k<=n;k++)
// {
// if(a[k][cur]<dist[k]){
// dist[k]=a[k][cur];
// pre[k]=cur;
// }
// }
// }
//}
void prime()
{
for(int i=1;i<=n;i++)
{
pre[i]=1;
dist[i]=a[1][i];
}
vis[1]=true;
for(int i=1;i<n;i++)
{
int cur = -1;
for(int j=1;j<=n;j++)
if(!vis[j]&&(cur==-1||dist[j]<dist[cur])) cur = j;;
if(dist[cur]!=0) printf("%lld %lld\n",pre[cur],cur);
vis[cur]=true;
for(int k=1;k<=n;k++)
if(!vis[k]&&a[k][cur]<dist[k])
{
dist[k]=a[k][cur];
pre[k]=cur;
}
}
// return sum;
}
signed main()
{
cin>>n;
memset(a, 0x3f, sizeof(a));
for(int i=1;i<=n;i++)
cin>>x[i]>>y[i];
for(int i=1;i<=n;i++)
{
for(int j=1;j<i;j++)
a[i][j]=a[j][i]=(x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j]);
a[i][i]=INF;
}
cin>>m;
for(int i=1;i<=m;i++)
{
cin>>u>>v;
a[u][v]=a[v][u]=0;
}
prime();
return 0;
}
Arctic Network
题意:同样是一个比较核善的最短路,只不过这次求的东西又不一样了,最小生成树中所有的路径,卫星要占据大的部分(此处sort排序解决),然后求剩下的最大的长度。
AC代码:
#include <cstdio>
#include <string>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <cmath>
#define int long long
using namespace std;
const int INF = 0x3f3f3f3f3f;
const int N = 505;
double a[N][N],dist[N];
bool vis[N];//用来判断走没走过
double sum=0;
int u,v,w,n,m,tot=0;
double x[N],y[N];
double ans[N];
//有S颗卫星和P个哨所,有卫星的两个哨所之间可以任意通信;
//否则,一个哨所只能和距离它小于等于D的哨所通信。给出卫星的数量和P个哨所的坐标,求D的最小值
void prime(int pos)
{
for(int i=1;i<=m;i++)
dist[i]=a[pos][i];
dist[pos]=0;
vis[pos]=1;
for(int i=1;i<m;i++)
{
int cur = -1;
for(int j=1;j<=m;j++)
if(!vis[j]&&(cur==-1||dist[j]<dist[cur])) cur = j;
// if(dist[cur]>=INF) return INF;
// sum += dist[cur];
// ans[++tot]=dist[cur];
vis[cur]=true;
for(int k=1;k<=m;k++)
if(!vis[k]) dist[k] = min(dist[k],a[cur][k]);
}
// sort(dist+1,dist+1+m);
// printf("%.2f\n",dist[m-n+1]);
// return sum;
}
signed main()
{
int T;cin>>T;
while(T--){
cin>>n>>m;
memset(a, 0x3f, sizeof(a));
// memset(dist, 0x3f, sizeof(dist));
memset(vis, false, sizeof(vis));
for(int i=1;i<=m;i++)
{
cin>>x[i]>>y[i];
for(int j=1;j<i;j++)
a[i][j]=a[j][i]=sqrt((x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j]));
}
prime(1);
sort(dist+1,dist+1+m);
printf("%.2f\n",dist[m-n+1]);
}
return 0;
}
写在最后:模版
prim模版:
题意:
n个点,m条边,每次给出u,v,w;
u,v表示两点,w表示边长(花的钱),或者说权重
代码:
#include <cstdio>
#include <string>
#include <cstring>
#include <iostream>
#include <algorithm>
#define int long long
using namespace std;
const int INF = 0x3f3f3f3f3f;
const int N = 505;
int a[N][N],dist[N];
bool vis[N];//用来判断走没走过
int n,m,sum=0;
int u,v,w;
int prime(int pos)
{
dist[pos]=0;
for(int i=1;i<=n;i++)
{
int cur = -1;
for(int j=1;j<=n;j++)
if(!vis[j]&&(cur==-1||dist[j]<dist[cur])) cur = j;
if(dist[cur]>=INF) return INF;
sum += dist[cur];
vis[cur]=true;
for(int k=1;k<=n;k++)
if(!vis[k]) dist[k] = min(dist[k],a[cur][k]);
}
return sum;
}
signed main()
{
cin>>n>>m;
memset(a, 0x3f, sizeof(a));
memset(dist, 0x3f, sizeof(dist));
memset(vis, false, sizeof(vis));
for(int i=1;i<=m;i++)
{
cin>>u>>v>>w;
a[u][v]=min(a[u][v],w);
a[v][u]=min(a[v][u],w);
}
int val = prime(1);
if(val>=INF) puts("impossible");
else cout<<sum<<endl;
return 0;
}
Kruskal模版:
题意:
n个点,m条边,每次给出u,v,w;
u,v表示两点,w表示边长(花的钱),或者说权重
代码:
#include <cstdio>
#include <string>
#include <cstring>
#include <iostream>
#include <algorithm>
#define int long long
using namespace std;
const int N = 2e5 + 10;
//kruskal(克鲁斯卡尔算法)的思路就是把所有边的值取出,然后从小到大去取边
//如果达到了n-1就断开,如果未达到,就继续
//如果形成了自环,就打开。
/*用来存图*/
struct node{
int x,y,z;
}edge[N];
bool cmp(struct node a,struct node b)
{
return a.z<b.z;
}
int fa[N],n,m,sum;
//求父亲节点
int get(int x)
{
return x == fa[x] ? x : fa[x] = get(fa[x]);
}
signed main()
{
cin>>n>>m;
for(int i=1;i<=m;i++)
cin>>edge[i].x>>edge[i].y>>edge[i].z;
for(int i=1;i<=n;i++)
fa[i]=i;
sort(edge+1,edge+1+n,cmp);
for(int i=1;i<=m;i++)
{
int x = get(edge[i].x);
int y = get(edge[i].y);
if(x==y) continue;
fa[y] = x;
sum += edge[i].z;
}
int ans = 0;
for(int i=1;i<=n;i++)
if(i == fa[i]) ans++;
if(ans>1) puts("impossible");
else cout<<sum<<endl;
return 0;
}
参考文章:
树与树算法(一)树的介绍_BlazarBruce的博客-CSDN博客
最小生成树问题(Prim算法和Kruskal算法的异同总结)_舔狗之王的博客-CSDN博客_用prim和kruskal算法求最小生成树一样吗