( 图论专题 )【 最小生成树 】

( 图论专题 )【 最小生成树 】

关于图的几个概念定义:

  • 连通图:在无向图中,若任意两个顶点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;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值