《算法》第六章部分程序 part 7

时间:2023-03-09 23:50:06
《算法》第六章部分程序 part 7

▶ 书中第六章部分程序,加上自己补充的代码,包括全局最小切分 Stoer-Wagner 算法,最小权值二分图匹配

● 全局最小切分 Stoer-Wagner 算法

 package package01;

 import edu.princeton.cs.algs4.In;
import edu.princeton.cs.algs4.StdOut;
import edu.princeton.cs.algs4.EdgeWeightedGraph;
import edu.princeton.cs.algs4.Edge;
import edu.princeton.cs.algs4.UF;
import edu.princeton.cs.algs4.IndexMaxPQ; public class class01
{
private static final double FLOATING_POINT_EPSILON = 1E-11;
private double weight = Double.POSITIVE_INFINITY; // 输出的最小割值
private boolean[] cut; // 顶点是否在割除集 T 中
private int V; private class CutPhase // 最小 s-t 割类(cut-of-the-phase)
{
private double weight;
private int s;
private int t; public CutPhase(double inputWeight, int inputS, int inputT)
{
weight = inputWeight;
s = inputS;
t = inputT;
}
} public class01(EdgeWeightedGraph G)
{
UF uf = new UF(G.V()); // 用类 union–find 来表示顶点的合并情况
boolean[] marked = new boolean[G.V()]; // 已合并的顶点集,初始化为空
cut = new boolean[G.V()]; // 割除集 T,初始化为空
CutPhase cp = new CutPhase(0.0, 0, 0); // 用于首次搜索的割,无意义
for (int v = G.V(); --v > 0; marked[cp.t] = true) // 遍历 V-1 次,每次标记被合并的顶点,以后不再遍历该点
{
cp = minCutPhase(G, marked, cp); // 更新最小割
if (cp.weight < weight) // 发现权值更小的割,更新全局割
{
weight = cp.weight;
for (int j = 0; j < G.V(); j++) // 在最新的图中,与 cp.t 相连的顶点都是 T 的元素
cut[j] = uf.connected(j, cp.t);
}
G = contractEdge(G, cp.s, cp.t); // 顶点 t 合并到顶点 s,更新图
uf.union(cp.s, cp.t); // 顶点 t 加入 T 中
}
} public double weight()
{
return weight;
} public boolean cut(int v)
{
return cut[v];
} private CutPhase minCutPhase(EdgeWeightedGraph G, boolean[] marked, CutPhase cp) // 计算 s - t 的最小割,称为 maximum adjacency (cardinality) search
{
IndexMaxPQ<Double> pq = new IndexMaxPQ<Double>(G.V()); // 用于挑选权值最大的点用于合并
pq.insert(cp.s, Double.POSITIVE_INFINITY); // 顶点 s 自己的权值为 +∞
for (int v = 0; v < G.V(); v++) // 其他顶点权值初始化为 0
{
if (v != cp.s && !marked[v])
pq.insert(v, 0.0);
}
for (; !pq.isEmpty();)
{
cp.s = cp.t; // 记录最后取出的两个顶点,每次往前挪一格
cp.t = pq.delMax(); // 取走权值最大的顶点 v
for (Edge e : G.adj(cp.t)) // 只要 cp.t 的邻居还在 pq 中没有被取走,就更新邻居的权值
{
int w = e.other(cp.t);
if (pq.contains(w))
pq.increaseKey(w, pq.keyOf(w) + e.weight()); // 顶点 w 的权值自增边 e 的权值
}
}
cp.weight = 0.0; // 计算最后加入 T 的顶点的权值,即为最小割值
for (Edge e : G.adj(cp.t))
cp.weight += e.weight();
return cp;
} private EdgeWeightedGraph contractEdge(EdgeWeightedGraph G, int s, int t) // 把顶点 t 合并到顶点 s,更新其他边的权值
{
EdgeWeightedGraph H = new EdgeWeightedGraph(G.V()); // 合并后的图,顶点与原来相同
for (int v = 0; v < G.V(); v++)
{
for (Edge e : G.adj(v)) // 依顶点序遍历所有边
{
int w = e.other(v);
if (v == s && w == t || v == t && w == s) // 边 s-t 自身不要
continue;
if (v < w) // 只考虑后向边,滤掉重复
{
if (w == t) // 远端顶点 w 是被合并的 t,边 v - w(t) 替换成边 v - s
H.addEdge(new Edge(v, s, e.weight()));
else if (v == t) // 近端顶点 v 是被合并的 t,边 v(t) - w 替换成边 s - w
H.addEdge(new Edge(w, s, e.weight()));
else // 边 v - w 与 s 或 t 无关,原样放进 H
H.addEdge(new Edge(v, w, e.weight()));
}
}
}
return H;
} public static void main(String[] args)
{
In in = new In(args[0]);
EdgeWeightedGraph G = new EdgeWeightedGraph(in);
class01 mc = new class01(G);
StdOut.print("Min cut: ");
for (int v = 0; v < G.V(); v++)
{
if (mc.cut(v))
StdOut.print(v + " ");
}
StdOut.println("\nMin cut weight = " + mc.weight());
}
}

● 最小权值二分图匹配

 package package01;

 import edu.princeton.cs.algs4.StdOut;
