▶ 书中第六章部分程序,包括在加上自己补充的代码,包括二分图最大匹配(最小顶点覆盖)的交替路径算法和 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. sqlserver收缩数据库

    缩数据库文件(如果不压缩,数据库的文件不会减小 企业管理器--右键你要压缩的数据库--所有任务--收缩数据库--收缩文件    --选择日志文件--在收缩方式里选择收缩至XXM,这里会给出一个允许收缩 ...

  2. camtasis studio 未能创建视频内存资源。

    camtasis studio failed to create a video memory resource.camtasis studio 未能创建视频内存资源. 在工具--选项中取消GPU加速 ...

  3. 构建Jenkins自动化编译管理环境

    今天研究了一下Jenkins,有了一个粗浅的认识,顺手把构建的过程说一下,后续慢慢补充: (1)Secure CRT 连接到Linux服务器 要注意的一点是,要搞好一个文件传输的路子,否则不好传东西. ...

  4. centos 磁盘清理 /dev/vda1系统盘满了

    df   -h   检查一台服务器磁盘使用空间,发现磁盘已经使用了100% 思路是: 1.cd /usr   当然这里不一定是/usr目录,最好是cd到 根目录再执行下一步 2.du -sh * 看哪 ...

  5. 【java】之常用四大线程池用法以及ThreadPoolExecutor详解

    为什么用线程池? 1.创建/销毁线程伴随着系统开销,过于频繁的创建/销毁线程,会很大程度上影响处-理效率2.线程并发数量过多,抢占系统资源从而导致阻塞3.对线程进行一些简单的管理 在Java中,线程池 ...

  6. mac nginx 一些资料

    http://www.jianshu.com/p/918eb337a206 mac 的nginx 配置目录在/usr/local/etc/nginx 安装之前最好执行brew的update和upgra ...

  7. 浏览器唤起APP的功能

    http://blog.html5funny.com/2015/06/19/open-app-from-mobile-web-browser-or-webview/ http://panli.mu.g ...

  8. vo和pojo

    pojo直接描述数据库中的表和字段,一一对应 vo的话,可以多添加些属性,比如code对应的name,或者标识符等等 查询列表的时候也可以直接用vo,但是修改或添加记录必须是pojo QueryVo ...

  9. for循环计算li的个数

    今天有一段代码 在ie6下面显示 我检查了一下代码,发现每for循环一次,就会重新计算li的个数,会拖慢运行速度,所以改成以下代码,ie6就正常了

  10. android实现3D Gallery 轮播效果,触摸时停止轮播

    1.轮播控件涉及到的两个类 CarouselViewPager.java public class CarouselViewPager extends ViewPager { @IntDef({RES ...