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

● 二分图最大匹配(最小顶点覆盖)的交替路径算法

 package package01;

 import edu.princeton.cs.algs4.StdOut;
import edu.princeton.cs.algs4.BipartiteX;
import edu.princeton.cs.algs4.Graph;
import edu.princeton.cs.algs4.GraphGenerator;
import edu.princeton.cs.algs4.Queue; public class class01
{
private final int V;
private BipartiteX bipartition;
private int cardinality; // 匹配集的顶点数
private int[] mate; // 每个顶点的配对顶点,-1 表示未配对
private boolean[] inMinVertexCover; // 顶点是否处于最小覆盖中
private boolean[] marked; // 标记已经搜索过的顶点
private int[] edgeTo; // 搜索序列中的父节点标号 public class01(Graph G)
{
V = G.V();
mate = new int[V];
for (int v = 0; v < V; v++)
mate[v] = -1;
for (; hasAugmentingPath(G);)
{
int t = -1;
for (int v = 0; v < G.V(); v++) // 寻找增广路径的一个端点,它不在 mate 表中,但在搜索序列中
{
if (!isMatched(v) && edgeTo[v] != -1)
{
t = v;
break;
}
}
for (int v = t; v != -1; v = edgeTo[edgeTo[v]]) // 增广路经中每两个相邻顶点做成匹配
{
int w = edgeTo[v];
mate[v] = w;
mate[w] = v;
}
cardinality++; // 全部调整完成,匹配集中顶点数量增加 1
}
inMinVertexCover = new boolean[V]; // 更新 inMinVertexCover[],加入最小覆盖的条件是未匹配的黑点和已匹配的红点
for (int v = 0; v < V; v++)
{
if (bipartition.color(v) && !marked[v] || !bipartition.color(v) && marked[v])
inMinVertexCover[v] = true;
}
assert certifySolution(G);
} private boolean hasAugmentingPath(Graph G) // 是否存在可调整路径,算法是寻找图中最短增广路经,注意函数对 mate[] 只读
// 交替路径:沿着交替路径前进,每条边依次属于、不属于匹配集合
// 增广路经:起点和终点都属于未匹配集的交替路径,说明可以通过对换路径上所有边(匹配 <-> 未匹配)来增加匹配集的顶点数
{
marked = new boolean[V];
edgeTo = new int[V];
for (int v = 0; v < V; v++)
edgeTo[v] = -1;
Queue<Integer> queue = new Queue<Integer>();
for (int v = 0; v < V; v++)
{
if (bipartition.color(v) && !isMatched(v)) // 所有未配对的黑色点(true)加入搜索队列,作为初始点
{
queue.enqueue(v);
marked[v] = true;
}
}
for (; !queue.isEmpty();)
{
int v = queue.dequeue();
for (int w : G.adj(v))
{
if (isResidualGraphEdge(v, w) && !marked[w])// w 是一个新顶点,v - w 是未配对黑-红边且或是已配对红-黑边
{
edgeTo[w] = v; // 搜索序列中,把 w 当做 v 的后继
marked[w] = true;
if (!isMatched(w)) // w 是未经配对的顶点,说明找到了增广路经,否则 w 也加入搜索队列
return true;
queue.enqueue(w);
}
}
}
return false;
} private boolean isResidualGraphEdge(int v, int w) // 要么 v 黑且 v - w 未配对,要么 v 红且 v - w 已配对
{
return (mate[v] != w) && bipartition.color(v) || (mate[v] == w) && !bipartition.color(v);
} public int mate(int v)
{
return mate[v];
} public boolean isMatched(int v) // mate[v] >= 0 返回 1,mate[v] == -1 返回 0
{
return mate[v] != -1;
} public int size()
{
return cardinality;
} public boolean isPerfect() // 是否为完美匹配
{
return cardinality * 2 == V;
} public boolean inMinVertexCover(int v) // 顶点是否在最小覆盖中
{
return inMinVertexCover[v];
} private boolean certifySolution(Graph G) // 检查结果正确性
{
for (int v = 0; v < V; v++) // 检查 mate[] 对称性
{
if (mate(v) == -1)
continue;
if (mate(mate(v)) != v)
return false;
}
int matchedVertices = 0; // 检查 cardinality 与 mate[] 一致性
for (int v = 0; v < V; v++)
{
if (mate(v) != -1)
matchedVertices++;
}
if (2 * size() != matchedVertices)
return false;
int sizeOfMinVertexCover = 0; // 检查 cardinality 与 inMinVertexCover[] 一致性
for (int v = 0; v < V; v++)
{
if (inMinVertexCover(v))
sizeOfMinVertexCover++;
}
if (size() != sizeOfMinVertexCover)
return false;
boolean[] isMatched = new boolean[V]; // 检查 mate[] 唯一性
for (int v = 0; v < V; v++)
{
int w = mate[v];
if (w == -1)
continue;
if (v == w)
return false;
if (v >= w)
continue;
if (isMatched[v] || isMatched[w]) return false;
isMatched[v] = true;
isMatched[w] = true;
}
for (int v = 0; v < V; v++) // 检查匹配集中的每个顶点至少有一条远端也属于匹配集的边
{
if (mate(v) == -1)
continue;
boolean isEdge = false;
for (int w : G.adj(v))
{
if (mate(v) == w)
isEdge = true;
}
if (!isEdge)
return false;
}
for (int v = 0; v < V; v++) // 检查 inMinVertexCover[] 是一个覆盖
{
for (int w : G.adj(v))
{
if (!inMinVertexCover(v) && !inMinVertexCover(w))
return false;
}
}
return true;
} public static void main(String[] args)
{
int V1 = Integer.parseInt(args[0]);
int V2 = Integer.parseInt(args[1]);
int E = Integer.parseInt(args[2]);
Graph G = GraphGenerator.bipartite(V1, V2, E);
if (G.V() < 1000)
StdOut.println(G); class01 matching = new class01(G); StdOut.printf("Number of edges in max matching = %d\n", matching.size());
StdOut.printf("Number of vertices in min vertex cover = %d\n", matching.size());
StdOut.printf("Graph has a perfect matching = %b\n", matching.isPerfect());
StdOut.println(); if (G.V() >= 1000)
return;
StdOut.print("Max matching: ");
for (int v = 0; v < G.V(); v++)
{
int w = matching.mate(v);
if (matching.isMatched(v) && v < w)
StdOut.print(v + "-" + w + " ");
}
StdOut.print("\nMin vertex cover: ");
for (int v = 0; v < G.V(); v++)
{
if (matching.inMinVertexCover(v))
StdOut.print(v + " ");
}
StdOut.println();
}
}

