版权声明:欢迎大家转载,指正。 https://blog.csdn.net/yin__ren/article/details/83421418
1. 基本图算法
- BFS和DFS详解以及java实现 推荐阅读
1. 广度优先搜索(BFS)
从临近源顶点s最近的顶点开始,通过对图G的边的探索发现从源顶点s能够抵达的每个顶点
1. 伪代码
BFS(G,s)
for each vertex u 属于 G.V - {S}
u.color = WHITE
u.d = 无穷
u.pi = NIL
s.color = GRAY
s.d = 0
s.pi = NIL
Q = null
ENQUEUE(Q,s)
while Q != null
u = DEQUEUE(Q)
for each v 属于 G.Adj[u]
if v.color == WHITE
v.color = GRAY
v.d = u.d + 1
v.pi = u
ENQUEUE(Q,v)
u.color = BLACK
2. 时间复杂度
3. java 代码实现
/**
* Broadth First Search
*
* @param graph 用于存放图中每个结点的邻接表
* key:Character
* value:该结点的邻接表 LinkedList<Character>
* @param map 用于存放每个结点与顶点的距离
* key:Character
* value:距离
* @param start 起始顶点
*/
public static void BFS(HashMap<Character, LinkedList<Character>> graph, HashMap<Character, Integer> map, char start) {
Queue<Character> q = new LinkedList<>();
q.add(start); //将起始顶点加入队列
map.put(start, 0);
int i = 0;
while (!q.isEmpty()) {
//取出队首元素
char top = q.poll();
i++;
System.out.println("The" + i + "th element: " + top + "Distance from start is: " + map.get(top));
//计算周边未访问过的结点的距离
int distance = map.get(top) + 1;
//访问队首元素结点的邻接表
for (Character c : graph.get(top)) {
//在该邻接表中,如果某元素还没被访问到,说明还未遍历,则访问这个结点
if (!map.containsKey(c)) {
map.put(c, distance);
q.add(c);
}
}
}
}
//测试
//构造图
private static HashMap<Character, LinkedList<Character>> initGraph(){
//构造各顶点
LinkedList<Character> list_s = new LinkedList<Character>();
list_s.add('w');
list_s.add('r');
LinkedList<Character> list_w = new LinkedList<Character>();
list_w.add('s');
list_w.add('i');
list_w.add('x');
LinkedList<Character> list_r = new LinkedList<Character>();
list_r.add('s');
list_r.add('v');
LinkedList<Character> list_x = new LinkedList<Character>();
list_x.add('w');
list_x.add('i');
list_x.add('u');
list_x.add('y');
LinkedList<Character> list_v = new LinkedList<Character>();
list_v.add('r');
LinkedList<Character> list_i = new LinkedList<Character>();
list_i.add('u');
list_i.add('x');
list_i.add('w');
LinkedList<Character> list_u = new LinkedList<Character>();
list_u.add('i');
list_u.add('x');
list_u.add('y');
LinkedList<Character> list_y = new LinkedList<Character>();
list_y.add('u');
list_y.add('x');
//构造图
HashMap<Character, LinkedList<Character>> graph = new HashMap<Character, LinkedList<Character>>();
graph.put('s', list_s);
graph.put('w', list_w);
graph.put('r', list_r);
graph.put('x', list_x);
graph.put('v', list_v);
graph.put('i', list_i);
graph.put('y', list_y);
graph.put('u', list_u);
return graph;
}
public static void main(String[] args) {
//构造图
HashMap<Character, LinkedList<Character>> graph = initGraph();
//记录每个顶点离起始点的距离,也即最短距离
HashMap<Character, Integer> dist = new HashMap<Character, Integer>();
//遍历的起始点
char start = 's';
//调用广度优先方法
BFS(graph, dist, start);
}
2. 深度优先搜索(DFS)
从当前访问顶点开始,探索图的边以发现图中的每个顶点
1. 伪代码
DFS(G)
for each vertex u 属于 G.V
u.color = WHITE
u.pi = NIL
time = 0
for each vertex u 属于 G.V
if u.color == WHITE
DFS-VISIT(G,u)
DFS-VISIT(G,u)
time = time + 1 //white vertex u has just been discovered
u.d = time
u.color = GRAY
for each v 属于 G:Adj[u]
if v.color == WHITE
v.pi = u
DFS-VISIT(G,v)
u.color = BLACK //blacken u;it is finished
time = time + 1
u.f = time
2. 时间复杂度
3. java 代码实现
/**
* Depth First Search
*
* @param graph 用于存放图中每个结点的邻接表
* @param visited 用于存放每个结点与顶点的距离
*/
public static void DFS(HashMap<Character, LinkedList<Character>> graph, HashMap<Character, Boolean> visited) {
visit(graph, visited, 'u');
visit(graph, visited, 'w');
}
static int count = 0;//通过一个全局变量count记录了进入每个节点和离开每个节点的时间
/**
* @param graph 用于存放图中每个结点的邻接表
* key:Character
* value:该结点的邻接表 LinkedList<Character>
* @param visited 用于存放每个结点与顶点的距离
* key:Character
* value:距离
* @param start 起始节点
*/
private static void visit(HashMap<Character, LinkedList<Character>> graph, HashMap<Character, Boolean> visited, char start) {
if (!visited.containsKey(start)) {
count++;
System.out.println("The time into element: " + start + ":" + count);//记录进入该结点的时间
//将该结点标志为已访问
visited.put(start, true);
//访问队首元素结点的邻接表
for (char c : graph.get(start)) {
//递归访问其他邻近结点
if (!visited.containsKey(c)) {
visit(graph, visited, c);
}
}
count++;
System.out.println("The time out element: " + start + ":" + count);// 记录离开该节点的时间
}
}
//构造图
private static HashMap<Character, LinkedList<Character>> initDFSGraph() {
//构造各顶点
LinkedList<Character> list_u = new LinkedList<Character>();
list_u.add('v');
list_u.add('x');
LinkedList<Character> list_v = new LinkedList<Character>();
list_v.add('y');
LinkedList<Character> list_y = new LinkedList<Character>();
list_y.add('x');
LinkedList<Character> list_x = new LinkedList<Character>();
list_x.add('v');
LinkedList<Character> list_w = new LinkedList<Character>();
list_w.add('y');
list_w.add('z');
LinkedList<Character> list_z = new LinkedList<Character>();
//构造图
HashMap<Character, LinkedList<Character>> graph = new HashMap<Character, LinkedList<Character>>();
graph.put('u', list_u);
graph.put('v', list_v);
graph.put('y', list_y);
graph.put('x', list_x);
graph.put('w', list_w);
graph.put('z', list_z);
return graph;
}
public static void main(String[] args) {
HashMap<Character, LinkedList<Character>> graph = initDFSGraph();
HashMap<Character, Boolean> visited = new HashMap<Character, Boolean>();
//调用深度优先遍历方法
DFS(graph, visited);
}
3. 拓扑排序
拓扑排序:找出有向无回路图G = (V, E)中顶点的一个线性序,使得(u, v)如果是图中的一条边,那么在这个线性序中u在v前出现
- 有向图的拓扑排序算法JAVA实现 推荐阅读
1. 伪代码
//对有向无环图
TOPOLOGICAL-SORT(G)
call DFS(G) to compute finish times v. f for each vertex v
as each vertex is finished,insert it onto the front of a linked list
return the link list of vertices
2. 时间复杂度
3. Java 代码实现
public void topoSort() throws Exception{
int count = 0;
Queue<Vertex> queue = new LinkedList<>();// 拓扑排序中用到的栈,也可用队列.
//扫描所有的顶点,将入度为0的顶点入队列
Collection<Vertex> vertexs = directedGraph.values();
for (Vertex vertex : vertexs)
if(vertex.inDegree == 0)
queue.offer(vertex);
while(!queue.isEmpty()){
Vertex v = queue.poll();
System.out.print(v.vertexLabel + " ");
count++;
for (Edge e : v.adjEdges)
if(--e.endVertex.inDegree == 0)
queue.offer(e.endVertex);
}
if(count != directedGraph.size())
throw new Exception("Graph has circle");
}
4. 强连通分支
- DFS应用——查找强分支 推荐阅读
- 图论–强连通分支 推荐阅读
1. 伪代码
STRONGLY-CONNECTED-COMPONENTS(G)
call DFS(G) to compute finishing times u. f for each vertex u
compute G^T
call DFS(G^T),but in the main loop of DFS,consider the vertices
in order of decreasing u. f(as computed in line 1)
output the vertices of each tree in the depth-first forest formed in line 3 as a separate strongly connected component
2. 最小生成树
1. Kruskal算法(加边法)
- Kruskal算法(三)之 Java详解 推荐阅读
1. 伪代码
MST-KRUSKAL(G,w)
A = null
for each vertex v 属于G.V
MAKE-SET(v)
sort the edges of G.E into nondecreasing order by weight w
for each edges(u,v)属于G.E,taken in nondecreasing order by weight
if FIND-SET(v) != FIND-SET(v)
A = A and {(u,v)}
UNION(u,v)
return A
2. 时间复杂度
3. Java 代码实现
/**
* 求最小树的Kruskal算法
* 算法思想:克鲁斯卡尔算法从另一个途径求网中的最小生成树。假设联通网N=(V,{E}),则令
* 最小生成树的厨师状态为只有n个顶点而无边的非连通图T=(V,{}),途中每个顶点自称一个连通分量。
* 在E中选择代价最小的边,若该边衣服的顶点落在T中不同的连通分量上,则将此边加入到T中,否则舍去此边
* 而选择下一条最小的边。以此类推,直至T中所有的顶点都在同一连通分量上为止。
*
* @param V 图中的节点集合
* @param E 图中边的集合
*/
public static void KRUSKAL(int[] V, Edge[] E) {
Arrays.sort(E);//将边按照权重w升序排序
ArrayList<HashSet> sets = new ArrayList<HashSet>();
for (int i = 0; i < V.length; i++) {
HashSet set = new HashSet();
set.add(V[i]);
sets.add(set);
}
for (int i = 0; i < E.length; i++) {//遍历边
int start = E[i].i, end = E[i].j;
int counti = -1, countj = -2;
for (int j = 0; j < sets.size(); j++) {//遍历节点
HashSet set = sets.get(j);
if (set.contains(start)) {
counti = j;
}
if (set.contains(end)) {
countj = j;
}
}
if (counti < 0 || countj < 0) {
System.err.println("没有在子树中找到节点,错误");
}
if (counti != countj) {
System.out.println("输出start=" + start + "||end=" + end + "||w=" + E[i].w);
HashSet setj = sets.get(countj);
sets.remove(countj);
HashSet seti = sets.get(counti);
sets.remove(counti);
seti.addAll(setj);
sets.add(seti);
} else {
System.out.println("他们在一棵子树中,不能输出start=" + start + "||end=" + end + "||w=" + E[i].w);
}
}
}
public static void main(String[] args) {
int[] V = {1, 2, 3, 4, 5, 6};
Edge[] E = new Edge[10];
E[0] = new Edge(1, 2, 6);
E[1] = new Edge(1, 3, 1);
E[2] = new Edge(1, 4, 5);
E[3] = new Edge(2, 3, 5);
E[4] = new Edge(2, 5, 3);
E[5] = new Edge(3, 4, 5);
E[6] = new Edge(3, 5, 6);
E[7] = new Edge(3, 6, 4);
E[8] = new Edge(4, 6, 2);
E[9] = new Edge(5, 6, 6);
MiniSpanTree.KRUSKAL(V, E);
}
2. Prim算法(加点法)
1. 伪代码
MST-PRIM(G,w,r)
for each u 属于 G.V
v:key = 无穷
v:pi = NIL
r:key = 0
Q = G.V
while Q != null
u = EXTRACT-MIN(Q)
for each v属于G.Adj[u]
if v 属于 Q and w(u,v) < v.key
v.pi = u
v.key = w(u,v)
2. 时间复杂度
3. Java 代码实现
/**
* 求图最小生成树的PRIM算法
* 基本思想:假设N=(V,{E})是联通网,TE是N上的最想生成树中的变得集合。算法从U={u0}(u0属于V),
* TE={}开始,重复执行下述操作:在所有的u属于U,v属于V-U的边(u,v)属于E中找到一条代价最小
* 的边(u0,v0)并入集合TE,同事v0并入U,直至U=V为止。此时TE中必有n-1条边,则T=(V,{TE})
* 为N的最小生成树。
*
* @param graph 图
* @param start 开始节点
* @param n 图中节点数
*/
public static void PRIM(int[][] graph, int start, int n) {
int[][] mins = new int[n][2];//用于保存集合U到V-U之间的最小边和它的值,mins[i][0]值表示到该节点i边的起始节点
//值为-1表示没有到它的起始点,mins[i][1]值表示到该边的最小值,
//mins[i][1]=0表示该节点已将在集合U中
for (int i = 0; i < n; i++) {//初始化mins
if (i == start) {
mins[i][0] = -1;
mins[i][1] = 0;
} else if (graph[start][i] != -1) {//说明存在(start,i)的边
mins[i][0] = start;
mins[i][1] = graph[start][i];
} else {
mins[i][0] = -1;
mins[i][1] = Integer.MAX_VALUE;
}
}
for (int i = 0; i < n - 1; i++) {
int minV = -1, minW = Integer.MAX_VALUE;
for (int j = 0; j < n; j++) {//找到mins中最小值,使用O(n^2)时间
if (mins[j][1] != 0 && minW > mins[j][1]) {
minW = mins[j][1];
minV = j;
}
}
mins[minV][1] = 0;
System.out.println("最小生成树的第" + i + "条最小边=<" + (mins[minV][0] + 1) + "," + (minV + 1) + ">,权重=" + minW);
for (int j = 0; j < n; j++) {//更新mins数组
if (mins[j][1] != 0) {
if (graph[minV][j] != -1 && graph[minV][j] < mins[j][1]) {
mins[j][0] = minV;
mins[j][1] = graph[minV][j];
}
}
}
}
}
public static void main(String[] args) {
int[][] tree = {
{-1, 6, 1, 5, -1, -1},
{6, -1, 5, -1, 3, -1},
{1, 5, -1, 5, 6, 4},
{5, -1, 5, -1, -1, 2},
{-1, 3, 6, -1, -1, 6},
{-1, -1, 4, 2, 6, -1}
};
MiniSpanTree.PRIM(tree, 0, 6);
}
3. 单源最短路径
1. Bellman-Ford 算法
- 图解贝尔曼-福特算法 推荐阅读
1. 伪代码
BELLMAN-FORD(G, w, s)
INITIALIZE-SINGLE-SOURCE(G, s)
for i 1 to |V[G]| - 1
do for each edge (u, v) E[G]
do RELAX(u, v, w)
// 检查是否存在权值为负的环
for each edge (u, v) E[G]
do if d[v] > d[u] + w(u, v)
then return FALSE
return TRUE
2. 时间复杂度
3. java 代码实现
public class BellmanFord {
public static void main(String[] args) {
//创建图
Edge[] edges = initEdge();
bellmanFord(edges);
}
//bellmanFord 算法
private static void bellmanFord(Edge[] edges) {
//存放到各个节点所需要消耗的时间
HashMap<String, Integer> costMap = new HashMap<String, Integer>();
//到各个节点对应的父节点
HashMap<String, String> parentMap = new HashMap<String, String>();
//初始化各个节点所消费的,当然也可以再遍历的时候判断下是否为Null
//i=0的时候
costMap.put("A", 0); //源点
costMap.put("B", Integer.MAX_VALUE);
costMap.put("C", Integer.MAX_VALUE);
costMap.put("D", Integer.MAX_VALUE);
costMap.put("E", Integer.MAX_VALUE);
//进行节点数n-1次循环
for (int i = 1; i < costMap.size(); i++) {//遍历各个点
for (int j = 0; j < edges.length; j++) {//遍历每条边
Edge edge = edges[j];
//该边起点目前总的路径大小
int startPointCost = costMap.get(edge.getStartPoint()) == null ? 0 : costMap.get(edge.getStartPoint());
//该边终点目前总的路径大小
int endPointCost = costMap.get(edge.getEndPoint()) == null ? Integer.MAX_VALUE : costMap.get(edge.getEndPoint());
//如果该边终点目前的路径大小 > 该边起点的路径大小 + 该边权重 ,说明有更短的路径了
if (endPointCost > (startPointCost + edge.getWeight())) {
costMap.put(edge.getEndPoint(), startPointCost + edge.getWeight());
parentMap.put(edge.getEndPoint(), edge.getStartPoint());
}
}
}
//在进行一次判断是否存在负环路
boolean hasRing = false;
for (int j = 0; j < edges.length; j++) {
Edge edge = edges[j];
int startPointCost = costMap.get(edge.getStartPoint()) == null ? 0 : costMap.get(edge.getStartPoint());
int endPointCost = costMap.get(edge.getEndPoint()) == null ? Integer.MAX_VALUE : costMap.get(edge.getEndPoint());
if (endPointCost > (startPointCost + edge.getWeight())) {
System.out.print("\n图中存在负环路,无法求解\n");
hasRing = true;
break;
}
}
//结果打印
if (!hasRing) {
//打印出到各个节点的最短路径
for (String key : costMap.keySet()) {
System.out.print("\n到目标节点" + key + "最低耗费:" + costMap.get(key));
if (parentMap.containsKey(key)) {
List<String> pathList = new ArrayList<String>();
String parentKey = parentMap.get(key);
while (parentKey != null) {
pathList.add(0, parentKey);
parentKey = parentMap.get(parentKey);
}
pathList.add(key);
String path = "";
for (String k : pathList) {
path = path.equals("") ? path : path + " --> ";
path = path + k;
}
System.out.print(",路线为" + path);
}
}
}
}
//初始化图
private static Edge[] initEdge() {
Edge ab = new Edge("A", "B", -1);
Edge ac = new Edge("A", "C", 4);
Edge bc = new Edge("B", "C", 3);
Edge be = new Edge("B", "E", 2);
Edge ed = new Edge("E", "D", -3);
Edge dc = new Edge("D", "C", 5);
Edge bd = new Edge("B", "D", 2);
Edge db = new Edge("D", "B", 1);
//从起点A出发,步骤少的排前面
Edge[] edges = new Edge[]{ab, ac, bc, be, bd, ed, dc, db};
return edges;
}
//邻接表来实现图
static class Edge {
//起点id
private String startPoint;
//结束点id
private String endPoint;
//该边的权重
private int weight;
public Edge(String startPoint, String endPoint, int weight) {
this.startPoint = startPoint;
this.endPoint = endPoint;
this.weight = weight;
}
public String getStartPoint() {
return startPoint;
}
public String getEndPoint() {
return endPoint;
}
public int getWeight() {
return weight;
}
}
}
2. Dijkstra 算法
1. 伪代码
DIJKSTRA(G,w,s)
INITIALIZE-SINGLE-SORT(G,s)
S = null
Q = G.V
while Q != null
u = EXTRACT-MIN(Q)
S = S + {u}
for each vertex v 属于G.Adj[u]
RELAX(u,v,w)
2. 时间复杂度
3. java 代码实现
public static void main(String[] args) {
//final int No = Integer.MAX_VALUE;//不能使用 Integer.MAX_VALUE,避免两个 No 相加造成溢出
final int No = 10000;
int[][] weight = {
{0, 6, No, No, 7},
{No, 0, 5, -4, 8},
{No, -2, 0, No, No},
{2, No, 7, 0, No},
{No, No, -3, 9, 0}
};
dijkstra(weight, 0);
}
/**
* dijkstra 算法实现
*
* @param weight 有向图的权重矩阵
* @param start 起点编号
*/
public static int[] dijkstra(int[][] weight, int start) {
int num = weight.length;//顶点个数
int[] shortPath = new int[num];//存放从 start 到其他各点的最短路径
String[] path = new String[num];//存放从start到其他各点的最短路径的字符串表示
for (int i = 0; i < num; i++) {
path[i] = new String(start + "-->" + i);
}
int[] visited = new int[num];//标记当前该顶点的最短路径是否已经求出,1表示已求出
//初始化
shortPath[start] = 0;
visited[start] = 1;
for (int i = 1; i < num; i++) {//要遍历 n-1 次
int k = -1;//选出距 start 点最近的未标记点
int dmin = Integer.MAX_VALUE;
for (int j = 0; j < num; j++) {//遍历当前节点附近的其余节点
if (visited[j] == 0 && weight[start][j] < dmin) {
dmin = weight[start][j];//修正最短距离
k = j;//记录下最近点标记
}
}
shortPath[k] = dmin;//将新选出的顶点标记为已求出最短路径,且到start的最短路径就是dmin
visited[k] = 1;
for (int j = 0; j < num; j++) {//以k为中间点,修正从start到未访问各点的距离
if (visited[j] == 0 && weight[start][k] + weight[k][j] < weight[start][j]) {
weight[start][j] = weight[start][k] + weight[k][j];
path[i] = path[k] + "-->" + i;
}
}
}
//打印结果
printPath(path, num, start);
printResult(shortPath, num, start);
return shortPath;
}
//打印所经过的各个点的记录
public static void printPath(String[] path, int num, int start) {
for (int i = 0; i < num; i++) {
System.out.println("从" + start + "出发到" + i + "的最短路径为: " + path[i]);
}
System.out.println("========================================");
}
//打印结果
public static void printResult(int[] shortPath, int num, int start) {
for (int i = 0; i < num; i++) {
System.out.println("从" + start + "出发到" + i + "的最短距离为: " + shortPath[i]);
}
}
4. 全源最短路径
1. Floyd-Warshall 算法
- 【算法导论】【Floyd-Warshall 算法】每对节点之间的最短路径 推荐阅读
- 最短路径Floyd算法【图文详解】 推荐阅读,博主未看懂,还请指教
- Floyd-Warshall算法求解所有结点对的最短路径问题Java实现
1. 伪代码
FLOYD-WARSHALL(W)
n = W.rows
D(0) = W
for k = 1 to n
let D(k) = d(k)_ij be a new nXn matrix
for i = 1 to n
for j = 1 to n
d(k)_ij = min(d(k-1)_ij,d(k-1)_ik + d(k-1)_kj)
return D(n)
2. 时间复杂度
3. java 代码实现
public class FloydWarShall {
private static int INF = Integer.MAX_VALUE;
public static void main(String[] args) {
int[][] matrix = {
{0, 3, 8, INF, -4},
{INF, 0, INF, 1, 7},
{INF, 4, 0, INF, INF},
{2, INF, -5, 0, INF},
{INF, INF, INF, 6, 0},
};
floyd(matrix);
System.out.println("============最短路径长度============");
for (int i = 0; i < matrix.length; i++) {
for (int j = 0; j < matrix[i].length; j++) {
System.out.print(matrix[i][j] + " ");
}
System.out.println();
}
}
//FloydWarShall 算法
public static void floyd(int[][] dist) {
int size = dist.length;
for (int k = 0; k < size; k++) {
for (int i = 0; i < size; i++) {
for (int j = 0; j < size; j++) {
if (dist[i][k] != INF && dist[k][j] != INF
&& dist[i][k] + dist[k][j] < dist[i][j]) {
dist[i][j] = dist[i][k] + dist[k][j];
}
}
}
}
}
}
2. Johnson 算法
- Johnson 全源最短路径算法 推荐阅读
1. 伪代码
2. 时间复杂度
3. java 代码实现
5. 最大流
1. Ford-Fulkerson 算法
1. 伪代码
FORK-FULKERSON(G,s,t)
for each edge(u,v) 属于 G.E
(u,v).f = 0
while there exists a path p form s to t in the residual network G_f
c_f(p) = min{c_f(u,v):(u,v) is in p}
for each edge(u,v) in p
if (u,v) 属于 E
(u,v).f = (u,v).f + c_f(p)
else (v,u).f = (v,u).f - c_f(p)
2. 时间复杂度
3. Java 代码实现
- 计算残存网络
- 计算增广路径
- 沿着增广路径,重复增加流量,直到找到最大值为止
2. 最大二分匹配
- 匈牙利算法——最大匹配问题详解 推荐阅读
public class MaxMatch {
public static int n = 0, m = 0; //二分图的左边和右边顶点数目
/**
* 如果能够找到已顶点start开始的增广路径返回true,否则返回false
*
* @param map 给定的二分图,map[i][j]等于1表示i到j连通,为0则表示不连通
* @param used
* @param linked linked[i] = u表示顶点i与顶点u连接
* @param start 当前start顶点出发,寻找增广路径
* @return
*/
public static boolean dfs(int[][] map, boolean[] used, int[] linked, int start) {
for (int i = 0; i < m; i++) {
if (used[i] == false && map[start][i] == 1) {
used[i] = true;
if (linked[i] == -1 || dfs(map, used, linked, linked[i])) {
linked[i] = start;
return true;
}
}
}
return false;
}
public static int getMaxNum(int[][] map) {
int count = 0;
int[] linked = new int[m];
for (int i = 0; i < m; i++) {
linked[i] = -1;
}
for (int i = 0; i < n; i++) {
boolean[] used = new boolean[m]; //初始化m部分顶点均为被访问
if (dfs(map, used, linked, i)) {//从顶点i出发能够得到一条增广路径
count++;
}
}
return count;
}
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
n = in.nextInt();
m = in.nextInt();
int[][] map = new int[n][m];
int k = in.nextInt(); //二分图中边的数目
for (int i = 0; i < k; i++) {
int a = in.nextInt(); //n部分中的顶点
int b = in.nextInt(); //m部分中顶点
map[a][b] = 1;
}
System.out.println(getMaxNum(map));
}
}