( 图论专题 )【 最小生成树 】
关于图的几个概念定义:
- 连通图:在无向图中,若任意两个顶点vi与vj都有路径相通,则称该无向图为连通图。
- 强连通图:在有向图中,若任意两个顶点vi与vj都有路径相通,则称该有向图为强连通图。
- 连通网:在连通图中,若图的边具有一定的意义,每一条边都对应着一个数,称为权;权代表着连接连个顶点的代价,称这种连通图叫做连通网。
- 生成树:一个连通图的生成树是指一个连通子图,它含有图中全部n个顶点,但只有足以构成一棵树的n-1条边。一颗有n个顶点的生成树有且仅有n-1条边,如果生成树中再添加一条边,则必定成环。
- 最小生成树:在连通网的所有生成树中,所有边的代价和最小的生成树,称为最小生成树。
下面介绍两种求最小生成树算法
1.Kruskal算法
此算法可以称为“加边法”,初始最小生成树边数为0,每迭代一次就选择一条满足条件的最小代价边,加入到最小生成树的边集合里。
1. 把图中的所有边按代价从小到大排序;
2. 把图中的n个顶点看成独立的n棵树组成的森林;
3. 按权值从小到大选择边,所选的边连接的两个顶点 ui,vi,应属于两颗不同的树,则成为最小生成树的一条边,并将这两颗树合并作为一颗树。
4. 重复(3),直到所有顶点都在一颗树内或者有n-1条边为止。
代码:
#include <bits/stdc++.h>
using namespace std;
typedef struct node {
int from,to,dis;
}ty;
ty a[1000005]; // 开的100005 都太小
int pre[1000005];
int date[1000005];
int n,ans,cnt,k;
bool rule(ty a, ty b ) // 优先输出权值最小的边走
{
return a.dis<b.dis;
}
int root(int x) // 返回x点的祖先节点
{
if ( x!=pre[x] ) {
pre[x] = root(pre[x]); // 压缩关系
}
return pre[x];
}
void join( int a, int b, int c ) // 将ab 用并查集连接起来
{
int x = root(a);
int y = root(b);
if ( x!=y ) {
pre[x] = y;
ans += c;
k ++; // k是已经连接的变数
}
}
int main()
{
int i,j,listt,x;
cin >> listt;
while ( listt-- ) {
cin >> n;
ans = 0;
cnt = 0;
k = 0;
for ( i=1; i<=n; i++ ) {
cin >> date[i];
}
for ( i=1; i<=n; i++ ) {
for ( j=1; j<=n; j++ ) {
cin >> x;
if ( i==j ) {
pre[i] = i;
}
else {
a[cnt].from = i;
a[cnt].to = j;
a[cnt].dis = date[i]+date[j]+x;
cnt ++;
}
}
}
sort(a,a+cnt,rule);
for ( i=0; i<cnt; i++ ) { // kruskal
if ( k==n-1 ) { // 如果当前连接好的边k== n-1, 就已经符合最小生成数了
break ;
}
join(a[i].from,a[i].to,a[i].dis);
}
cout << ans << endl;
}
return 0;
}
例题1 : Building a Space Station POJ - 2031
题意:在三维空间中,有n个球,他们的半径是不固定的,位置关系可以是相离,相切,相交,嵌套。想让任意两个球都能联通问最少需要修多长的通道。
思路:三维的最小生成树问题。先将每个球都编号。然后两两确定距离,如果r1+r2>dis 那么是相交的,所以第i号和第j号的距离是0.
代码:
#include <iostream>
#include <algorithm>
#include <stdio.h>
#include <math.h>
using namespace std;
typedef struct node {
double x,y,z,r;
} tyy;
tyy date[1005]; // 存球的坐标,半径
typedef struct edge {
int from,to;
double dis;
}ty;
ty a[100005]; // 存球和球之间的关系
int pre[1005];
int n,k,cnt;
double ans;
double fdis( tyy a, tyy b ) // 返回两个球的距离
{
double dis = sqrt( (a.x-b.x)*(a.x-b.x) + (a.y-b.y)*(a.y-b.y) + (a.z-b.z)*(a.z-b.z) );
if ( a.r+b.r>=dis ) {
return 0.0;
}
else {
return dis-a.r-b.r;
}
}
bool rule( ty a, ty b )
{
return a.dis<b.dis;
}
int root(int x)
{
if ( x!=pre[x] ) {
pre[x] = root(pre[x]);
}
return pre[x];
}
void join( int a, int b, double c )
{
int x = root(a);
int y = root(b);
if ( x!=y ) {
pre[x] = y;
ans += c;
k ++;
}
}
int main()
{
int i,j;
while ( cin>>n ) {
if ( n==0 ) {
break;
}
cnt = 0;
ans = 0;
k = 0;
for ( i=1; i<=n; i++ ) { // 讲所有数据存起来
cin >> date[i].x >> date[i].y >> date[i].z >> date[i].r;
}
for ( i=1; i<=n; i++ ) { // 遍历两球的所有情况,并建立联系
for ( j=1; j<=n; j++ ) {
if ( i==j ) {
pre[i] = i;
}
else {
a[cnt].from = i;
a[cnt].to = j;
a[cnt].dis = fdis(date[i],date[j]);
cnt ++;
}
}
}
// kruskal
sort(a,a+cnt,rule);
for ( i=0; i<cnt; i++ ) {
if ( k==n-1 ) {
break ;
}
join(a[i].from,a[i].to,a[i].dis);
}
printf("%.3f\n",ans);
}
return 0;
}