博主内推:阿里菜鸟网络春招 【部门直推】【海量实习岗位&正式岗位】 0.二分图
二分图的概念
二分图的判定
二分图博客推荐
1.KM算法初步
增广路径
- 如果是深搜,x2找到y0匹配,但发现y0已经被x1匹配了,于是就深入到x1,去为x1找新的匹配节点,结果发现x1没有其他的匹配节点,于是匹配失败,x2接着找y1,发现y1可以匹配,于是就找到了新的增广路径。
- 如果是宽搜,x1找到y0节点的时候,由于不能马上得到一个合法的匹配,于是将它做为候选项放入队列中,并接着找y1,由于y1已经匹配,于是匹配成功返回了。
匈牙利算法
匈牙利算法步骤
匈牙利算法博客推荐
KM算法
KM算法步骤
KM算法标杆(又名顶标)的引入
- 所以我们可以把这匈牙利算法和FF算法结合起来。这就是KM算法的思路了:尽量找最大的边进行连边,如果不能则换一条较大的。
-
- FF算法里面,我们每次是找最长(短)路进行通流,所以二分图匹配里面我们也按照FF算法找最大边进行连边!
- 但是遇到某个点被匹配了两次怎么办?那就用匈牙利算法进行更改匹配!
- 所以,根据KM算法的思路,我们一开始要对边权值最大的进行连线。
- 那问题就来了,我们如何让计算机知道该点对应的权值最大的边是哪一条?或许我们可以通过某种方式记录边的另一端点,但是呢,后面还要涉及改边,又要记录边权值总和,而这个记录端点方法似乎有点麻烦。
-
- 于是KM采用了一种十分巧妙的办法(也是KM算法思想的精髓):添加标杆(顶标)
- 我们对左边每个点Xi和右边每个点Yi添加标杆Cx和Cy。其中我们要满足Cx+Cy>=w[x][y](w[x][y]即为点Xi、Yi之间的边权值)
- 对于一开始的初始化,我们对于每个点分别进行如下操作:Cx=max(w[x][y]); Cy=0;
KM流程详解
- 初始化可行顶标的值 (设定lx,ly的初始值)
- 用匈牙利算法寻找相等子图的完备匹配
- 若未找到增广路则修改可行顶标的值
- 重复(2)(3)直到找到相等子图的完备匹配为止
- 于是乎我们连了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]=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];
- 满足了要求!即添加了新的边。
- 重复进行上述流程。(匈牙利算法以及FF算法的结合)
KM算法博客推荐
2.DFS版本的KM算法
/*==================================================*\ | 二分图匹配(匈牙利算法DFS 实现) | INIT: graph[][]邻接矩阵; | CALL: res = dfsHungarian (); | 优点:实现简洁容易理解,适用于稠密图,DFS 找增广路快。 | 找一条增广路的复杂度为O(E),最多找V条增广路,故时间复杂度为O(VE) | 算法简述: | 从二分图中找出一条路径来,让路径的起点和终点都是还没有匹配过的点, | 并且路径经过的连线是一条没被匹配、一条已经匹配过,再下一条又没匹配这样交替地出现。 | 找到这样的路径后,显然路径里没被匹配的连线比已经匹配了的连线多一条, | 于是修改匹配图,把路径里所有匹配过的连线去掉匹配关系,把没有匹配的连线变成匹配的。 | 这样匹配数就比原来多1个。不断执行上述操作,直到找不到这样的路径为止。 \*==================================================*/ #include<iostream> #include<memory.h> using namespace std; #define MAXN 10 int graph[MAXN][MAXN]; int match[MAXN]; int visitX[MAXN], visitY[MAXN]; int nx, ny; bool findPath( int u ) { visitX[u] = 1; for( int v=0; v<ny; v++ ) { if( !visitY[v] && graph[u][v] ) { visitY[v] = 1; if( match[v] == -1 //第一次,用到了短路计算,否则findPath(-1)会出问题 || findPath(match[v]) )//这里就表示深度优先遍历 不撞南山头不回 不见黄河心不死 { match[v] = u; return true; } } } return false; } int dfsHungarian() { int res = 0; memset( match, -1, sizeof(match) ); for( int i=0; i<nx; i++ ) { memset( visitX, 0, sizeof(visitX) ); memset( visitY, 0, sizeof(visitY) ); if( findPath(i) ) res++; } return res; }
/*==================================================*\ | 二分图匹配(匈牙利算法BFS 实现) | INIT: graph[][]邻接矩阵; | CALL: res = bfsHungarian (); | 优点:适用于稀疏二分图,边较少,增广路较短。 | 匈牙利算法的理论复杂度是O(VE) \*==================================================*/ #include<iostream> #include<memory.h> using namespace std; #define MAXN 10 int graph[MAXN][MAXN]; //在bfs中,增广路径的搜索是一层一层展开的,所以必须通过prevX来记录上一层的顶点 //chkY用于标记某个Y顶点是否被目前的X顶点访问尝试过。 int matchX[MAXN], matchY[MAXN], prevX[MAXN], chkY[MAXN]; int queue[MAXN]; int nx, ny; int bfsHungarian() { int res = 0; int qs, qe; memset( matchX, -1, sizeof(matchX) ); memset( matchY, -1, sizeof(matchY) ); memset( chkY, -1, sizeof(chkY) ); for( int i=0; i<nx; i++ ) { if( matchX[i] == -1 ) //如果该X顶点未找到匹配点,将其放入队列。 { qs = qe = 0; queue[qe++] = i; prevX[i] = -1; //并且标记,它是路径的起点 bool flag = 0; while( qs<qe && !flag ) { int u = queue[qs]; for( int v=0; v<ny&&!flag; v++ ) { if( graph[u][v] && chkY[v]!=i ) //如果该节点与u有边且未被访问过 { chkY[v] = i; //标记且将它的前一个顶点放入队列中,也就是下次可能尝试这个顶点看能否为它找到新的节点 queue[qe++] = matchY[v]; if( matchY[v] >= 0 ) prevX[matchY[v]] = u; else //到达了增广路径的最后一站 { flag = 1; int d=u, e=v; while( d!=-1 ) //一路通过prevX找到路径的起点 { int t = matchX[d]; matchX[d] = e; matchY[e] = d; d = prevX[d]; e = t; } } } } qs++; } if( matchX[i] != -1 ) res++; } } return res; }