今天也大体学了下KM算法,用于求二分图匹配的最佳匹配。ios
何为最佳?咱们能用匈牙利算法对二分图进行最大匹配,但匹配的方式不惟一,若是咱们假设每条边有权值,那么必定会存在一个最大权值的匹配状况,但对于KM算法的话这个状况有点特殊,这个匹配状况是要在彻底匹配(就是各个点都能一一对应另外一个点)状况下的前提。算法
天然,KM算法跟匈牙利算法有类似之处。数组
其算法步骤以下:网络
1.用邻接矩阵(或其余方法也行啦)来储存图,注意:若是只是想求最大权值匹配而不要求是彻底匹配的话,请把各个不相连的边的权值设置为0。ide
2.运用贪心算法初始化标杆。spa
3.运用匈牙利算法找到完备匹配。 code
4.若是找不到,则经过修改标杆,增长一些边。 blog
5.重复3,4的步骤,直到彻底匹配时可结束。 string
一言不合地冒出了个标杆??标杆是什么???io
在解释这个问题以前,咱们先来假设一个很简单的状况,用咱们人类伟大的智能思惟去思考思考。
如上的一个二分图,咱们要求它的最大权值匹配(最佳匹配)
咱们能够思索思索
二分图最佳匹配仍是二分图匹配,因此跟和匈牙利算法思路差很少
二分图是特殊的网络流,最佳匹配至关于求最大(小)费用最大流,因此FF方法也能实现
因此咱们能够把这匈牙利算法和FF方法结合起来
FF方法里面,咱们每次是找最长(短)路进行通流
因此二分图匹配里面咱们也找最大边进行连边!
可是遇到某个点被匹配了两次怎么办?
那就用匈牙利算法进行更改匹配!
这就是KM算法的思路了:尽可能找最大的边进行连边,若是不能则换一条较大的。
因此,根据KM算法的思路,咱们一开始要对边权值最大的进行连线,那问题就来了,咱们如何让计算机知道该点对应的权值最大的边是哪一条?或许咱们能够经过某种方式
记录边的另外一端点,可是呢,后面还要涉及改边,又要记录边权值总和,而这个记录端点方法彷佛有点麻烦,因而KM采用了一种十分巧妙的办法(也是KM算法思想的精髓):
添加标杆(顶标)
是怎样子呢?咱们对左边每一个点Xi和右边每一个点Yi添加标杆Cx和Cy。
其中咱们要知足Cx+Cy>=w[x][y](w[x][y]即为点Xi、Yi之间的边权值)
对于一开始的初始化,咱们对于每一个点分别进行以下操做
Cx=max(w[x][y]);
Cy=0;
而后,咱们能够进行连边,即采用匈牙利算法,只是在判断两点之间是否有连线的条件下,由于咱们要将最大边进行连线,因此原来判断是否有边的条件w[x][y]==0换成了
Cx+Cy==w[x][y]
此时,有一个新的名词——相等子图。
由于咱们经过了巧妙的处理让计算机自动链接边权最大的边,换句话说,其余边计算机就不会连了,也就“不存在”这个图中,但咱们能够随时加上这些“不存在”图中的边。此时这个图能够认为是原图的子图,而且是等效。
这样,计算机在枚举右边的点的时候,知足以上条件,就可以知道这条边是咱们要连的最大的边,就能进行连边了。
因而乎咱们连了AD。
接下来就尴尬了,计算机接下来要连B点的BD,可是D点已经和A点连了,怎么办呢???
根据匈牙利算法,咱们作的是将A点与其余点进行连线,但此时的子图里“不存在”与A点相连的其余边,怎么办呢??
为此,咱们就须要加上这些边!
很明显,咱们添边,天然要加上不在子图中边权最大的边,也就是和子图里这个边权值差最小的边。
因而,咱们再一度引入了一变量d,d=min{Cx[i]+Cy[j]-w[i][j]}
其中,在这个题目里Cx[i]指的是A的标杆,Cy[j]是除D点(即已连点)之外的点的标杆。
随后,对于原先存在于子图的边AD,咱们将A的标杆Cx[i]减去d,D的标杆Cy[d]加上d。
这样,这就保证了原先存在AD边保留在了子图中,而且把不在子图的最大权值的与A点相连的边AE添加到了子图。
由于计算机判断一条边是否在该子图的条件是其两端的顶点的标杆知足
Cx+Cy==w[x][y]
对于原先的边,咱们对左端点的标杆减去了d,对右端点的标杆加上了d,因此最终的结果仍是不变,仍然是w[x][y]。
对于咱们要添加的边,咱们对于左端点减去了d,即Cx[i]=Cx[i]-d;为方便表示咱们把更改后的的Cx[i]视为Cz[i],即Cz[i]=Cx[i]-d;
对于右端点,咱们并无对其进行操做。那这条咱们要添加边的两端点的标号是否知足Cz[i]+Cy[j]=w[i][j]?
由于Cz[i]=Cx[i]-d;d=Cx[i]+Cy[j]-w[i][j];
咱们把d代入左式可得Cz[i]=Cx[i]-(Cx[i]+Cy[j]-w[i][j]);
化简得Cz[i]+Cy[j]=w[i][j]。
知足了要求!即添加了新的边。
值得注意的是,这里咱们只是对于一条边操做,当咱们添加了几条边,要进行如上操做时,要保证原先存在的边不消失,那么咱们就要先求出了d,而后
对于每一个连边的左端点(记做集合S)的每一个点的标号减去了d以后,而后连边的右端点(记做T)加上d,这样就保证了原先的边不消失啦~
实际上这就是一直在寻找着增广路,经过不断修改标杆进行添边实现。
接下来就继续着匈牙利算法,直到彻底匹配完为止。
该算法的正确性就在于 它每次都选择最大的边进行连边
至此,咱们再回顾KM算法的步骤:
1.用邻接矩阵(或其余方法也行啦)来储存图。
2.运用贪心算法初始化标杆。
3.运用匈牙利算法找到完备匹配。
4.若是找不到,则经过修改标杆,增长一些边。
5.重复3,4的步骤,直到彻底匹配时可结束。
是否是清楚了许多??
由于二分图是网络流的一种特殊状况,在网络流里咱们是经过不断的SPFA找到费用最大(小)的路径进行通流,跟这个有点相似。
若是咱们要求边权值最小的匹配呢???
咱们能够把边权值取负值,得出结果后再取相反数就能够了。
至于为何,正负大小相反了嘛~
至此,这大概是我我的的一点点理解了,但愿对您有所帮助。
如有不当之处还请你们指出QwQ。
1 #include<iostream> 2 #include<cstring> 3 #include<cstdio> 4 using namespace std; 5 const int qwq=0x7fffffff; 6 int w[1000][1000]; //w数组记录边权值 7 int line[1000],usex[1000],usey[1000],cx[1000],cy[1000]; //line数组记录右边端点所连的左端点, usex,usey数组记录是否曾访问过,也是判断是否在增广路上,cx,cy数组就是记录点的顶标 8 int n,ans,m; //n左m右 9 bool find(int x){ 10 usex[x]=1; 11 for (int i=1;i<=m;i++){ 12 if ((usey[i]==0)&&(cx[x]+cy[i]==w[x][i])){ //若是这个点未访问过而且它是子图里面的边 13 usey[i]=1; 14 if ((line[i]==0)||find(line[i])){ //若是这个点未匹配或者匹配点能更改 15 line[i]=x; 16 return true; 17 } 18 } 19 } 20 return false; 21 } 22 int km(){ 23 for (int i=1;i<=n;i++){ //分别对左边点依次匹配 24 while (true){ 25 int d=qwq; 26 memset(usex,0,sizeof(usex)); 27 memset(usey,0,sizeof(usey)); 28 if (find(i)) break; //直到成功匹配才换下一个点匹配 29 for (int j=1;j<=n;j++) 30 if (usex[j]) 31 for (int k=1;k<=m;k++) 32 if (!usey[k]) d=min(d,cx[j]+cy[k]-w[j][k]); //计算d值 33 if (d==qwq) return -1; 34 for (int j=1;j<=n;j++) 35 if (usex[j]) cx[j]-=d; 36 for (int j=1;j<=m;j++) 37 if (usey[j]) cy[j]+=d; //添加新边 38 } 39 } 40 ans=0; 41 for (int i=1;i<=m;i++) 42 ans+=w[line[i]][i]; 43 return ans; 44 } 45 int main(){ 46 while (~scanf("%d%d",&n,&m)){ 47 memset(cy,0,sizeof(cy)); 48 memset(w,0,sizeof(w)); 49 memset(cx,0,sizeof(cx)); 50 for (int i=1;i<=n;i++){ 51 int d=0; 52 for (int j=1;j<=n;j++){ 53 scanf("%d",&w[i][j]); 54 d=max(d,w[i][j]); //此处顺便初始化左边点的顶标 55 } 56 cx[i]=d; 57 } 58 memset(line,0,sizeof(line)); 59 printf("%d\n",km()); 60 } 61 return 0; 62 }