定义:
设G = (V, E)是连通的无向图,T是图G的一个最小生成树.如果有另外一棵树T1,T1 ≠ T,满足不存在树T',T' ≠ T,w(T') < w(T1),则称T1是图G的次小生成树.
算法
最简单的是,设T是G的最小生成树,依次枚举T的边并去掉,再求最小生成树,所得到的这些值的最小值就是次小生成树,由于最小生成树有N-1条边,这种方法就相当于运行了N次最小生成树的算法,算法的时间复杂度比较高.大约是N * M的数量级.
当然对于次小生成树,我们也可以用prim,kurskal算法求解。这两种算法的思路都是相同的,首先求出最小生成树,我们枚举每条不在最小生成树上的边,并把这条边放到最小生成树上面,然后就一定会形成环,那么我们在这条环路中取出一条最长的路(除了新加入的那一条边)。最终我们得到的权值就是次小生成树的权值。
prim
我们需要一个数组maxd来记录 i 到 j 的最大距离。(如果加了i-j这条边会形成环,那么这个环上权值最大的就是j到pre[minj]或者minj到pre[minj]的边其中一个)
pre[j]表示 j 由pre[j] 更新而来
我们还需要数组:connect[i][j]表示最小生成树中这条边有没有被用到,剩下的就是我们要去模拟算法解释里所说的删边以及添边的操作了
代码如下:
package 图论;
import java.util.Arrays;
import java.util.Scanner;
public class 次小生成树_prim {
static int maxn = 1000, inf = (int) 1e9;
static int[][] map = new int[maxn][maxn];
static int[][] maxd = new int[maxn][maxn];// 二维数组maxd[i][j]表示最小生成树中i点到j点的最远距离
static int[] dis = new int[maxn];
static int[] pre = new int[maxn];
static boolean[] vis = new boolean[maxn];
static boolean[][] connect = new boolean[maxn][maxn];// 是否加入最小生成树
static void init(int n) {
for (int i = 0; i <= n; i++) {
for (int j = 0; j <= n; j++) {
if (i == j)
map[i][j] = 0;
else {
map[i][j] = inf;
}
}
}
}
static int prim(int n) {
for (int i = 1; i <= n; i++) {
dis[i] = map[1][i];// 开始最短距离都是1-i的距离
pre[i] = 1;// 首先父亲节点都是根节点
}
vis[1] = true;
dis[1] = 0;
int res = 0;
for (int i = 1; i < n; i++) {
int mind = inf, minj = -1;
for (int j = 1; j <= n; j++) {
if (!vis[j] && mind > dis[j]) {
minj = j;
mind = dis[j];
}
}
if (minj == -1)
return res;
vis[minj] = true;
connect[minj][pre[minj]] = true;// 这条边已经在最小生成树中,后面我们就不能添加这条边了
connect[pre[minj]][minj] = true;
res += mind;
maxd[minj][pre[minj]] = maxd[pre[minj]][minj] = mind;
for (int j = 1; j <= n; j++) {
if (!vis[j] && map[minj][j] < dis[j]) {
dis[j] = map[minj][j];
pre[j] = minj;// j点由minj更新而来
}
if (vis[j] && j != minj) {// 只是更新我们已经遍历过来的节点
// 如果加了i-j这条边会形成环,那么这个环上权值最大的就是j到pre[minj]或者minj到pre[minj]的边其中一个
maxd[minj][j] = maxd[j][minj] = Math.max(maxd[j][pre[minj]], dis[minj]);
}
}
}
return res;// 最小生成树权值之和
}
static void prim2(int n) {
int min_ans = prim(n);
System.out.println("min_ans " + min_ans);
int ans = inf;
// 枚举最小生成树之外的边
for (int i = 1; i <= n; i++) {
for (int j = i + 1; j <= n; j++) {
// i-j有边且不在最小生成树里
if (map[i][j] != inf && !connect[i][j]) {
ans = Math.min(ans, min_ans + map[i][j] - maxd[i][j]);
}
}
}
if (ans == inf)
System.out.println("无次小生成树");
else
System.out.println("second_ans " + ans);
}
public static void main(String[] args) {
// TODO Auto-generated method stub
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int m = sc.nextInt();
init(n);
for (int i = 0; i < m; i++) {
int u = sc.nextInt();
int v = sc.nextInt();
int w = sc.nextInt();
map[u][v] = map[v][u] = w;
}
prim2(n);
}
}