本人的第一个匈牙利算法
之前离散数学中讲的二分图都没有什么概念了,拿出来学学
二分图就是这么一个图,图中的点集分为两个子集,子集中的点没有相连,只和另一子集中的点相连。二分图也分有向图和无向图。 最基本的二分图的题目是求最大匹配,匹配是二分图中边的集合,且集合中的任意两条边没有公共点,包含边数最多的匹配就是最大匹配。 求最大匹配的方法常见的是 匈牙利算法,在相关资料中论述的有很多了,这里不再赘述,用网络流也可以解决匹配问题。
这里要细讲的是与二分图相关的其他问题, 例如 最小覆盖问题, 最大独立集问题, 最小路径覆盖问题, 最小带权覆盖问题 ,最小带权独立集问题。
最小覆盖问题: 图的覆盖:寻找一个点集,使得图中每一条边至少有一点在该点集中。
二分图的最小覆盖 = 最大匹配
这是为什么呢? 让我们来分析一下, 这里有一个很好的模拟过程: 将右边未匹配上的点依次加标记, 然后标记 左边与右边标记的相连的点 ,左边的点一定是最大匹配上的点,不然找到的一定不是最大匹配, 标记最大匹配且左边已经标记了的点 。 最后 取左边标记了的点 和右边未标记的点就组成了最小覆盖。 现在最大匹配的点一定都覆盖了,左边是最大匹配,右边不是的边也覆盖了, 右边是最大匹配,左边不是这种情况也覆盖了。所有情况都覆盖了 , 而左边标记了的点 由最大匹配对应的右边的点 与右边未标记的(为标记一定是最大匹配上的) 加起来就是最大匹配数。
图的独立集:寻找一个点集,其中任意两点在图中无对应边。
二分图的最大独立集 = 图的点数 - 最大匹配数
这个比较好理解, 把最小覆盖的点去了,则图中一条边也没有了。剩下的就是最大独立集。
最小路径覆盖问题:用尽量少的不相交简单路径覆盖有向无环图的所有顶点。
将每个顶点分为两个,分别在X集合和Y集合中,如果存在有向边(a,b),对应在二分图中有(Xa,Yb)
最小路径数 = 节点数 - 最大匹配
参考 http://hi.baidu.com/desmoon/blog/item/ce5de032bc6d63f11a4cff16.html
对于任意图:
|最小边覆盖|+|最大匹配|=|V|
二分图的最大匹配=最小点覆盖数
对于二分图:
以下数值等价.
最大匹配
最小点覆盖
|V|-最大独立集(二分图or有向无环图)
|V|-最小边覆盖数
|V|-最小路径覆盖数(有向无环图)
|V|-最小路径覆盖数/2(无向图)
(上面括号里有有向无环图的,均是将一个点拆成两个点连边匹配)
由于任意图的那几个几乎用不到于是这里只贴二分图的定义
最小点覆盖:理解为点覆盖边,即用最小的点覆盖所有的边。(若一条边的其中一个端点被选用,这条边就被覆盖了)
最大独立集:求一个最大的点集,里面的点不存在任何的边相连。
最小边覆盖:理解为边覆盖点,用最少的边把图中的点全部覆盖。
最小路径覆盖:用最少的路径把图中的所有点覆盖。
另外:最大独立集与最小覆盖集互补。
推广到有权的形式也一样,即最大点权独立集与最小点权覆盖集互补
求最小点权覆盖集可以这样求:
先对图黑白染色,然后向白色的点放X部,黑色的点放Y部。
1、连边[S,i],容量等于i的点权。(对于二分图的X集)
2、连边[i,T],容量等于i的点权。(对于二分图的Y集)
3、对于有边的i和j连边[i,j](i∈X,j∈Y),容量为INF
最后得出的最大流就是最小点权覆盖,实际上是最小割与之对应。
对于有向无环图:
最大反链=|V|-最大匹配
http://hi.baidu.com/edwardmj/blog/item/b5fc0419bd3661f1af513325.html
http://poj.org/problem?id=3041 POJ Asteroids
代码
/* Author : yan * Question : POJ 3041 Asteroids * Data : Tuesday, December 21 2010 */ //第一次写匈牙利算法,学习 #include<stdio.h> #define MAX 501 #define bool _Bool #define true 1 #define false 0 bool map[MAX][MAX]; bool valid[MAX]; int match[MAX]; int n; int findPath(int p) { int _i; for(_i=1;_i<=n;_i++) { if(valid[_i]==0 && map[p][_i]) { valid[_i]=1; if(match[_i]==-1 || findPath(match[_i])) { match[_i]=p; return true; } } } return false; } int MAX_Match() { int ans=0; int _i; for(_i=1;_i<=n;_i++) { memset(valid,0,sizeof(valid)); if(findPath(_i)) ans++; } return ans; } int main() { //freopen("input","r",stdin); int k,i; int u,v; memset(match,-1,sizeof(match)); scanf("%d %d",&n,&k); for(i=0;i<k;i++) { scanf("%d %d",&u,&v); map[u][v]=true; } printf("%d",MAX_Match()); return 0; }
自己改写稍微规范的匈牙利算法
/* Author : yan * Question : POJ 3041 Asteroids * Data : Tuesday, December 21 2010 */ #include<stdio.h> #define Left_MAX 501 #define Right_MAX 501 #define bool _Bool #define true 1 #define false 0 bool map[Left_MAX][Right_MAX];//定义左右两边元素是否有连接 bool used[Right_MAX]; int link[Right_MAX];//link[]记录与右边元素连接的元素,-1表示没有连接 int left_num,right_num;//定义左右两边元素 int findPath(int p) { int _i; for(_i=1;_i<=right_num;_i++) { if(used[_i]==0 && map[p][_i]) { used[_i]=1; if(link[_i]==-1 || findPath(link[_i])) { link[_i]=p; return true; } } } return false; } int MAX_Match() { int ans=0; int _i; for(_i=1;_i<=left_num;_i++) { memset(used,0,sizeof(used)); if(findPath(_i)) ans++; } return ans; } int main() { //freopen("input","r",stdin); int n,k,i; int u,v; memset(link,-1,sizeof(link)); scanf("%d %d",&n,&k); left_num=n,right_num=n; for(i=0;i<k;i++) { scanf("%d %d",&u,&v); map[u][v]=true; } printf("%d",MAX_Match()); return 0; }