最小支撑树(最小生成树)

如下图所示,连通图G的某一无环两通子图T若覆盖G中所有的顶点,则称作G的一棵支撑树生成树(spanning tree)。

其中顶点数为n,其边数为n-1。

如果其生成树中各边权值和最小,那么称其为最小生成树(可以不唯一)。

 

实现一 Prim算法

在图中G=(V;E)中,顶点集V的任一非平凡子集U及其补集V\U都构成G的一个(cut),记作(U:V\U)。若边uv满足u属于U且v不属于U,则称作该割的一条跨越边(crosssing edge)。因此类边联接于V及其补集之间,称作该割为(bridge)。 

Prim算法就是,每次采用最短的跨越边。 

 

b> 首先选择A结点作为点集

c> 找A--B,A--D,A--G权值最小的点(B),之后将其加入点集中

d> 找点集中跨越边最小的边,A--D,A--G,B--C,到D的权值最小,将其加入到点集中

e> 重复上述过程,A--G,D--G,D--E,B--C,到G的权值最小,将其加入到点集中

一直重复上述步骤直到找到的边数为n-1条,i就是通过Prim算法找到的最小生成树了。

模板

#include<cstdio>
#include<cstdlib>
#include<iostream>

using namespace std;
/*最小生成树Prim未优化版*/

int book[100];//用于记录这个点有没有被访问过
int dis[100];//用于记录距离树的距离最短路程
int MAX = 99999;//边界值
int maps[100][100];//用于记录所有边的关系

int main()
{
    int i,j,k;//循环变量
    int n,m;//输入的N个点,和M条边
    int x,y,z;//输入变量
    int min,minIndex;
    int sum=0;//记录最后的答案
    
    cin>>n>>m;

    //初始化maps,除了自己到自己是0其他都是边界值
    for (i = 1; i <= n; i++)
    {
        for (j = 1; j <= n; j++)
        {
            if(i!=j)
                maps[i][j] = MAX;
            else
                maps[i][j] = 0;
        }
    }
            
    for (i = 1; i <= m; i++)
    {
        cin>>x>>y>>z;//输入的为无向图
        maps[x][y] = z;
        maps[y][x] = z;
    }

    //初始化距离数组,默认先把离1点最近的找出来放好
    for (i = 1; i <= n; i++)
        dis[i] = maps[1][i];

    book[1]=1;//记录1已经被访问过了

    for (i = 1; i <= n-1; i++)//1已经访问过了,所以循环n-1次
    {
        min = MAX;//对于最小值赋值,其实这里也应该对minIndex进行赋值,但是我们承认这个图一定有最小生成树而且不存在两条相同的边
        //寻找离树最近的点
        for (j = 1; j <= n; j++)
        {
            if(book[j] ==0 && dis[j] < min)
            {
                min = dis[j];
                minIndex = j;
            }
        }

        //记录这个点已经被访问过了
        book[minIndex] = 1;
        sum += dis[minIndex];

        for (j = 1; j <= n; j++)
        {
            //如果这点没有被访问过,而且这个点到任意一点的距离比现在到树的距离近那么更新
            if(book[j] == 0 && maps[minIndex][j] < dis[j])
                dis[j] = maps[minIndex][j];
        }
    }

    cout<<sum<<endl;
}

 

实现二 kruskal算法

先将每条边按照权值的大小非降序的排列,之后按照顺序选取边(不在同一集合),如果两个结点不在同一集合,就将它们合并,直到所有结点在同一集合为止。

蓝色为放弃的边,因为依次选取时3--4,4--1都为同一个集合,而2--3不为一个集合,所有选择2--3。

模板

#include <stdio.h>
#include <string.h>
#include <algorithm>
#define MAXN 11  //顶点个数的最大值
#define MAXM 20  //边的个数的最大值
using namespace std; 

struct edge  //边
{
    int u, v, w; //边的顶点、权值
}edges[MAXM]; //边的数组

int parent[MAXN];  //parent[i]为顶点 i 所在集合对应的树中的根结点
int n, m;  //顶点个数、边的个数
int i, j;  //循环变量
void UFset( )  //初始化
{
    for( i=1; i<=n; i++ ) 
        parent[i] = -1;
}
int Find( int x ) //查找并返回节点 x 所属集合的根结点
{
    int s; //查找位置
    for( s=x; parent[s]>=0; s=parent[s] );
    while( s!=x ) //优化方案―压缩路径,使后续的查找操作加速。
    {
        int tmp = parent[x];
        parent[x] = s;
        x = tmp;
    }
    return s;
}

//将两个不同集合的元素进行合并,使两个集合中任两个元素都连通
void Union( int R1, int R2 )
{
    int r1 = Find(R1), r2 = Find(R2); //r1 为 R1 的根结点,r2 为 R2 的根结点
    int tmp = parent[r1] + parent[r2]; //两个集合结点个数之和(负数)
    //如果 R2 所在树结点个数 > R1 所在树结点个数(注意 parent[r1]是负数)
    if( parent[r1] > parent[r2] ) //优化方案――加权法则
    {
        parent[r1] = r2; 
        parent[r2] = tmp;
    }
    else
    {
        parent[r2] = r1; 
        parent[r1] = tmp;
    }
}
bool cmp( edge a, edge b ) //实现从小到大排序的比较函数
{
    return a.w <= b.w;
}
void Kruskal( )
{
    int sumweight = 0;  //生成树的权值
    int num = 0;  //已选用的边的数目
    int u, v;  //选用边的两个顶点
    UFset( ); //初始化 parent[]数组
    for( i=0; i<m; i++ )
    {
        u = edges[i].u; v = edges[i].v;
        if( Find(u) != Find(v) )
        {
            printf( "%d %d %d\n", u, v, edges[i].w );
            sumweight += edges[i].w; num++;
            Union( u, v );
        }
        if( num>=n-1 ) break;
    }
    printf( "weight of MST is %d\n", sumweight );
}
int main( )
{
    int u, v, w; //边的起点和终点及权值
    scanf( "%d%d", &n, &m ); //读入顶点个数 n
    for( int i=0; i<m; i++ )
    {
    scanf( "%d%d%d", &u, &v, &w ); //读入边的起点和终点
    edges[i].u = u; edges[i].v = v; edges[i].w = w;
    }
    sort(edges,edges+m,cmp);
    Kruskal();
    return 0;
}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值