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

    例如对windows发消息让文本选中.     SendMessage(Text1.hwnd,EM_GETSEL,0,-1 ); EC_LEFTMARGIN(&H1) EC_USEFONTIN ...

  2. 解决nginx转发websocket报400错误

    解决nginx转发websocket报400错误 说明 由于个人服务器上面有多个项目,配置了二级域名,需要对二级域名进行转发,在转发工作这快采取了大名鼎鼎的nginx.在这之前所有的项目运行转发都没问 ...

  3. PAT 乙级 1051 复数乘法 (15) C++版

    1051. 复数乘法 (15) 时间限制 400 ms 内存限制 65536 kB 代码长度限制 8000 B 判题程序 Standard 作者 CHEN, Yue 复数可以写成(A + Bi)的常规 ...

  4. JSON 语法

    ylbtech-JSON: JSON 语法 JSON 语法是 JavaScript 语法的子集. 1. JSON 语法规则返回顶部 JSON 语法是 JavaScript 对象表示语法的子集. 1. ...

  5. 廖雪峰Java2面向对象编程-2数据封装-1方法

    1.数据封装 一个class可以包含多个field.直接把field用public暴露给外部可能破坏了封装,例如传入不合理的数值(年龄填入1000).如下 public class Person { ...

  6. 通过编写PHP代码并运用“正则表达式”来实现对试题文档进行去重复、排序

    通过编写PHP代码并运用“正则表达式”来实现对试题文档进行去重复.排序 <?php $subject = file_get_contents('test.txt'); $pattern = '/ ...

  7. git基本的使用原理

    一:Git是什么? Git是目前世界上最先进的分布式版本控制系统. 二:SVN与Git的最主要的区别? SVN是集中式版本控制系统,版本库是集中放在中央服务器的,而干活的时候,用的都是自己的电脑,所以 ...

  8. java中,什么是方法的重载?需要满足什么条件?两同三不同指的什么?

    方法重载需要满足以下几个条件: 在同一个类中 方法的名称相同 参数列表不同 方法重载有以下特点: 与访问修饰符和返回值类型无关 与异常无关 方法重载的作用: 传递不同的参数实现相同的效果 所谓两同,就 ...

  9. Composer 安装时要求输入授权用户名密码

    composer require "overtrue/laravel-socialite:~2.0" Authentication required (packagist.phpc ...

  10. 总结一下连日来在MAC下被Python3设下的坑

    当时的情况:mac下自带python2, 1.安装pyhon3: 首次从官网下载了安装包安装,安装目录在/Library/Frameworks/Python.framework/Versions/3. ...