二分图:又叫二部图,图G中顶点集V可以分成互不相交的子集(X,Y),并且图中的每一条边所关联的点分别属于两个不同的顶点集,则图G叫二分图。(不含奇环)
二分图的匹配:给定一个二分图G的子图M,M的边集中任意两条边都不依附于同一个顶点(点单独配对),则称M是一个匹配。
二分图的最大匹配:边数最大的一个匹配就是二分图的最大匹配。
看上去二分图匹配好像没有什么用途,但以下三个定理会有大用处:
1.二分图的最小点覆盖(x或y中的都行) = 最大匹配;
2.二分图的最大独立集 = 顶点数 - 最大匹配;
(最大独立集是从图中找出最多的顶点数并且顶点两两之间没有边)
3.有向无环图的最小路径覆盖 = 顶点数- 最大匹配。
二分图最大匹配——匈牙利算法
算法的思路是不停的寻找增广路(交错轨),并增加匹配的个数;增广轨的形式:第一条边未参与匹配,第二条边参与匹配,第三条边未参与匹配,...,最后一条边未参与匹配,显然它有奇数条边。那么对于这样一条路径,可以将它变为:第一条边参与匹配,第二条边未参与匹配,第三条边参与匹配,...,最后一条边参与匹配。匹配依然合法,但匹配数增加了一对。可以证明,当找不到增广轨时,就得到了最大匹配,这就是匈牙利算法的思路。
const int N = 50; const int M = 50; bool use[M]; //记录y中节点是否使用 int link[M]; //记录当前与y节点相连的x的节点:i未加入匹配时为link[i]==0 bool g[N][M]; //记录连接x和y的边,如果i和j之间有边则为1,否则为0 int gn,gm; //二分图中x和y中点的数目 bool find(int v) //对x中的结点v,找增广路径。 { int i; for(i = 1; i <= gm; i++) //对结点v发出的每条边 { if(g[v][i] && !use[i]) { use[i] = true; //如果y中的结点i还没有加入到匹配中(link[i]==0),可直接连线; //或者y加入到了匹配中,根据link[i]找到x中的结点v,根据v发出的边寻找另外一个未加入匹配结点y //找到就记录到link中,返回true;找不到返回false(即找增广路径)。 if(link[i] == 0 || find(link[i])) { link[i] = v; return true; } } } return false; } int MaxMatch() //找二分图的最大匹配数。 { int i,ans=0; for(i = 1; i <= gn; i++) { memset(use,0,sizeof(use)); if(find(i)) ans++; } return ans; }
最小路径覆盖
在有向无环图G中,找出最小的路径条数,使之覆盖所有顶点,且任意一个顶点有且只有一条路径与之关联。从这可以看出:
1.一个单独的顶点是一条路径;
2.如果存在一条路径p1,p2,...,pk,其中p1为起点,pk为终点,那么在覆盖图中,p1,p2,...,pk这些顶点不再与其它的顶点之间存在有向边。
最小路径覆盖就是找出最小的路径条数,使之覆盖所有点。
最小路径覆盖数 = P - 最大匹配数。(G必须是无环的有向图)
其中最大匹配数的求法是把G中的每个顶点pi分成两个顶点pi与pi',如果G中存在一条pi到pj的边,那么在二分图G'中就有一条连接pi'与pj'的无向边;这里pi'就是G中pi的出边,pj'就是G中pj的一条入边。
可以这样理解:
匹配数为0,那么图G中不存在有向边,显然有最小覆盖数=P-0=P。
如果在G'中增加一条匹配边pi'-->pj',那么在图G中就有pi-->pj在一条路径上,于是覆盖的路径数减1,如此增加,直到二分图的最大匹配,也就求出了最小路径覆盖。