import edu.princeton.cs.algs4.StdRandom;
import edu.princeton.cs.algs4.EdgeWeightedDigraph;
import edu.princeton.cs.algs4.DirectedEdge;
import edu.princeton.cs.algs4.DijkstraSP; public class class01
{
private static final double FLOATING_POINT_EPSILON = 1E-14;
private int n; // 二分图一侧的顶点数,总顶点数2 * n
private double[][] weight; // 边权矩阵
private double minWeight; // 最小权值
private double[] px; // 每一行的对偶变量
private double[] py; // 每一列的对偶变量
private int[] xy; // 正向标记,xy[i] = j 表示 i-j 匹配
private int[] yx; // 反向标记,yx[j] = i 表示 i-j 匹配 public class01(double[][] inputWeight)
{
n = inputWeight.length;
weight = new double[n][n];
minWeight = Double.MAX_VALUE;
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++)
{
if (Double.isNaN(weight[i][j]))
throw new IllegalArgumentException("weight " + i + "-" + j + " is NaN");
weight[i][j] = inputWeight[i][j];
minWeight = Math.min(minWeight, weight[i][j]);
}
}
px = new double[n];
py = new double[n];
xy = new int[n];
yx = new int[n];
for (int i = 0; i < n; i++)
xy[i] = yx[i] = -1; for (int k = 0; k < n; k++) // 调整 n 次,每次添加一条边
{
assert isDualFeasibleAndComplementarySlack();
augment();
}
assert isDualFeasibleAndComplementarySlack() && isPerfectMatching(); // 检查结果正确性,要求互补松弛,完美匹配
} private void augment() // 寻找最小权值路径并更新
{
EdgeWeightedDigraph G = new EdgeWeightedDigraph(2 * n + 2);
int s = 2 * n, t = 2 * n + 1;
for (int i = 0; i < n; i++) // 所有未匹配的顶点连到 s 和 t 上,s 侧权值为 0,t 侧有权值
{
if (xy[i] == -1)
G.addEdge(new DirectedEdge(s, i, 0.0));
}
for (int j = 0; j < n; j++)
{
if (yx[j] == -1)
G.addEdge(new DirectedEdge(n + j, t, py[j]));
}
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++) // 已匹配的顶点对添加权值为 0 的反向边,未匹配的顶点对添加修正权值的正向边
{
if (xy[i] == j)
G.addEdge(new DirectedEdge(n + j, i, 0.0));
else
G.addEdge(new DirectedEdge(i, n + j, reducedCost(i, j)));
}
}
DijkstraSP spt = new DijkstraSP(G, s); // 计算从 s 到各顶点最短距离
for (DirectedEdge e : spt.pathTo(t)) // 研究从 s 到 t 的边
{
int i = e.from(), j = e.to() - n;
if (i < n) // 去掉与顶点 s 和 t 有关的部分和反向边
{
xy[i] = j;
yx[j] = i;
}
}
for (int i = 0; i < n; i++) // 垫起各顶点的距离
{
px[i] += spt.distTo(i);
py[i] += spt.distTo(n + i);
}
} private double reducedCost(int i, int j) // 顶点对之间的修正权值,用原始权值减去全局最小权值,再加上起点、终点的距离差
{
double reducedCost = (weight[i][j] - minWeight) + px[i] - py[j];
assert reducedCost >= 0.0;
if (Math.abs(reducedCost) <= FLOATING_POINT_EPSILON * (Math.abs(weight[i][j]) + Math.abs(px[i]) + Math.abs(py[j])))
return 0.0;
return reducedCost;
} public double dualRow(int i)
{
return px[i];
} public double dualCol(int j)
{
return py[j];
} public int sol(int i)
{
return xy[i];
} public double weight() // 输出解的权值总和
{
double total = 0.0;
for (int i = 0; i < n; i++)
{
if (xy[i] != -1)
total += weight[i][xy[i]];
}
return total;
} private boolean isDualFeasibleAndComplementarySlack() // 检查对偶可行性和互补松弛性
{
for (int i = 0; i < n; i++) // 检查所有边的修正权值不小于 0
{
for (int j = 0; j < n; j++)
{
if (reducedCost(i, j) < 0)
{
StdOut.println("Dual variables are not feasible");
return false;
}
}
}
for (int i = 0; i < n; i++) // 检查原变量和对偶变量的互补松弛性,即已匹配的顶点对修正权值为 0,未匹配的非 0
{
if (xy[i] != -1 && reducedCost(i, xy[i]) != 0)
{
StdOut.println("Primal and dual variables are not complementary slack");
return false;
}
}
return true;
} private boolean isPerfectMatching()// 检查是否为完美匹配
{
boolean[] perm = new boolean[n];
for (int i = 0; i < n; i++)
{
if (perm[xy[i]])// ?perm[-1] 初始化为 true?
{
StdOut.println("Not a perfect matching");
return false;
}
perm[xy[i]] = true;
}
for (int j = 0; j < n; j++)// 检查 xy[] 和 yx[] 对称性
{
if (xy[yx[j]] != j)
{
StdOut.println("xy[] and yx[] are not inverses");
return false;
}
}
for (int i = 0; i < n; i++)
{
if (yx[xy[i]] != i)
{
StdOut.println("xy[] and yx[] are not inverses");
return false;
}
}
return true;
} public static void main(String[] args)
{
int n = Integer.parseInt(args[0]);
double[][] weight = new double[n][n];
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++)
weight[i][j] = StdRandom.uniform(900) + 100; // 权值 100 ~ 999
} class01 assignment = new class01(weight);
StdOut.printf("weight = %.0f\n\n", assignment.weight()); if (n >= 20)
return;
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++)
StdOut.printf("%c%.0f ", (j == assignment.sol(i)) ? '*' : ' ', weight[i][j]);
StdOut.println();
}
}
}