《算法》第六章部分程序 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();
}
}
}
《算法》第六章部分程序 part 7的更多相关文章
- 《算法》第六章部分程序 part 6
▶ 书中第六章部分程序,包括在加上自己补充的代码,包括二分图最大匹配(最小顶点覆盖)的交替路径算法和 HopcroftKarp 算法 ● 二分图最大匹配(最小顶点覆盖)的交替路径算法 package ...
- 《算法》第六章部分程序 part 5
▶ 书中第六章部分程序,包括在加上自己补充的代码,网络最大流 Ford - Fulkerson 算法,以及用到的流量边类和剩余流量网络类 ● 网络最大流 Ford - Fulkerson 算法 pac ...
- 《算法》第六章部分程序 part 8
▶ 书中第六章部分程序,加上自己补充的代码,包括单纯形法求解线性规划问题 ● 单纯形法求解线性规划问题 // 表上作业法,I 为单位阵,y 为对偶变量,z 为目标函数值 // n m 1 // ┌── ...
- 《算法》第六章部分程序 part 4
▶ 书中第六章部分程序,包括在加上自己补充的代码,利用后缀树查找最长重复子串.查找最大重复子串并输出其上下文(Key word in context,KWIC).求两字符串的最长公共子串 ● 利用后缀 ...
- 《算法》第六章部分程序 part 3
▶ 书中第六章部分程序,包括在加上自己补充的代码,后缀树的两种实现 ● 后缀树实现一 package package01; import java.util.Arrays; import edu.pr ...
- 《算法》第六章部分程序 part 2
▶ 书中第六章部分程序,包括在加上自己补充的代码,B-树 ● B-树 package package01; import edu.princeton.cs.algs4.StdOut; public c ...
- 《算法》第六章部分程序 part 1
▶ 书中第六章部分程序,包括在加上自己补充的代码,粒子碰撞系统及用到的粒子类 ● 粒子系统 package package01; import java.awt.Color; import edu.p ...
- 《算法》第一章部分程序 part 1
▶ 书中第一章部分程序,加上自己补充的代码,包括若干种二分搜索,寻找图上连通分量数的两种算法 ● 代码,二分搜索 package package01; import java.util.Arrays; ...
- 《算法》第二章部分程序 part 5
▶ 书中第二章部分程序,加上自己补充的代码,包括利用优先队列进行多路归并和堆排序 ● 利用优先队列进行多路归并 package package01; import edu.princeton.cs.a ...
随机推荐
- 关于PHP程序员技术职业生涯规划 转自 韩天锋
转自 http://rango.swoole.com/ 看到很多PHP程序员职业规划的文章,都是直接上来就提Linux.PHP.MySQL.Nginx.Redis.Memcache.jQuery这些, ...
- <亲测>.NET Core项目在Linux上使用QRCoder时出错"Unable to load DLL 'gdiplus'"
Centos 7 解决方案如下: yum install libgdiplus-devel
- yarn和npm命令对比
- ALGO-9_蓝桥杯_算法训练_摆动序列(DP)
问题描述 如果一个序列满足下面的性质,我们就将它称为摆动序列: . 序列中的所有数都是不大于k的正整数: . 序列中至少有两个数. . 序列中的数两两不相等: . 如果第i – 1个数比第i – 2个 ...
- 【springBoot】之配置文件application
springboot使用一个全局的配置文件application.properties或者是application.yml,放在在src/main/recesources下或者在类路径下的/confi ...
- bzoj5020: [THUWC 2017]在美妙的数学王国中畅游
Description 数学王国中,每个人的智商可以用一个属于 [0,1]的实数表示.数学王国中有 n 个城市,编号从 0 到 n−1 ,这些城市由若干座魔法桥连接.每个城市的中心都有一个魔法球,每个 ...
- windows10如何查看wifi密码
1.首先,在你的电脑的右下角的WiFi的图标,右击它,选择"网络和internet设置"或者选择打开设置 :如下图 点击"更改适配器选项" 选择 WLAN选项, ...
- [UE4]自定义函数,快速增加输入参数的一种方法
- python中文件操作
打印进度条
- aspnet-api-versioning
https://github.com/Microsoft/aspnet-api-versioning