前提:
生成树是建立在无向图的基础上的,所以下面的讨论都是在无向图上进行的。
直观的一个应用就是:有n个村庄,现在要在这些村庄之间修一些路,其中村庄i和村庄j之间的距离是Dij,现在要修最短的路使得所有村庄连接起来。
最小生成树( MIT )的经典解决方法有2种:prim算法和kruskal算法。
1.prim算法
算法思想:设图G顶点集合为U,首先任意选择图G中的一点作为起始点a,将该点加入集合V,再从集合U-V中找到另一点b使得点b到V中任意一点的权值最小,此时将b点也加入集合V;以此类推,现在的集合V={a,b},再从集合U-V中找到另一点c使得点c到V中任意一点的权值最小,此时将c点加入集合V,直至所有顶点全部被加入V,此时就构建出了一颗MST。因为有N个顶点,所以该MST就有N-1条边,每一次向集合V中加入一个点,就意味着找到一条MST的边。
算法需要3个数组来实现:
arr[i][j]:存放点到点之间的权值,初始值为最大值,若 是该两点之间有边则改变权值
a[i]:表示以i为终点的边的最小权值,当a[i]=0说明以i为终点的边的最小权值=0,也就是表示i点加入了MST
s[i]:表示对应a[i]的起点,即说明边< s[i],i>是MST的一条边,当s[i]=0表示起点i加入MST
例子:
- 任取一点作为起始点,假设选 点 v1.
则
a[2]=6, a[3]=0, a[4]=5, a[5]=无穷,a[6]=无穷
s[2]=3, s[3]=0, s[4]=1, s[5]=1, a[6]=1 , - 可以看出 a[3] 的值最小,所以 点 v3 加入MST,于是更新数组:
a[2]=5, a[3]=0, a[4]=5,a[5]=6,a[6]=4
s[2]=3, s[3]=0, s[4]=1, s[5]=3,a[6]=3 , - a[6]的值最小,所以 点 V6加入 MST,更新数组:
a[2]=5, a[3]=0, a[4]=2,a[5]=6,a[6]=0
s[2]=3, s[3]=0, s[4]=6, s[5]=3,a[6]=0 , - a[4]的值最小,所以点 V4 加入 MST,更新数组:
a[2]=5, a[3]=0, a[4]=0,a[5]=6,a[6]=0
s[2]=3, s[3]=0, s[4]=0, s[5]=3,a[6]=0 , - a[2]数组的值最小,所以点 V2 加入 MST,更新数组:
a[2]=0, a[3]=0, a[4]=0,a[5]=3,a[6]=0
s[2]=0, s[3]=0, s[4]=0, s[5]=2,a[6]=0 , - a[5]数组的值最小,所以点 V5 加入 MST,更新数组:
a[2]=0, a[3]=0, a[4]=0,a[5]=0,a[6]=0
s[2]=0, s[3]=0, s[4]=0, s[5]=0,a[6]=0 ,
所有的点都加入到了MST中,结束。
//最小生成树算法,输出点路径以及权值
#include<iostream>
using namespace std;
const int maxn=1000;
const int maxnum=100000;
int arr[maxn][maxn];
int a[maxn];
int s[maxn];
int n,m;
void prims(int begin)
{
cout<<"路径:"<<begin<<" ";
int i,j,minum,minindex,sum=0;
for(i=1;i<=n;i++)
{
a[i]=arr[begin][i];
s[i]=begin;
}
s[begin]=0;
for(i=1;i<=n;i++)
{
if(i==begin)
continue;
minum=maxnum;
for(j=1;j<=n;j++)
{
if(a[j]<minum&&a[j])
{
minum=a[j];
minindex=j;
}
}
a[minindex]=0;
s[minindex]=0;
cout<<minindex<<" ";
sum+=minum;
for(j=1;j<=n;j++)
{
if(s[j]&&arr[minindex][j]<a[j])
{
s[j]=minindex;
a[j]=arr[minindex][j];
}
}
}
cout<<endl;
cout<<"权值:"<<sum;
}
int main()
{
int i,j,a,b,c,begin;
//初始化存放点的数组
cin>>n>>m;
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
arr[i][j]=maxnum;
while(m--)
{
cin>>a>>b>>c;
arr[a][b]=arr[b][a]=c;
}
cout<<"输入起始点:"<<endl;
cin>>begin;
prims(begin);
return 0;
}
测试数据:
6 10
1 2 6
1 3 1
1 4 5
2 3 5
2 5 3
3 4 5
3 5 6
3 6 4
4 6 2
5 6 6
3
结果:
2.kruskal算法 (使用并查集实现)
例子:
以图为例,kruskal算法的步骤:
- 取得权值最小的边 v1 到 v3
- 在不构成一个圈的情况下,取得权值次小的边:v4 到 v6
- 在不构成一个圈的情况下,取得下一条边:v2 到 v6
- 在不构成一个圈的情况下,取得下一条边:v3 到 v6
- 在不构成一个圈的情况下,取得下一条边:v2 到 v3
实现代码:
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=1000;
struct node
{
int start,end,value;
bool operator < (const node &n)const
{
return value<n.value;
}
}arr[maxn];
int n,m;//count:若有最小生成树,则为最小权值和
int fa[maxn];//
int find(int x)
{
return fa[x]==x?x:fa[x]=find(fa[x]);
}
void kruskal()
{
int i,ans,count;
node e;
ans=0;count=0;
//初始化fa数组
for( i=1;i<=n;i++)
fa[i]=i;
for(i=0;i<m;i++)
{
int x=find(arr[i].start);
int y=find(arr[i].end);
if(x!=y)
{
fa[x]=y;
ans++;
count+=arr[i].value;
}
}
if(ans<n-1)
{
cout<<"最小生成树不存在"<<endl;
}else{
cout<<count<<endl;
}
}
int main()
{
int i,a,b,c;
cin>>n>>m;//n:点数 m:边数
for(i=0;i<m;i++)
{
cin>>a>>b>>c;
arr[i].start=a;
arr[i].end=b;
arr[i].value=c;
}
sort(arr,arr+m);
kruskal();
return 0;
}
测试数据:
6 10
1 2 6
1 3 1
1 4 5
2 3 5
2 5 3
3 4 5
3 5 6
3 6 4
4 6 2
5 6 6
实验结果:
15