最小生成树和次小生成树定义:
生成树:图G的生成树包含原图中的全部顶点n,和n-1条边.
最小生成树就是所有生成树中边权和最小的.
2 那么如何求最小生成树呢?
介绍两种基于贪心的算法:
1.prim算法
设图G =(V,E),其生成树的顶点集合为U。
①、把v0放入U。
②、在所有u∈U,v∈V-U的边(u,v)∈E中找一条最小权值的边,加入生成树。
③、把②找到的边的v加入U集合。如果U集合已有n个元素,则结束,否则继续执行②。
2.kruskal算法
首先将所有边按边权排序,然后按照边权从小到大依次处理.这里要用到并查集的思想,假设已有点集U,现在正在处理边i->j,如果i,j已在
U中则处理下一条边,否则将i,j加入并查集中.继续处理下一条边,直到有n-1条边为止.
3 次小生成树
次小生成树可由最小生成树换一条边得到
算法:
1)先用prim求出最小生成树T,在prim的同时,用一个矩阵max[u][v]记录在树中连接u-v的路径中权值最大的边.
2)枚举所有不在T中的边u-v,加入边u-v,删除权值为max[u][v]的边,不断枚举找到次小生成树.
POJ1679 The Unique MST
题意:给定图,让求它的最小生成树是否唯一。如果唯一的话输出最小生成树的权值和,否则输出Not Unique!
思路:直接求次小生成树就行。
次小生成树的模板:
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int INF=0x3f3f3f3f;
int g[110][110],dist[110],mmax[110][110];
int pre[110];
bool mark[110];
bool connect[110][110];
int mst,mint;
int n,m;
int prim()
{
int res=0,fa,p,min,i,j;
memset(mmax,0,sizeof(mmax));
for(i=1;i<=n;i++)
{
dist[i]=g[1][i];
pre[i]=1;
mark[i]=false;
}
dist[1]=0;
mark[1]=true;
for(i=1;i<n;i++)
{
p=-1;min=INF;
for(j=1;j<=n;j++)
{
if(!mark[j]&&dist[j]<min)
{
p=j;
min=dist[j];
}
}
if(p==-1) return res;
mark[p]=true;
res+=dist[p];
fa=pre[p];
connect[fa][p]=false;
connect[p][fa]=false;
mmax[fa][p]=min;
for(j=1;j<=n;j++)
mmax[j][p]=(mmax[fa][p]>mmax[j][fa])?mmax[fa][p]:mmax[j][fa];
for(j=1;j<=n;j++)
{
if(!mark[j]&&dist[j]>g[p][j])
{
dist[j]=g[p][j];
pre[j]=p;
}
}
}
return res;
}
int main()
{
int tc;
//freopen("1.txt","r",stdin);
scanf("%d",&tc);
while(tc--)
{
scanf("%d %d",&n,&m);
memset(g,INF,sizeof(g));
memset(connect,false,sizeof(connect));
while(m--)
{
int u,v,c;
scanf("%d %d %d",&u,&v,&c);
g[u][v]=c;
g[v][u]=c;
connect[u][v]=true;
connect[v][u]=true;
}
mst=prim();
int i,j;
bool flag=false;
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
{
if(connect[i][j]==false||g[i][j]==INF)
continue;
if(g[i][j]==mmax[i][j])
{
flag=true;
break;
}
}
if(flag)
printf("Not Unique!\n");
else
printf("%d\n",mst);
}
return 0;
}
2、Kruskal
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
using namespace std;
const int MAXN = 1010;
const int MAXM = 100010;
int father[MAXN];
int find(int x)
{
if(x != father[x])
father[x] = find(father[x]);
return father[x];
}
struct Node
{
int from;
int to;
int w;
bool vis;
};
Node Edges[MAXM];//存储边信息
bool cmp(Node a, Node b)
{
return a.w < b.w;
}
//链式前向星
struct Node1
{
int to;
int next;
};
Node1 Vertex[MAXN];//边数组,表示结点连向的边
int N,M;
int head[MAXN]; //邻接表头结点位置
int End[MAXN]; //邻接表尾结点位置,方便合并
int Len[MAXN][MAXN];//图中两点之间在最小生成树上路径最长的边
void Kruskal()
{
int x,y,k = 0;
int ans = 0;
//初始化邻接表,每个节点初始的时候添加一条指向自己的边,表示结点i各自为一个集合
memset(head,-1,sizeof(head));
memset(End,-1,sizeof(End));
for(int i = 1; i <= N; i++)
{
Vertex[i].to = i;
Vertex[i].next = head[i];
End[i] = i;
head[i] = i;
}
sort(Edges,Edges+M,cmp);//边按权值排序
for(int i = 0; i < M; i++)
{
if(k == N-1)
break;
if(Edges[i].w < 0)
continue;
x = find(Edges[i].from);
y = find(Edges[i].to);
if(x != y)
{
//遍历两个节点所在的集合
for(int w = head[x]; w != -1; w = Vertex[w].next)
{
for(int v = head[y]; v != -1; v = Vertex[v].next)
{
Len[Vertex[w].to][Vertex[v].to] = Len[Vertex[v].to][Vertex[w].to] = Edges[i].w;
//当前加入的边一定是加(x,y)边成环后删去的除(x,y)外长度最大的边
}
}
Vertex[End[y]].next = head[x];//合并两个邻接表,表示两点已连边连在一个集合中,最终连成一个最小生成树
head[x] = head[y];
End[y] = End[x];
father[y] = x;
k++;
Edges[i].vis = true;
}
}
}
int main()
{
int T,x,y,w;
scanf("%d",&T);
while(T--)
{
scanf("%d%d",&N,&M);
for(int i = 1; i <= N; i++)
father[i] = i;
memset(Len,0x7f,sizeof(Len));
for(int i = 0; i < M; i++)
{
scanf("%d%d%d",&x,&y,&w);
Edges[i].from = x;
Edges[i].to = y;
Edges[i].w = w;
Edges[i].vis = false;
}
int MST,SecMST;
Kruskal();
MST = 0;//最小生成树长度
for(int i = 0; i < M; i++)
{
if(Edges[i].vis)
MST += Edges[i].w;
}
SecMST = 0xfffff0;
for(int i = 0; i < M; i++)
{
if(!Edges[i].vis)//加边,并删去最小生成树上的边
SecMST = min(SecMST,MST+Edges[i].w - Len[Edges[i].from][Edges[i].to]);
}
if(SecMST == MST)
printf("Not Unique!\n");
else
printf("%d\n",MST);
}
return 0;
}