最小生成树大家都会,但是次小生成树呢?生成树算法在计算机运用广泛啊!!!!
先来讲一下定义:
设 是连通的无向图, 是图 的一棵最小生成树。如果有另一棵树,满足不存在(找不到)树,,则称T1是图G的次小生成树。(就是长度第二小的树,但是非严格次小生成树的长度可以等于最小生成树的长度,严格起来就一定是长度之比最小生成树大的生成树)求解次小生成树的算法
结论1
次小生成树可由最小生成树换一条边得到.证明:
具体操作是:step 1. 在Ti中任取一条不在T中的边uv.
step 2. 把边uv去掉,就剩下两个连通分量A和B,
在T中,必有唯一的边u'v' 连结A和B.step 3. 显然u'v'的权比uv小 (否则,uv就应该在T中).
把u'v'替换uv即得树T(i+1).特别地:取T0为任一棵次小生成树,T(n-1) 也就是次小生成树且
跟T差一条边. 结论1得证.算法:
只要充分利用结论1, 即得V^2的算法. 具体如下:
- step 1. 先用prim求出最小生成树T.
在prim的同时,用一个矩阵max[u][v] 记录 在T中连结任意两点u,v的唯一的
路中权值最大的那条边的权值. (注意这里).
这是很容易做到的,因为prim是每次增加一个结点s, 而设已经标号了的结点
集合为W, 则W中所有的结点到s的路中的最大权值的边就是当前加入的这条边.
step 1 用时 O(V^2).- step 2. 枚举所有不在T中的边uv, 加入边uv则必然替换权为max[u][v]的边.
故总时间为O(V^2).
prim
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#define INF 0x3f3f3f3f
using namespace std;
int mp[110][110];
int vis[110];
int dis[110];
int path[110][110];//记录i,到j点的最大距离
int pre[110];//记录每个点的父节点
int use[110][110];//标记这条边是否用过(是否在最小生成树中)
int ans,n,m;
int prim(int cost[ ][110],int n)
{
memset(dis,INF,sizeof(dis));
memset(vis,0,sizeof(vis));
memset(path,0,sizeof(path));
memset(use,0,sizeof(use));
vis[1]=1;//从节点1开始
for(int i=1;i<=n;i++)
{
dis[i]=cost[1][i];
pre[i]=1;//开始的父节点都为1
}
int ans=0;
for(int i=2;i<=n;i++)
{
int pos=-1;
for(int j=1;j<=n;j++)
{
if(!vis[j]&&(pos==-1||dis[pos]>dis[j]))//寻找最小边
{
pos=j;
}
}
if(pos==-1)
{
break;
}
ans+=dis[pos];//加入
vis[pos]=1;
use[pre[pos]][pos]=use[pos][pre[pos]]=1;//标记这条边已经在最小生成树里面
for(int j=1;j<=n;j++)
{
if(vis[j]==1&&j!=pos)
{
path[pos][j]=path[j][pos]=max(dis[pos],path[j][pre[pos]]);//添加pos到各个已经在最小生成树的点集中的点的最大距离,
//之所以是dis[pos]与path[j][pre[pos]]比较,是以为dis[pos]表示的是pos与其父节点的距离,再者要想将pos加入点集,
//说明它的父节点早已经在最小生成树的点集中了,于是只要比较各个点到pos的父节点的最大值与pos到它父节点的值就可以了。因为path[][]记录的就是i点到j点的最大值
}
if(vis[j]==0&&cost[pos][j]<dis[j])//发现通过pos节点到达节点j的距离更小
{
dis[j]=cost[pos][j];
pre[j]=pos;//于是节点j更新父节点为pos
}
}
}
return ans;
}
int Subprim(int n)
{
int res=INF;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
if(i!=j&&use[i][j]==0)//挑选没有在最小生成树的边
{
res=min(res,ans-path[i][j]+mp[i][j]);//删除两节点的最大边,加上没有加入的边,
//加入的边或许没有连接,所以初始化mp为INF
}
}
}
if(res==INF)
{
return -1;
}
return res;
}
int main( )
{
int t,x,y,w;
cin>>t;
while(t--)
{
cin>>n>>m;
memset(mp,INF,sizeof(mp));
for(int i=1;i<=n;i++)
{
mp[i][i]=0;
}
for(int i=1;i<=m;i++)
{
cin>>x>>y>>w;
mp[x][y]=mp[y][x]=w;
}
ans=prim(mp,n);
int ans1=Subprim(n);
if(ans1==ans)
{
cout<<"Not Unique!"<<endl;
}
else
{
cout<<ans<<endl;
}
}
return 0;
}
Kruskal
#include<iostream>
#include<cstdio>
#include<algorithm>
#define INF 0x3f3f3f3f
using namespace std;
const int M=1e5;
int root[M];
struct node
{
int a,b,w;
} edge[M];
int n,m;
int a[M];
int res;
void init(int n)
{
for(int i=1; i<=n; i++)
{
root[i]=i;
}
}
int find(int x)
{
if(x==root[x])
{
return x;
}
else
{
return root[x]=find(root[x]);
}
}
int ouin(int a,int b)
{
int x=find(a);
int y=find(b);
if(x==y)
{
return 0;
}
else
{
root[x]=y;
return 1;
}
}
bool cmp(node a,node b)
{
return a.w<b.w;
}
int Kruskal_MinTree( )
{
int ans=0;
int cnt=0;
for(int i=0; i<m; i++)
{
if(ouin(edge[i].a,edge[i].b))
{
ans+=edge[i].w;
a[res++]=i;
cnt++;
}
if(cnt==n-1)
{
break;
}
}
if(cnt==n-1)
{
return ans;
}
return -1;
}
int Subsmall( )
{
int sum=INF;
for(int i=0; i<res; i++)
{
init(n);
int ans=0;
int cnt=0;
for(int j=0; j<m; j++)
{
if(j!=a[i])
{
if(ouin(edge[j].a,edge[j].b))
{
ans+=edge[j].w;
cnt++;
}
}
if(cnt==n-1)
{
break;
}
}
if(cnt==n-1)
{
sum=min(sum,ans);
}
}
if(sum==INF)
{
return -1;
}
return sum;
}
int main()
{
int t;
cin>>t;
while(t--)
{
res=0;
cin>>n>>m;
init(n);
for(int i=0; i<m; i++)
{
cin>>edge[i].a>>edge[i].b>>edge[i].w;
}
int ans=Kruskal_MinTree( );
int ans2=Subsmall( );
if(ans==ans2)
{
cout<<"Not Unique!"<<endl;
}
else
{
cout<<ans<<endl;
}
}
return 0;
}
根据定义,利用克鲁斯卡尔找出两点路径中最大值
#include<iostream>
#include<cstdio>
#include<vector>
#include<algorithm>
#include<cstring>
#define INF 0x3f3f3f3f
using namespace std;
const int M=110;
struct node
{
int a,b,w;
}edge[M*M];
int root[M];
int lenth[M][M];
vector<int>G[M];//存放点
int use[M*M];
int find(int x)
{
if(x==root[x])
{
return x;
}
else
{
return root[x]=find(root[x]);
}
}
int ouin(int a,int b)
{
if(a==b)
{
return 0;
}
else
{
return 1;
}
}
bool cmp(node a,node b)
{
return a.w<b.w;
}
void Kruskal(int n,int m)
{
memset(use,0,sizeof(use));
memset(lenth,0,sizeof(lenth));
for(int i=1;i<=n;i++)
{
G[i].clear( );
G[i].push_back(i);//各个点集中只有本身
root[i]=i;
}
sort(edge,edge+m,cmp);
int ans=0,cnt=0;
for(int i=0;i<m;i++)
{
int u=find(edge[i].a),v=find(edge[i].b);
if(ouin(u,v))
{
ans+=edge[i].w;
cnt++;
use[i]=1;
int lenu=G[u].size( );//点集1
int lenv=G[v].size( );//点集2
for(int j=0;j<lenu;j++)
{
for(int k=0;k<lenv;k++)
{
lenth[G[u][j]][G[v][k]]=lenth[G[v][k]][G[u][j]]=edge[i].w;//更新点集1中的各个点与点集2中各个的点的距离,
//因为edge[i].w从小到大排序,所以后面的一定比前面的大
}
}
root[u]=v;//合并点集父节点,u的父节点是v;
for(int j=0;j<lenu;j++)//合并两个点集
{
G[v].push_back(G[u][j]);//把子节点的点放到父节点的点集中
}
}
if(cnt==n-1)
{
break;
}
}
int res=INF;
for(int i=0;i<m;i++)
{
if(!use[i])
{
res=min(res,ans+edge[i].w-lenth[edge[i].a][edge[i].b]);
}
}
if(res>ans)
{
cout<<ans<<endl;
}
else
{
cout<<"Not Unique!"<<endl;
}
}
int main( )
{
int t,n,m;
cin>>t;
while(t--)
{
cin>>n>>m;
for(int i=0;i<m;i++)
{
cin>>edge[i].a>>edge[i].b>>edge[i].w;
}
Kruskal(n,m);
}
return 0;
}
网友评论