1.问题描述
我们先来了解一下相关图论的概念:
- 二分图:又称二部图。是图论中的一种特殊模型。设G=(V,E)是一个无向图,如果结点集V可分割为两个互不相交的子集(V1,V2),并且图中的每条边(i,j),所关联的两个结点i和j分别属于这两个不同的结点集,则称G为一个二分图。
- 匹配:在图论中,一个匹配(matching)是一个边的集合,其中任意两条边都没有公共结点。如图所示就算是一个匹配:
- 最大匹配(maximum matching):一个图的所有匹配中,边数最多匹配称为这个图的最大匹配。
求二分图最大匹配可以用最大流(Maximal Flow)或者匈牙利算法(Hungarian Algorithm),但目前我们先不用这两种方法来实现求解二分图最大匹配,而是用简单的dfs实现:
现在有这么有个例子:
A同学和伙伴们去游乐场玩,准备要坐过山车。过山车一排有两个座位,保险起见,每个女生必须与一个男生坐在一起。但是,每个人都希望和自己认识的人坐在一起。比如说,1号女生和1号男生认识,因此他们想坐在一起。另外1号女生与2号男生也互相认识,因此他们也可以坐在一起。像这样的关系还有:2号女生认识2号和3号的男生,3号男生认识1号男生。请问怎么安排座位才能让最多的人满意呢?这就是典型简单的二分图最大匹配。
2.算法设计
- 首先从任意一个未被匹配的点u开始,从点u的边中任意选一条边(假设这条便是u->v)开始配对。如果此时点v还没有被配对,则配对成功,此时便找到了一条增广路(只不过这条增广路比较简单)。如果此时v已经被配对,那就要尝试进行“连锁反应”。如果尝试成功了,则找到一条增广路,此时需要更新原来的配对关系。这里要用一个数组match来记录配对关系,比如点v与点u配对了,救济做match[v]=u。配对成功后,记得将配对数+1.配对的过程我们可以通过深度优先搜索来实现。
- 如果刚才所选的边配对失败,要从点u的边中再重新选一条边,进行尝试,直到u配对成果,或者尝试过点u所有的边为止。
- 接下来继续对剩下没有配对的点一一进行配对,直到所有的点都尝试完毕,找不到新的增广路为止。
- 输出配对数。
3.源代码
#include <iostream> #include <cstdio> #include <cstdlib> #include <cstring> #include <algorithm> using namespace std; const int N=111; int e[N][N];//存储边 int matched[N];//判断是否已经匹配 int marked[N]; //判断是否已经访问 int n; int m; int dfs(int u) { for(int i=1;i<=n;i++) { if(marked[i]==0 && e[u][i]==1) //没有被访问过且可达 { marked[i]=1; if(matched[i]==0 || dfs(matched[i])) //如果没有配对,将i与u配对。 //用dfs遍历一编i,看有没有配对。如果没有配对,返回0 { matched[i]=u; return 1;//配对成功返回1.不用进行dfs } } } return 0; } int main() { int edge1; int edge2; int sum=0; cout << "请输入n个顶点m条边" << endl; cin >> n >> m; cout << "请输入所有可进行配对的顶点: "<< endl; for (int i=1;i<=m;i++) { cin >> edge1 >> edge2; e[edge1][edge2]=1;//说明这两个顶点之间存在边。 } //初始化匹配数组。 for (int i=1;i<=n;i++) { matched[i]=0; } for(int i=1;i<=n;i++) { for(int j=1;j<=n;j++) { marked[j]=0; //清空上次搜索访问过的记录 } if(dfs(i)) { sum++; } } cout << "最大匹配数是:"<< endl; cout << sum << endl; return 0; }