当前位置:   article > 正文

二分图最大匹配---匈牙利算法BFS 实现_匈牙利算法bfs实现

匈牙利算法bfs实现

        二分图指的是这样一种图,其所有顶点可以分成两个集合X和Y,其中X或Y中任意两个在同一集合中的点都不相连,所有的边关联在两个顶点中,恰好一个属于集合X,另一个属于集合Y。给定一个二分图G,M为G边集的一个子集,如果M满足当中的任意两条边都不依附于同一个顶点,则称M是一个匹配。图中包含边数最多的匹配称为图的最大匹配。

       二分图的最大匹配有两种求法,第一种是最大流;第二种就是我现在要讲的匈牙利算法。这个算法说白了就是最大流的算法,但是它跟据二分图匹配这个问题的特点,把最大流算法做了简化,提高了效率。

      增广路径的定义(也称增广轨或交错轨):
  若P是图G中一条连通两个未匹配顶点的路径,并且属M的边和不属M的边(即已匹配和待匹配的边)在P上交替出现,则称P为相对于M的一条增广路径。
由增广路径的定义可以推出下述4个结论:
        1-P的路径长度必定为奇数,第一条边和最后一条边都不属于M。
        2-P上所有第奇数条边都不在M中,所有第偶数条边都出现在M中。
   3-P经过取反操作可以得到一个更大的匹配M’。所谓“取反”即把P上所有第奇数条边(原不在M中)加入到M中,并把P中所有第偶数条边(原在M中)从M中删除,则新的匹配数就比原匹配数多了1个。(增广路顾名思义就是使匹配数增多的路径)
        4-M为G的最大匹配当且仅当不存在相对于M的增广路径。

   最大流算法的核心问题就是找增广路径(augment path)。匈牙利算法也不例外,它的基本模式就是:
     初始时最大匹配为空
      while 找得到增广路径
               do 把增广路径加入到最大匹配中去
      可见和最大流算法是一样的。但是这里的增广路径就有它一定的特殊性。(注:匈牙利算法虽然根本上是最大流算法,但是它不需要建网络模型,所以图中不再需要源点和汇点,仅仅是一个二分图。每条边也不需要有方向。)

      算法的思路是不停的找增广路径, 并增加匹配的个数,增广路径顾名思义是指一条可以使匹配数变多的路径,在匹配问题中,增广路径的表现形式是一条"交错路径",也就是说这条由图的边组成的路径, 它的第一条边是目前还没有参与匹配的,第二条边参与了匹配,第三条边没有..最后一条边没有参与匹配,并且始点和终点还没有被选择过。这样交错进行,显然他有奇数条边。那么对于这样一条路径,我们可以将第一条边改为已匹配,第二条边改为未匹配...以此类推。也就是将所有的边进行"反色",容易发现这样修改以后,匹配仍然是合法的,但是匹配数增加了一对。另外,单独的一条连接两个未匹配点的边显然也是交错路径。可以证明。当不能再找到增广路径时,就得到了一个最大匹配,这也就是匈牙利算法的思路。

3个重要结论:

最小点覆盖数: 最小覆盖要求用最少的点(X集合或Y集合的都行)让每条边都至少和其中一个点关联。可以证明:最少的点(即覆盖数)=最大匹配数
最小路径覆盖=最小路径覆盖=|N|-最大匹配数
用尽量少的不相交简单路径覆盖有向无环图G的所有结点。解决此类问题可以建立一个二分图模型。把所有顶点i拆成两个:X结点集中的i和Y结点集中的i',如果有边i->j,则在二分图中引入边i->j',设二分图最大匹配为m,则结果就是n-m。
二分图最大独立集=顶点数-二分图最大匹配
在N个点的图G中选出m个点,使这m个点两两之间没有边,求m最大值。
如果图G满足二分图条件,则可以用二分图匹配来做.最大独立集点数 = N - 最大匹配数。

例题:

http://poj.org/problem?id=1469  COURSES

