对于图,其生成树中的边也带权,将生成树各边的权值总和称为生成树的权,并将权值最小的生成树称为最小生成树(Minimun Spanning Tree),简称为MST。有两种非常典型的算法:Prim算法和kruskal算法,这两种算法都采用了贪心策略。
Prim算法的基本思想是:
(1) 在图G=(V,E)(V表示顶点,E表示边)中,从集合V中任取一个顶点(例如取顶点v0)放入集合A中,这时A={v0},集合T(E)为空。
(2) 遍搜A中所有vi,使从vi出发在V中寻找与vi相邻(且不在A中)权值最小的边的另一顶点vi+1,取vi+1最小值v1加入A。即A={v0,v1},同时将该边加入集合T(E)中。
(3) 重复(2),直到A = V为止。
(1) 在图G=(V,E)(V表示顶点,E表示边)中,从集合V中任取一个顶点(例如取顶点v0)放入集合A中,这时A={v0},集合T(E)为空。
(2) 遍搜A中所有vi,使从vi出发在V中寻找与vi相邻(且不在A中)权值最小的边的另一顶点vi+1,取vi+1最小值v1加入A。即A={v0,v1},同时将该边加入集合T(E)中。
(3) 重复(2),直到A = V为止。
这时T(E)中有n-1条边,T=(A,T(E))就是一棵最小生成树。
在本例中,数组origin存放原始数据,max_distance存放矩阵中的最大值,
result
存放最小生成树的最大边,opt存放节点和最小生成树之间的最小距
离, flag判断是否已经加入到最小生成树中,首先将1号顶点加入最小生成树
中,flag[1]为true,其他为false,opt[i]的值为origin[1][i]的值,然后选
择不在最小生成树中的最小边i,然后加入到最小生成树中,另外更新
opt[i],flag[i]。如此反复,直到取到v-1条边为止。
Prim算法AC代码:
#include <iostream>
using namespace std;
int A[505],vis[505]; //将节点加入A中
int w[505][505]; //储存路径(邻接矩阵)
int main()
{
int n, dis ,N;
int min, min_v;
int i,j,u,v;
cin>>N;
while(N--){
while(cin >>n) {
dis = 0; //初始化总距离为0
memset(A,0,sizeof(A));
memset(vis,0,sizeof(vis));
for(int i = 0; i < n; i++)
for( int j = 0; j < n; j++)
cin >> w[i][j];
A[0] = 0; //A集合
vis[0] = 1; //标记数组
for( i = 1; i < n; i++) { //执行n-1次,所有结点才会加入A树
min = 999999999;
for(u = 0; u < i; u++) //要遍搜A树中每个结点A[u]
for(v = 0; v < n; v++)
if( !vis[v] && w[(A[u])][v] && w[(A[u])][v] < min) { //找到A[u]到剩余点v的最小的非零距
min = w[(A[u])][v];
min_v = v;
}
A[u] = min_v; //储存结点路径(建立树),每次执行完循环后u=i,扩展的新结点A[u]为min_v
vis[min_v] = 1;
if(dis<min) dis=min;
}
cout << dis << endl;
}
}
return 0;
}
坑爹啊 AC time 是1000ms ,真是没有浪费一点啊~~~~太悬了
下面是Kruscal算法~~:(766ms AC)
#include<iostream>
#include<algorithm>
using namespace std;
int u[125000],v[125000],w[125000],r[125000],p[125000],n;
int cmp(const int i,const int j) { return w[i]<w[j];}
int find(int x) { return p[x]==x ? x : p[x]=find(p[x]);}
int kruskal()
{
int ans=0,i;
for(i=0; i<n; i++) p[i]=i;
for(i=0; i<n; i++) r[i]=i;
sort(r,r+n,cmp);
for(i=0;i<n;i++)
{
int e=r[i];int x=find(u[e]); int y=find(v[e]);
if(x!=y) { if(w[e]>ans)ans=w[e];if(x>y)p[y]=x; else p[x]=y;}
}
return ans;
}
int main()
{
int k,l,N;
cin>>N;
while(N--){
cin>>n;
k=0;
for(int i=0; i<n;i++)
for(int j=0; j<n;j++){
cin>>l;
if(j>i) {w[k]=l,u[k]=i,v[k]=j;k++;}
}
n=k;
int ans=kruskal();
cout << ans << endl;
}
return 0;
}
第二种Prim算法(750ms AC)
#include<iostream>
using namespace std;
#define INF 99999999
#define N 505
int w[N][N],losscost[N];
int main()
{
int m,n,i,j,k;
cin>>m;
while(m--){
cin>>n;
for(i=0;i<n;i++)
for(j=0;j<n;j++){
cin>>w[i][j];
if(i==j) w[i][j]=-1;
}
int ans=0;
for(i=0;i<n;i++)
losscost[i]=w[0][i];
for(i=1;i<n;i++){
int min=INF;
for(j=0;j<n;j++)
if(losscost[j]>=0 && losscost[j]<min){
min=losscost[j];
k=j;
}
if(ans<min) ans=min;
for(j=0;j<n;j++)
if(losscost[j]>w[k][j])
losscost[j]=w[k][j];
}
cout<<ans<<endl;
}
return 0;
}
还有Kruscal第二个版本是别人做的~~:(这个最省时,300ms就过了,不知道为什么这么快,只用了1/3的时间)
#include <stdio.h>
#include <stdlib.h>
// 边类型
typedef struct Edge{
int x;
int y;
int weigh;
}Edge;
//最多可能的边
Edge edge[251001];
int parent[501];
//用于qsort()的比较函数
int compare(const void*a,const void *b)
{
return ((Edge*)a)->weigh - ((Edge*)b)->weigh;
}
// 找父节点并压缩路径
int FindParent(int x)
{
if( parent[x] != x)
parent[x] = FindParent(parent[x]);
else
return x;
return parent[x];
}
// 合并集合
void MergeSet(int x,int y)
{
x = FindParent(x);
y = FindParent(y);
if( x > y)
parent[y] = x;
else
parent[x] = y;
}
int main()
{
int m,n;
int count = 0;
scanf("%d",&n);
for( int i = 0; i < n; i++) {
scanf("%d",&m);
for( int j = 0; j < m; j++)
parent[j] = j; //初始化parent
for( int j = 0; j < m; j++)
for( int k = 0; k < m; k++) {
scanf("%d",&edge[count].weigh);
edge[count].x = j;
edge[count++].y = k;
}
qsort(edge,m * m,sizeof(Edge),compare);
count = 1;
int max;
// 忽略那些为0的节点,有m个,然后从小到大去边
for( int j = m; j < (m * m) && count < m; j++)
if(FindParent(edge[j].x) != FindParent(edge[j].y)) {
count ++;
MergeSet(edge[j].x,edge[j].y);
max = edge[j].weigh;
}
printf("%d\n",max);
}
return 0;
}
相似类型的题目还有:http://acm.pku.edu.cn/JudgeOnline/problem?id=2395 (注意有重边,所以每次输入一条边都要判断它是否是这两个点的最小边。。)