● 二分图最大匹配(最小顶点覆盖)的 HopcroftKarp 算法,仅注释与普通交替路径法不同的地方

 package package01;

 import java.util.Iterator;
import edu.princeton.cs.algs4.StdOut;
import edu.princeton.cs.algs4.BipartiteX;
import edu.princeton.cs.algs4.Stack;
import edu.princeton.cs.algs4.Graph;
import edu.princeton.cs.algs4.GraphGenerator;
import edu.princeton.cs.algs4.Queue; public class class01
{
private final int V;
private BipartiteX bipartition;
private int cardinality;
private int[] mate;
private boolean[] inMinVertexCover;
private boolean[] marked;
private int[] distTo; // 搜索序列中抵达每个顶点的最小路径长度 public class01(Graph G)
{
V = G.V();
mate = new int[V];
for (int v = 0; v < V; v++)
mate[v] = -1;
for (; hasAugmentingPath(G);)
{
Iterator<Integer>[] adj = (Iterator<Integer>[]) new Iterator[G.V()]; // 邻接表迭代器列表
for (int v = 0; v < G.V(); v++)
adj[v] = G.adj(v).iterator();
for (int s = 0; s < V; s++)
{
if (isMatched(s) || !bipartition.color(s)) // 只取未配对的黑色(true)顶点
continue;
Stack<Integer> path = new Stack<Integer>(); // 使用非递归的深度优先遍历寻找关于顶点 s 的交替路径
for (path.push(s); !path.isEmpty();)
{
int v = path.peek();
if (!adj[v].hasNext()) // 栈顶顶点没有出边,跳过
{
path.pop();
continue;
}
int w = adj[v].next();
if (!isLevelGraphEdge(v, w)) // 若 v -w 不是搜索队列生成的边,跳过
continue;
path.push(w);
if (!isMatched(w)) // w 是新点,它不在 mate 表中,但在搜索序列中
{
for (; !path.isEmpty();) // 增广路经中每两个相邻顶点做成匹配
{
int x = path.pop(), y = path.pop();
mate[x] = y;
mate[y] = x;
}
cardinality++;
}
}
}
}
inMinVertexCover = new boolean[V];
for (int v = 0; v < V; v++)
{
if (bipartition.color(v) && !marked[v] || !bipartition.color(v) && marked[v])
inMinVertexCover[v] = true;
}
} private boolean hasAugmentingPath(Graph G)
{
marked = new boolean[V];
distTo = new int[V];
for (int v = 0; v < V; v++)
distTo[v] = Integer.MAX_VALUE;
Queue<Integer> queue = new Queue<Integer>(); // 从未配对的顶点开始广度优先搜索
for (int v = 0; v < V; v++)
{
if (bipartition.color(v) && !isMatched(v)) // 所有未配对的黑色点(true)加入搜索队列,作为初始点,初始点距离为 0
{
queue.enqueue(v);
marked[v] = true;
distTo[v] = 0;
}
}
boolean hasAugmentingPath = false;
for (; !queue.isEmpty();) // 要运行直到所有顶点都被遍历过才结束
{
int v = queue.dequeue();
for (int w : G.adj(v))
{
if (isResidualGraphEdge(v, w) && !marked[w])
{
distTo[w] = distTo[v] + 1; // 标记顶点距离为已知顶点加 1 而不标记父节点编号
marked[w] = true;
if (!isMatched(w))
hasAugmentingPath = true; // 仅标记发现了交替路径而不返回
if (!hasAugmentingPath)
queue.enqueue(w);
}
}
}
return hasAugmentingPath;
} private boolean isResidualGraphEdge(int v, int w)
{
return (mate[v] != w) && bipartition.color(v) || (mate[v] == w) && !bipartition.color(v);
} private boolean isLevelGraphEdge(int v, int w) // v - w 是搜索队列生成的边
{
return (distTo[w] == distTo[v] + 1) && isResidualGraphEdge(v, w);
} public int mate(int v)
{
return mate[v];
} public boolean isMatched(int v)
{
return mate[v] != -1;
} public int size()
{
return cardinality;
} public boolean isPerfect()
{
return cardinality * 2 == V;
} public boolean inMinVertexCover(int v)
{
return inMinVertexCover[v];
} private static String toString(Iterable<Integer> path)
{
StringBuilder sb = new StringBuilder();
for (int v : path)
sb.append(v + "-");
String s = sb.toString();
s = s.substring(0, s.lastIndexOf('-'));
return s;
} public static void main(String[] args)
{
int V1 = Integer.parseInt(args[0]);
int V2 = Integer.parseInt(args[1]);
int E = Integer.parseInt(args[2]);
Graph G = GraphGenerator.bipartite(V1, V2, E);
if (G.V() < 1000)
StdOut.println(G); class01 matching = new class01(G); StdOut.printf("Number of edges in max matching = %d\n", matching.size());
StdOut.printf("Number of vertices in min vertex cover = %d\n", matching.size());
StdOut.printf("Graph has a perfect matching = %b\n", matching.isPerfect());
StdOut.println(); if (G.V() >= 1000)
return;
StdOut.print("Max matching: ");
for (int v = 0; v < G.V(); v++)
{
int w = matching.mate(v);
if (matching.isMatched(v) && v < w)
StdOut.print(v + "-" + w + " ");
}
StdOut.print("\nMin vertex cover: ");
for (int v = 0; v < G.V(); v++)
{
if (matching.inMinVertexCover(v))
StdOut.print(v + " ");
}
StdOut.println();
}
}

