O(V^2)
结论
次小生成树可由最小生成树转换一条边得到
证明
T是某一棵最小生成树,T0是任一棵异于T的树,通过变换T0->T1->T2->…->Tn(T)变成最小生成树,所谓的变换是,每次把T_i中的某条边换成T中的一条边,而且树T_(i + 1)的权小于等于T_i的权。
具体操作是:
step1. 在T_i中任取一条不在T中的边u_V;
step2. 把边u_v去掉,就剩下两个连通分量A和B,在T中,必有唯一的边u’_v’连结A和B;
step3. 显然u’v’的权比u_v小(否则,u_v就应该在T中),把u’_v’替换u_v即得到树T(i + 1);
特别地:取Tn为任一棵次小生成树,T_(n - 1)也就是次小生成树且跟T差一条边,结论得证。
算法
只要充分利用上述结论,既得v^2的算法。具体如下:
step1. 先用Prim求出最小生成树T,在Prim的同时,用一个矩阵MAX[u][v]记录在T中连结任意两点u,v的唯一的路中权值最大的那条边的权值。(注意这里),这是很容易做到的,因为Prim是每次增加一个结点s,而已经标好了的结点集合为w,则w中所有的结点到s的路中最大权值的边就是当前加入的这条边,用时O(V^2);
step2.枚举所有不在T中的边u_v,加入边u_v替换权为MAX[u][v]的边,不断更新最小值,即次小生成树,用时O(E),故总用时O(V^2)。
代码C++
/*
* 求最小生成树时,用数组MAX[i][j]表示i到j的最大边权
* 求完后,直接枚举所有不在MST中的边,替换掉最大边权的边,更新答案
* 点的编号从0开始
*/
const int MAXN = 110;
const int INF = 0x3f3f3f3f;
bool vis[MAXN];
int lowc[MAXN];
int pre[MAXN];
int MAX[MAXN][MAXN];
bool used[MAXN][MAXN];
int Prim(int cost[][MAXN], int n)
{
int ans = 0;
memset(vis, false, sizeof(vis));
memset(MAX, 0, sizeof(MAX));
memset(used, false, sizeof(used));
vis[0] = true;
pre[0] = -1;
lowc[0] = 0;
for (int i = 1; i < n; i++)
{
lowc[i] = cost[0][i];
pre[i] = 0;
}
for (int i = 1; i < n; i++)
{
int minc = INF;
int p = -1;
for (int j = 0; j < n; j++)
{
if (!vis[j] && minc > lowc[j])
{
minc = lowc[j];
p = j;
}
}
if (minc == INF)
{
return -1;
}
ans += minc;
vis[p] = true;
used[p][pre[p]] = used[pre[p]][p] = true;
for (int j = 0; j < n; j++)
{
if (vis[j])
{
MAX[j][p] = MAX[p][j] = max(MAX[j][pre[p]], lowc[p]);
}
if (!vis[j] && lowc[j] > cost[p][j])
{
lowc[j] = cost[p][j];
pre[j] = p;
}
}
}
return ans;
}