有了匈牙利算法的基础,该题就是一道非常简单的题目了:该题给出P门课程,N个学生,问能否从中选出P个学生,使每个学生上不同的课,且每个课程有一个学生。典型的二分图匹配的问题,我们只要计算最大二分图匹配数,如果和课程数相同就输出YES,否则输出NO。

  1. //poj_1469
  2. /*==================================================*\
  3. | 二分图匹配(匈牙利算法DFS 实现)
  4. | INIT: g[][]邻接矩阵;
  5. | 优点:实现简洁容易理解,适用于稠密图,DFS找增广路快。
  6. | 找一条增广路的复杂度为O(E),最多找V条增广路,故时间复杂度为O(VE)
  7. ==================================================*/
  8. #include<stdio.h>
  9. #include<memory.h>
  10. bool g[110][310]; //邻接矩阵,true代表有边相连
  11. bool flag,visit[310]; //记录V2中的某个点是否被搜索过
  12. int match[310]; //记录与V2中的点匹配的点的编号
  13. int p,n; //二分图中左边、右边集合中顶点的数目
  14. // 匈牙利算法
  15. bool dfs(int u)
  16. {
  17. for (int i = 1; i <= n; ++i)
  18. {
  19. if (g[u][i] && !visit[i]) //如果节点i与u相邻并且未被查找过
  20. {
  21. visit[i] = true; //标记i为已查找过
  22. if (match[i] == -1 || dfs(match[i])) //如果i未在前一个匹配M中,或者i在匹配M中,但是从与i相邻的节点出发可以有增广路径
  23. {
  24. match[i] = u; //记录查找成功记录,更新匹配M(即“取反”)
  25. return true; //返回查找成功
  26. }
  27. }
  28. }
  29. return false;
  30. }
  31. int main(void)
  32. {
  33. int i,j,k,t,v,ans;
  34. scanf("%d",&t);
  35. while (t--)
  36. {
  37. scanf("%d %d", &p, &n);
  38. for (i = 1; i <= p; i++)
  39. {
  40. for (j = 1; j <= n; j++)
  41. g[i][j] = false;
  42. }
  43. for (i = 1; i <= n; i++)
  44. match[i] = -1;
  45. flag = true;
  46. for (i = 1; i <= p; i++)
  47. {
  48. scanf("%d",&k);
  49. if (k == 0)
  50. flag = false;
  51. while (k--)
  52. {
  53. scanf("%d",&v);
  54. g[i][v] = true;
  55. }
  56. }
  57. if (flag)
  58. {
  59. ans = 0;
  60. for (i = 1; i <= p; i++)
  61. {
  62. memset(visit,false,sizeof(visit)); //清空上次搜索时的标记
  63. if( dfs(i) ) //从节点i尝试扩展
  64. ans++;
  65. }
  66. if (ans == p)
  67. puts("YES");
  68. else
  69. puts("NO");
  70. }
  71. else
  72. puts("NO");
  73. }
  74. return 0;
  75. }

http://poj.org/problem?id=1274

题意描述:
农夫约翰上个星期刚刚建好了他的新牛棚,他使用了最新的挤奶技术。不幸的是,由于工程问题,每个牛栏都不一样。第一个星期,农夫约翰随便地让奶牛们进入牛栏,但是问题很快地显露出来:每头奶牛都只愿意在她们喜欢的那些牛栏中产奶。上个星期,农夫约翰刚刚收集到了奶牛们的爱好的信息(每头奶牛喜欢在哪些牛栏产奶)。一个牛栏只能容纳一头奶牛,当然,一头奶牛只能在一个牛栏中产奶。
输入:第一行 两个整数,N (0 <= N <= 200) 和 M (0 <= M <= 200) 。N 是农夫约翰的奶牛数量,M 是新牛棚的牛栏数量。
第二行到第N+1行 一共 N 行,每行对应一只奶牛。第一个数字 (Si) 是这头奶牛愿意在其中产奶的牛栏的数目 (0 <= Si <= M) 。后面的 Si 个数表示这些牛栏的编号。牛栏的编号限定在区间 (1..M) 中,在同一行,一个牛栏不会被列出两次。
输出:
只有一行。输出一个整数,表示最多能分配到的牛栏的数量。

给出奶牛们的爱好的信息,计算最大分配方案。 

  1. #include<stdio.h>
  2. #include<memory.h>
  3. #define MAX 202
  4. bool flag,visit[MAX]; //记录V2中的某个点是否被搜索过
  5. int match[MAX]; //记录与V2中的点匹配的点的编号
  6. int cow, stall; //二分图中左边、右边集合中顶点的数目
  7. int head[MAX];
  8. struct edge
  9. {
  10. int to,next;
  11. }e[3000];
  12. int index;
  13. void addedge(int u,int v)
  14. { //向图中加边的算法,注意加上的是有向边
  15. //u为v的后续节点既是v---->u
  16. e[index].to=v;
  17. e[index].next=head[u];
  18. head[u]=index;
  19. index++;
  20. }
  21. // 匈牙利(邻接表)算法
  22. bool dfs(int u)
  23. {
  24. int i,v;
  25. for(i = head[u]; i != 0; i = e[i].next)
  26. {
  27. v = e[i].to;
  28. if(!visit[v]) //如果节点v与u相邻并且未被查找过
  29. {
  30. visit[v] = true; //标记v为已查找过
  31. if(match[v] == -1 || dfs(match[v])) //如果i未在前一个匹配M中,或者i在匹配M中,但是从与i相邻的节点出发可以有增广路径
  32. {
  33. match[v] = u; //记录查找成功记录,更新匹配M(即“取反”)
  34. return true; //返回查找成功
  35. }
  36. }
  37. }
  38. return false;
  39. }
  40. int MaxMatch()
  41. {
  42. int i,sum=0;
  43. memset(match,-1,sizeof(match));
  44. for(i = 1 ; i <= cow ; ++i)
  45. {
  46. memset(visit,false,sizeof(visit)); //清空上次搜索时的标记
  47. if( dfs(i) ) //从节点i尝试扩展
  48. {
  49. sum++;
  50. }
  51. }
  52. return sum;
  53. }
  54. int main(void)
  55. {
  56. int i,j,k,ans,m;
  57. while (scanf("%d %d",&cow, &stall)!=EOF)
  58. {
  59. memset(head,0,sizeof(head)); //切记要初始化
  60. index = 1;
  61. for (i = 1; i <= cow; ++i)
  62. {
  63. scanf("%d",&k);
  64. for (j = 0; j < k; ++j)
  65. {
  66. scanf("%d",&m);
  67. addedge(i , m);
  68. }
  69. }
  70. ans = MaxMatch();
  71. printf("%d\n",ans);
  72. }
  73. return 0;
  74. }



声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop】
推荐阅读
相关标签
  

闽ICP备14008679号