《算法》第六章部分程序 part 6的更多相关文章

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

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

  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. Excel技巧--使用温度计图让目标与实际对比更明显

    如上图,有一业绩目标与实际值对比表格,我们可使用如上图右方的温度计图表来让数字对比更明显些. 做法: 1.选择该表格,点击插入-->柱形图,簇状柱形图. 2.右键点击图表“实际值”柱,点选“设置 ...

  2. kali 安装qq

    使用的是longene TM2013 下载地址(百度云):http://pan.baidu.com/s/1dFx8azv 安装: 64位的需要安装32位依赖文件 用这条命令 apt-get insta ...

  3. 生成OSIDAAuto.OPCServer失败

    来源:https://pisquare.osisoft.com/message/13441 A failuare of generating OSIDAAuto.OPCServer dorislipe ...

  4. python基本知识点

    1.基本数据类型 1.1int 字符串转换为数字,比如 a = “123” print(type(a) , a) b = int(a) print(type(b),b) num = “b” v = i ...

  5. 黄聪:3分钟学会sessionStorage用法

    前言: 因最近移动端开发过程中遇到一个运营提出的所谓技术难点需求,对于原生APP来说轻而易举,毕竟自己的APP用户操作指哪打哪,但是H5该怎么做?H5就实现不了么?对于一个爱研究攻克这些前端棘手问题的 ...

  6. Oracle 锁的概念

    用scott/orcl登录并且模拟数据 SQL> conn scott/orclConnected.SQL> create table tt(id int primary key); Ta ...

  7. [UE4]Canvas Panel

    一.Canvas Panel:画布.Canvas Panel中内的元素可以任何摆放位置.Canvas Panel是UserWiget默认的根节点容器,可以把跟节点删除替换生成任何的UI元素. 二.选择 ...

  8. [UE4]单机游戏改网络游戏,不完全清单

    把Actor的复制打开 中腰数据的复制打开,且只在服务器修改(比如角色属性血量) 需要同步的Actor,不在客户端Spawn 客户端的操作,先报告到服务器,服务器再广播到所有客户端 某些逻辑只在服务器 ...

  9. ArcGIS for android访问天地图

    底图采用Web Mercator投影坐标系 获取元数据信息:http://t0.tianditu.com/img_w/wmts?SERVICE=WMTS&REQUEST=GetCapabili ...

  10. IDEA远程调试

    问题:         通常在java打成可运行的Jar项目时,运行jar时调试很不方便,特别是要在linux上面执行jar包的情况.此时需要将这个项目打成Jar包(如果是maven项目,直接使用pa ...