最大权二分匹配—KM算法入门 && 模板
2014-04-16 19:39
344 查看
初学KM算法,以下为搜寻了一下午,找到的比较通俗易懂的讲解(来源于网络):
最大权二分匹配问题就是给二分图的每条边一个权值,选择若干不相交的边,得到的总权值最大。解决这个问题可以用KM算法。
理解KM算法需要首先理解“可行顶标”的概念。可行顶标是指关于二分图两边的每个点的一个值lx[i]或ly[j],保证对于每条边w[i][j]都有lx[i]+ly[j]-w[i][j]>=0。如果所有满足lx[i]+ly[j]==w[i][j]的边组成的导出子图中存在一个完美匹配,那么这个完美匹配肯定就是原图中的最大权匹配。理由很简单:这个匹配的权值之和恰等于所有顶标的和,由于上面的那个不等式,另外的任何匹配方案的权值和都不会大于所有顶标的和。
但问题是,对于当前的顶标的导出子图并不一定存在完美匹配。这时,可以用某种方法对顶标进行调整。调整的方法是:根据最后一次不成功的寻找交错路的DFS,取所有i被访问到而j没被访问到的边(i,j)的lx[i]+ly[j]-w[i][j]的最小值d。将交错树中的所有左端点的顶标减小d,右端点的顶标增加d。经过这样的调整以后:原本在导出子图里面的边,两边的顶标都变了,不等式的等号仍然成立,仍然在导出子图里面;原本不在导出子图里面的边,它的左端点的顶标减小了,右端点的顶标没有变,而且由于d的定义,不等式仍然成立,所以他就可能进入了导出子图里。
初始时随便指定一个可行顶标,比如说lx[i]=max{ w[i][j] | j是右边的点 },ly[i]=0。然后对每个顶点进行类似Hungary算法的find过程,如果某次find没有成功,则按照这次find访问到的点对可行顶标进行上述调整。这样就可以逐步找到完美匹配了。
值得注意的一点是,按照上述d的定义去求d的话需要O(N^2)的时间,因为d需要被求O(N^2)次,这就成了算法的瓶颈。可以这样优化:设slack[j]表示右边的点j的所有不在导出子图的边对应的lx[i]+ly[j]-w[i][j]的最小值,在find过程中,若某条边不在导出子图中就用它对相应的slack值进行更新。然后求d只要用O(N)的时间找到slack中的最小值就可以了。
如果是求最小权匹配,只需要把那个不等式反一下就行了。算法需要作出的改变是:lx的初值为所有临界边中的最小值,find中t反号。
代码模板:
#include <stdio.h>
#include <string.h>
#define M 50
#define INF 0x7fffffff
int n;
int w[M][M]; // 保存权值
int lx[M], ly[M]; // 可行顶标
int pre[M], slack[M]; // 父节点、保存修改量
bool visx[M], visy[M];
int DFS(int x)
{
visx[x] = 1;
for(int y = 1; y <= n; y ++){
if(visy[y])
continue;
int t = lx[x] + ly[y] - w[x][y];
if(t == 0){ // 说明是相等子图
visy[y] = 1;
if(pre[y] == -1 || DFS(pre[y])){
pre[y] = x;
return 1; // 找到增广轨
}
}
else if(slack[y] > t){ // 不在相等子图中slack 取最小的
slack[y] = t;
}
}
return 0; // 没有找到增广轨(说明顶点x没有对应的匹配,与完备匹配(相等子图的完备匹配)不符)
}
int KM()
{
int i, j;
memset (pre, -1, sizeof(pre));
memset (ly, 0, sizeof(ly));
for(i = 1; i <= n; i ++){ // lx初始化为与它关联边中最大的
for(j = 1, lx[i] = -INF; j <= n; j ++){
if(w[i][j] > lx[i])
lx[i] = w[i][j];
}
}
for(int x = 1; x <= n; x ++){
for(i = 1; i <= n; i ++){ // slack 初始化
slack[i] = INF;
}
while(1){
memset (visx, 0, sizeof(visx)); // vis 初始化
memset (visy, 0, sizeof(visy));
if (DFS(x)){ // 若成功(找到了增广轨),则该点增广完成,进入下一个点的增广
break; // 若失败(没有找到增广轨),则需要改变一些点的顶标,使得图中可行边的数量增加。
} // 方法为:将所有在增广轨中(就是在增广过程中遍历到)的X方点的标号全部减去一个常数d,
// 所有在增广轨中的Y方点的标号全部加上一个常数d
int d = INF;
for(i = 1; i <= n; i ++){
if(!visy[i] && d > slack[i])
d = slack[i];
}
for(i = 1; i <= n; i ++){
if(visx[i])
lx[i] -= d;
}
for(i = 1; i <= n; i ++){ // 修改顶标后,要把所有不在交错树中的Y顶点的slack值都减去d
if(visy[i])
ly[i] += d;
else
slack[i] -= d;
}
}
}
int res = 0;
for(i = 1; i <= n; i ++){
if(pre[i] > -1)
res += w[pre[i]][i];
}
return res;
}
int main ()
{
while(scanf("%d", &n) != EOF){
for(int i = 1; i <= n; i ++){
for(int j = 1; j <= n; j ++){
scanf("%d", &w[i][j]);
}
}
int ans = KM();
printf("%d\n", ans);
}
return 0;
}
最大权二分匹配问题就是给二分图的每条边一个权值,选择若干不相交的边,得到的总权值最大。解决这个问题可以用KM算法。
理解KM算法需要首先理解“可行顶标”的概念。可行顶标是指关于二分图两边的每个点的一个值lx[i]或ly[j],保证对于每条边w[i][j]都有lx[i]+ly[j]-w[i][j]>=0。如果所有满足lx[i]+ly[j]==w[i][j]的边组成的导出子图中存在一个完美匹配,那么这个完美匹配肯定就是原图中的最大权匹配。理由很简单:这个匹配的权值之和恰等于所有顶标的和,由于上面的那个不等式,另外的任何匹配方案的权值和都不会大于所有顶标的和。
但问题是,对于当前的顶标的导出子图并不一定存在完美匹配。这时,可以用某种方法对顶标进行调整。调整的方法是:根据最后一次不成功的寻找交错路的DFS,取所有i被访问到而j没被访问到的边(i,j)的lx[i]+ly[j]-w[i][j]的最小值d。将交错树中的所有左端点的顶标减小d,右端点的顶标增加d。经过这样的调整以后:原本在导出子图里面的边,两边的顶标都变了,不等式的等号仍然成立,仍然在导出子图里面;原本不在导出子图里面的边,它的左端点的顶标减小了,右端点的顶标没有变,而且由于d的定义,不等式仍然成立,所以他就可能进入了导出子图里。
初始时随便指定一个可行顶标,比如说lx[i]=max{ w[i][j] | j是右边的点 },ly[i]=0。然后对每个顶点进行类似Hungary算法的find过程,如果某次find没有成功,则按照这次find访问到的点对可行顶标进行上述调整。这样就可以逐步找到完美匹配了。
值得注意的一点是,按照上述d的定义去求d的话需要O(N^2)的时间,因为d需要被求O(N^2)次,这就成了算法的瓶颈。可以这样优化:设slack[j]表示右边的点j的所有不在导出子图的边对应的lx[i]+ly[j]-w[i][j]的最小值,在find过程中,若某条边不在导出子图中就用它对相应的slack值进行更新。然后求d只要用O(N)的时间找到slack中的最小值就可以了。
如果是求最小权匹配,只需要把那个不等式反一下就行了。算法需要作出的改变是:lx的初值为所有临界边中的最小值,find中t反号。
代码模板:
#include <stdio.h>
#include <string.h>
#define M 50
#define INF 0x7fffffff
int n;
int w[M][M]; // 保存权值
int lx[M], ly[M]; // 可行顶标
int pre[M], slack[M]; // 父节点、保存修改量
bool visx[M], visy[M];
int DFS(int x)
{
visx[x] = 1;
for(int y = 1; y <= n; y ++){
if(visy[y])
continue;
int t = lx[x] + ly[y] - w[x][y];
if(t == 0){ // 说明是相等子图
visy[y] = 1;
if(pre[y] == -1 || DFS(pre[y])){
pre[y] = x;
return 1; // 找到增广轨
}
}
else if(slack[y] > t){ // 不在相等子图中slack 取最小的
slack[y] = t;
}
}
return 0; // 没有找到增广轨(说明顶点x没有对应的匹配,与完备匹配(相等子图的完备匹配)不符)
}
int KM()
{
int i, j;
memset (pre, -1, sizeof(pre));
memset (ly, 0, sizeof(ly));
for(i = 1; i <= n; i ++){ // lx初始化为与它关联边中最大的
for(j = 1, lx[i] = -INF; j <= n; j ++){
if(w[i][j] > lx[i])
lx[i] = w[i][j];
}
}
for(int x = 1; x <= n; x ++){
for(i = 1; i <= n; i ++){ // slack 初始化
slack[i] = INF;
}
while(1){
memset (visx, 0, sizeof(visx)); // vis 初始化
memset (visy, 0, sizeof(visy));
if (DFS(x)){ // 若成功(找到了增广轨),则该点增广完成,进入下一个点的增广
break; // 若失败(没有找到增广轨),则需要改变一些点的顶标,使得图中可行边的数量增加。
} // 方法为:将所有在增广轨中(就是在增广过程中遍历到)的X方点的标号全部减去一个常数d,
// 所有在增广轨中的Y方点的标号全部加上一个常数d
int d = INF;
for(i = 1; i <= n; i ++){
if(!visy[i] && d > slack[i])
d = slack[i];
}
for(i = 1; i <= n; i ++){
if(visx[i])
lx[i] -= d;
}
for(i = 1; i <= n; i ++){ // 修改顶标后,要把所有不在交错树中的Y顶点的slack值都减去d
if(visy[i])
ly[i] += d;
else
slack[i] -= d;
}
}
}
int res = 0;
for(i = 1; i <= n; i ++){
if(pre[i] > -1)
res += w[pre[i]][i];
}
return res;
}
int main ()
{
while(scanf("%d", &n) != EOF){
for(int i = 1; i <= n; i ++){
for(int j = 1; j <= n; j ++){
scanf("%d", &w[i][j]);
}
}
int ans = KM();
printf("%d\n", ans);
}
return 0;
}
相关文章推荐
- 最大二分匹配 匈牙利算法模板&&POJ 1469 COURSES
- POJ-2195 Going Home (最小费用最大流初学 && 最大权二分匹配—KM算法)
- 最大流 & 二分匹配模板
- KM算法模板(二分图的最大权匹配)
- POJ 1469 COURSES【匈牙利算法入门 二分图的最大匹配 模板题】
- 【玲珑杯 1047】【二分匹配 KM算法或者费用流】Best couple【定义男女生的距离为最短距离,求匹配之后使得总距离最大】
- POJ 2112 Optimal Milking (二分+最大流/多重匹配) && POJ 2391 Ombrophobic Bovines( 二分+拆点+最大流)
- HDU 3829 Cat VS Dog-二分匹配&最大点集
- POJ 1469 COURSES【匈牙利算法入门 二分图的最大匹配 模板题】
- UVA 11419 SAM I AM(最大二分匹配&最小点覆盖:König定理)
- KM算法 求二分图最大权值的完美匹配 【模板 记录】
- 求最大二分匹配(模板)
- 【二分图匹配入门专题1】F - COURSES poj1469【最大匹配--匈牙利算法模板题】
- 利用匈牙利算法&Hopcroft-Karp算法解决二分图中的最大二分匹配问题 例poj 1469 COURSES
- 最大流,最大权二分匹配,二分匹配算法模板
- 【最大权二分匹配的KM算法】
- POJ 2289--Jamie's Contact Groups【二分图多重匹配问题 &&二分查找最大值的最小化 && 最大流求解】
- HDU 1533 Going Home 最小费用最大流(入门)(模板)或者 二分匹配
- KM算法详解+模板(二分图最大权值匹配)
- poj 3686 The Windy's 二分匹配 KM算法求最小权匹配