小撸--二分图

讲二分图 之前 我先提下关于它的各种基础概念  在了解一个新的算法 应该有必要将关于它的概念 有所了解

1.什么是二分图?

:二分图 又叫做二部图 是图论中的一种特殊模型 设G=(V,E)是一个无向图 如果顶点V可以分割为两个互不相交的子集(A,B) 并且图中的每条边(i,j)所关联的两个顶点i和j分别属于这个不同的顶点集(i in A j in B) 则称G为一个二分图(这是官方的定义 感觉也是最详细的)

2.什么是匹配?

匹配是 基于二分图上提出的概念。 给定一个二分图G  M为G边集的一个子集 如果M满足当中的任意两条边都不依附于同一个顶点 则称M是一个匹配

3.什么是二分图 极大匹配?

是指在当前已完成的匹配下,无法再通过增加未完成匹配的边的方式来增加匹配的边数

4.什么是二分图 最大匹配?

所有极大匹配当中边数最大的一个匹配   同通俗来说就是----在匹配的基础上选择边数最大的子集称为最大匹配

5.增广路(增广轨 交错轨)是什么?

若P是图G中一条连通两个未匹配顶点的路径 并且属于M的边喝不属于M的边(即已匹配和未匹配的边)在P上交替出现 则称P为相对于M的一条增广路径
(sample: 有A B集合 增广路由A中一个点通向B中一个点 再由B中这个点通向A中一个点 就是这样交替进行下去.)

由增广路的定义引申出的三个结论:
1.P的路径长度必定为奇数 第一条边喝最后一条边都不属于M
2.不断寻找增广路可以得到一个更大的匹配M‘ 直到找不到更多的增广路
3.M为G的最大匹配当且仅当不存在M的增广路径

6.最小点覆盖数 是什么?

即要求用最少的点(A B任意一集合)让每条边至少和其中一个点相关联
可以证明: 最小点覆盖数 == 最大匹配数(证明过程这里不给出 希望读者自行查阅)

7.独立集是什么?

图G中两两互不相邻的顶点构成的集合

可以证明: 最大独立集 == 顶点数 - 二分图最大匹配(同上 证明过程不予给出 请自行查阅)

8.最小路径覆盖是什么?

简而言之-就是找出最小的路径条数 使之成为P的一个路径覆盖
一个PXP的有向图中 路径覆盖就是在图中找一些路径使之覆盖了图中所有的顶点 且任何一个顶点有且只有一条路径与之关联(如果把这些路径中的每条路径从它们起始点走到它的终点 那么恰好经过图中的每个顶点一次仅且一次) 如果不考虑图中存在回路 那么每条路径就是一个弱连通子集

得出的结论:
1.一个单独的顶点是一条路径
2.如果存在一路径p1,p2,......pk 其中p1 为起点 pk为终点 那么在覆盖图中顶点p1,p2,......pk不再与其它的顶点之间存在有向边

可以证明: 最小路径覆盖 == |P|-二分图最大匹配 (同上)

看完上面这么一大堆概念 是不是和我一样头晕了。。。  还是先去撸一把吧?

继续... 这里我们给出一道例题来更好的理解:

题目链接 : http://acm.nyist.net/JudgeOnline/problem.php?pid=239

这题 应该是最简单的 二分图最大匹配应用题 题意也很好转换 即是求 二分图最大匹配

同样 对于图的信息存储 这里同样可以用邻接表 或 邻接矩阵

先去上课了~~

 耽搁了一天了  继续。。。。

这边 我介绍一种 二分图中常用的经典算法---匈牙利算法  还有一种算法是-Hopcroft-Carp 它的时间复杂度比前者更小 更适合大数据

它的实现方式还是很简单的 如果 有2个顶点集 分别为A  B

我们只需要通过遍历A中的所有顶点 将它与B集合进行匹配 这个过程 我们可以通过DFS来实现。

因为 它的代码很容易理解 除了其中的反向查找增广路的代码片段 但是 你可以自己在纸上模拟演算下 会便于你理解很多

我这边给出关于它的 邻接表 与 邻接矩阵的实现方式 并附上注释

