▶ 书中第六章部分程序,加上自己补充的代码,包括全局最小切分 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的更多相关文章

  1. 《算法》第六章部分程序 part 6

    ▶ 书中第六章部分程序,包括在加上自己补充的代码,包括二分图最大匹配(最小顶点覆盖)的交替路径算法和 HopcroftKarp 算法 ● 二分图最大匹配(最小顶点覆盖)的交替路径算法 package ...

  2. 《算法》第六章部分程序 part 5

    ▶ 书中第六章部分程序,包括在加上自己补充的代码,网络最大流 Ford - Fulkerson 算法,以及用到的流量边类和剩余流量网络类 ● 网络最大流 Ford - Fulkerson 算法 pac ...

  3. 《算法》第六章部分程序 part 8

    ▶ 书中第六章部分程序,加上自己补充的代码,包括单纯形法求解线性规划问题 ● 单纯形法求解线性规划问题 // 表上作业法,I 为单位阵,y 为对偶变量,z 为目标函数值 // n m 1 // ┌── ...

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

    ▶ 书中第六章部分程序,包括在加上自己补充的代码,利用后缀树查找最长重复子串.查找最大重复子串并输出其上下文(Key word in context,KWIC).求两字符串的最长公共子串 ● 利用后缀 ...

  5. 《算法》第六章部分程序 part 3

    ▶ 书中第六章部分程序,包括在加上自己补充的代码,后缀树的两种实现 ● 后缀树实现一 package package01; import java.util.Arrays; import edu.pr ...

  6. 《算法》第六章部分程序 part 2

    ▶ 书中第六章部分程序,包括在加上自己补充的代码,B-树 ● B-树 package package01; import edu.princeton.cs.algs4.StdOut; public c ...

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

    ▶ 书中第六章部分程序,包括在加上自己补充的代码,粒子碰撞系统及用到的粒子类 ● 粒子系统 package package01; import java.awt.Color; import edu.p ...

  8. 《算法》第一章部分程序 part 1

    ▶ 书中第一章部分程序,加上自己补充的代码,包括若干种二分搜索,寻找图上连通分量数的两种算法 ● 代码,二分搜索 package package01; import java.util.Arrays; ...

  9. 《算法》第二章部分程序 part 5

    ▶ 书中第二章部分程序,加上自己补充的代码,包括利用优先队列进行多路归并和堆排序 ● 利用优先队列进行多路归并 package package01; import edu.princeton.cs.a ...

随机推荐

  1. 【转】[Android] NDK独立编译——独立工具链

    转载地址:https://blog.csdn.net/suningning/article/details/74510125

  2. ubuntu14 16使用libusb过程中遇到的问题及解决方法

    从ubuntu16换到ubuntu14后安装libusb运行一直在libusb_bulk_transfer语句出现运行出现段错误,分别换了libusb1.0.0,1.0.9及1.0.21. 通过查阅链 ...

  3. pytest.1.快速开始

    From: http://www.testclass.net/pytest/quick_start/ 简介 pytest测试框架可以让我们很方便的编写测试用例,这些用例写起来虽然简单,但仍然可以规模化 ...

  4. 对poi-Excel导入的浅层理解

    本文即将对POI方式导入excel文件最核心的步骤予以说明,为的是简单,也是为了阐明文件导入的原理. 文件导入有一个很明显的线索: 1.首先是我们知道硬盘中的文件,即:文件对象File file 2. ...

  5. [蓝桥杯]ALGO-95.算法训练_2的次幂表示

    题目描述: 问题描述 任何一个正整数都可以用2进制表示,例如:137的2进制表示为10001001. 将这种2进制表示写成2的次幂的和的形式,令次幂高的排在前面,可得到如下表达式:=^+^+^ 现在约 ...

  6. AutoCAD的代替软件

    AutoCAD的代替软件Autocad2005以上版本都需要.net framework的支持,安装起来太麻烦,而且卡顿.以下几个软件可以代替Autocad,操作和插件基本都能兼容Autocad.1. ...

  7. 【C#】语音识别 - System.Speech

    一个有趣的东西,今后可能用得上. C#语音识别:在命名空间 System.Speech下SpeechSynthesizer可以将文字转换成语音 贴出代码: public partial class F ...

  8. 信息安全-加密:RSA 算法

    ylbtech-信息安全-加密:RSA 算法 RSA公开密钥密码体制.所谓的公开密钥密码体制就是使用不同的加密密钥与解密密钥,是一种“由已知加密密钥推导出解密密钥在计算上是不可行的”密码体制. 1.返 ...

  9. HDOJ 2004 成绩转换

    #include<cstdio> #include<iostream> using namespace std; int main() { int score; while ( ...

  10. IPSec

    一. +IPSec(IP Security)Internet 协议安全性 是IFTF制定的为保证在Internet上传送数据的安全保密性能的框架协议 +IPSec包括报文验证头协议AH(协议号51)和 ...