二分匹配的匈牙利算法 51NOD2006(文末)

来源:互联网 发布:简单的单片机设计作品 编辑:程序博客网 时间:2024/05/20 12:50

参考及图片来源:https://comzyh.com/blog/archives/148/

二分图:简单来说,如果图中点可以被分为两组,并且使得所有边都跨越组的边界,则这就是一个二分图。准确地说:把一个图的顶点划分为两个不相交集 U  和 V ,使得每一条边都分别连接U 、 V  中的顶点。如果存在这样的划分,则此图为一个二分图。二分图的一个等价定义是:不含有「含奇数条边的环」的图。

(即  如果题目中的 元素 能够 被 分成 两组不同的点集,就是可以用二分图表示的,每个组内的点互不相连,边只在两个点集间产生 )

如下图:

 


最大匹配:一个图所有匹配中,所含匹配边数最多的匹配,称为这个图的最大匹配。下图 是一个最大匹配,它包含 4 条匹配边。

完美匹配:如果一个图的某个匹配中,所有的顶点都是匹配点,那么它就是一个完美匹配。上图  是一个完美匹配。显然,完美匹配一定是最大匹配(完美匹配的任何一个点都已经匹配,添加一条新的匹配边一定会与已有的匹配边冲突)。但并非每个图都存在完美匹配。

求二分图最大匹配可以用最大流(Maximal Flow)  (更复杂) 或者匈牙利算法(Hungarian Algorithm)

下面介绍匈牙利算法:

http://www.nocow.cn/index.php/Translate:USACO/stall4

以此题为例:

算法最基本轮廓:


  1. 置边集M为空(初始化,谁和谁都没连着)
  2. 选择一个新的原点寻找增广路
  3. 重复(2)操作直到找不出增广路径为止(2,3步骤构成一个循环)

模拟步骤如上图所示(过于详细,大牛请无视):

  1. 初始化(清空)
  2. 从A所连接的点中找到一个未在本次循环中搜索过的点2,并将2标记为搜索过,因为2没有被连接过,匹配A2
  3. 结束上次,开始新的循环,将所有点标记为未搜索过
  4. 搜索B,找到一个未在本次循环中搜索过的点2,标记为搜索过
  5. 发现2被匹配过,从2的父亲A寻找增广路,递归搜索A{从A所连接的点中找到一个未在本次循环中搜索过的点5(1已经标记为绿色),将5标记为搜索过,因为5没有被匹配过,匹配A5}找到增广路(此处为增广路的关键
  6. 结束上次,开始新的循环,将所有点标记为未搜索过
  7. 搜索C,找到一个未在本次循环中搜索过的点1,并将1标记为搜索过,发现1未被匹配过,匹配C1
  8. 结束上次,开始新的循环,将所有点标记为未搜索过
  9. 搜索D,找到一个未在本次循环中搜索过的点1,并将1标记为搜索过,发现1被匹配过,递归搜索1的源C寻找增广路
  10. {搜索C,找到一个未在本次循环中搜索过的点5,标记为搜索过,发现5被匹配,进一步返现没有其他可连接点,返回找不到增广路}返回第9步
  11. 搜索D,找到一个未在本次循环中搜索过的点2,发现2被匹配,递归搜索2的源B寻找增广路
  12. {搜索B,找到一个未在本次循环中搜索过的点3,并将3标记为搜索过,发现3未被匹配,匹配B3返回找到}既然B另寻新欢,匹配D2
  13. 结束上次,开始新的循环,将所有点标记为未搜索过,递归搜索D寻找增广路
  14. 搜索E,找到一个未在本次循环中搜索过的点2,并将2标记为搜索过,发现2被匹配过,递归搜索2的源D寻找增广路
  15. {搜索D,发现1,5均被匹配过,返回找不到增广路}
  16. E无其他可连接节点,放弃E,E后无后续节点,已经遍历A-E,结束算法

例题:  51NOD 2006  (看了code就明白)

https://www.51nod.com/onlineJudge/questionCode.html#problemId=2006&noticeId=293729

#include <bits/stdc++.h>using namespace std;const int AX = 200;int g[AX][AX];int linker[AX];bool used[AX];int m,n;bool dfs( int u ){int v;for( v = m+1 ; v <= n ;v ++ ){                   if( g[u][v] && !used[v] ){                  //如果u 和 v 能够相连 并且 点未被访问used[v] = true;//标记点if( linker[v] == -1 || dfs(linker[v]) ){  //如果点没有连接边 或者 已连接 但回溯 到其相连的点发现那个点仍能链接别的点,就将这个点的边变换linker[v] = u;   //链接新边return true;       //返回真,匹配成功}}}return false;         }int xyl(){int res = 0;int u;memset(linker,-1,sizeof(linker));      //首先各边初始化为未连接for( u = 1 ; u <= m ; u++ ){ memset(used,0,sizeof(used));   //每次都将点设置为为访问过if( dfs(u) ) res++;            //用DFS查找匹配}return res;}int main(){ios_base::sync_with_stdio(false);cin.tie(0);cin>>m>>n;int x,y;while( cin>>x>>y && x != -1 && y != -1 ){g[x][y] = 1;                   //加入题目给出的边}int ans = xyl();                   //调用匈牙利算法if( ans == 0  ) printf("No Solution!\n");else cout<<ans<<endl;return 0;}