这边 值得一提的是: 我用邻接矩阵 TLE   可能是写的太渣了吧 如果你AC了  不妨留言告诉我。

 1 #include <cstdio>
 2 #include <cstring>
 3 using namespace std;
 4 
 5 #define num 520
 6 int n;
 7 int matrix[num][num];//建立邻接矩阵来存储信息
 8 bool vis[num];//标记点是否被访问过
 9 int linker[num];//标记该点的前一个节点是那个
10 
11 bool dfs(int u)//从U集合开始向V集合寻找匹配
12 {
13     for(int v=1;v<=n;v++)
14     {
15         if( !vis[v] && matrix[u][v] )//V集合的点与U集合该点有边相邻 并且这个点不能被访问过
16         {
17             vis[v]=true;
18             if( !linker[v] || dfs( linker[v] ) )//反向找增广路 如果能成功 就可以使匹配边+1
19             {
20                 linker[v]=u;
21                 return true;
22             }
23         }
24     }
25     return false;
26 }
27 
28 int getSum()
29 {
30     int count=0;
31     memset( linker,0,sizeof(linker) );// 这里 与下面不同 只需要初始化一次 
32     for(int u=1;u<=n;u++)
33     {
34         memset( vis , false , sizeof(vis) );//这里 注意 每一次A集合中的顶点去与B集合去匹配的过程 都需要更新点的访问
35         if( dfs(u) )
36         {
37             count++;
38         }
39     }
40     return count;
41 }
42 
43 int main()
44 {
45     int t;
46     int m;
47     int x,y;
48     while(~scanf("%d",&t))
49     {
50         while(t--)
51         {
52             scanf("%d %d",&n,&m);
53             for(int i=0;i<=n;i++)
54             {
55                 for(int j=0;j<=n;j++)
56                 {
57                     matrix[i][j]=0;
58                 }
59             }
60             while(m--)
61             {
62                 scanf("%d %d",&x,&y);
63                 matrix[x][y]=matrix[y][x]=1;//无向图 2点相邻
64             }
65             printf("%d
",getSum() );
66         }
67     }
68 }
View Code
 1 #include <cstdio>
 2 #include <cstring>
 3 #include <vector>
 4 using namespace std;
 5 
 6 //如果你不清楚 下面有关vector的函数 请去查询资料 这是一个非常重要的容器 并且它的作用真的很大
 7 #define num 520
 8 vector<int>mp[num];//邻接表存储
 9 int linker[num];//记录该点的前驱.
10 bool vis[num];//标记是否被访问
11 int n;
12 
13 void inital(int n)//这里不能遗忘 每次需要清空邻接表
14 {
15     for(int i=1;i<=n;i++)
16     {
17         mp[i].clear();
18     }
19 }
20 bool dfs(int u)
21 {
22     for(int i=0;i<mp[u].size();i++)//第u行共有的元素数量
23     {
24         if( !vis[ mp[u][i] ] ) //该点未被匹配
25         {
26             vis[ mp[u][i] ]=true; //标记
27             if( !linker[ mp[u][i] ] || dfs( linker[ mp[u][i] ] ) )
28             {
29                 linker[ mp[u][i] ]=u; //找增广路 反向
30                 return true;
31             }
32         }
33     }
34     return false;
35 }
36 
37 int getSum()
38 {
39     int count=0;
40     memset( linker ,0, sizeof(linker) );
41     for(int u=1; u<=n ;u++ )
42     {
43         memset( vis ,false, sizeof(vis) );
44         if( dfs(u) )
45         {
46             count++;
47         }
48     }    
49     return count;
50 }
51 
52 int main()
53 {
54     int t,m;
55     int x,y;
56     while(~scanf("%d",&t))
57     {
58         while(t--)
59         {
60             scanf("%d %d",&n,&m);
61             inital(n);
62             while(m--)
63             {
64                 scanf("%d %d",&x,&y);
65                 mp[x].push_back(y);//这里 只需要将Y顶点压入X顶点即可
66             }
67             printf("%d
",getSum() );
68         }
69     }
70 }
View Code


至于 上文提过的 另外一种算法 等我掌握了 再来写。。

long long way to go

just follow your heart
原文地址:https://www.cnblogs.com/radical/p/3666783.html