如下图所示,连通图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;
}