[图论] 二分图匹配(匈牙利算法)

介绍部分转载于维基百科:

匈牙利算法是众多用于解决线性任务分配问题的算法之一,是用来解决二分图最大匹配问题的经典算法,可以在多项式时间内解决问题,由美国数学家Harold Kuhn 于1955年提出。此算法之所以被称作匈牙利算法是因为算法很大一部分是基于以前匈牙利数学家Dénes Kőnig和Jenő Egerváry的工作之上创建起来的.

问题简介:

设G=(V,E)是一个无向图。如顶点集V可分区为两个互不相交的子集V1,V2之并,并且图中每条边依附的两个顶点都分属于这两个不同的子集。则称图G为二分图。二分图也可记为G=(V1,V2,E)。


给定一个二分图G,在G的一个子图M中,M的边集{E}中的任意两条边都不依附于同一个顶点,则称M是一个匹配。 选择这样的子集中边数最大的子集称为图的最大匹配问题(maximal matching problem)


如果图的所有顶点都与某匹配中的一条边相关联,则称此匹配为完全匹配,也称作完备,完美匹配。


算法描述:


  求最大匹配的一种显而易见的算法是:先找出全部匹配,然后保留匹配数最多的。但是这个算法的时间复杂度为边数的指数级函数。因此,需要寻求一种更加高效的算法。下面介绍用增广路求最大匹配的方法(称作匈牙利算法,由数学家Harold Kuhn于1955年提出)。

  增广路的定义(也称增广轨或交错轨):

  若P是图G中一条连通两个未匹配顶点的路径,并且属于M的边和不属于M的边(即已匹配和待匹配的边)在P上交替出现,则称P为相对于M的一条增广路径。(M为一个匹配)

  由增广路的定义可以推出下述三个结论:

  1-P的路径长度必定为奇数,第一条边和最后一条边都不属于M。

  2-将M和P进行异或操作(去同存异)可以得到一个更大的匹配M’。

  3-M为G的最大匹配当且仅当不存在M的增广路径。

  算法轮廓:

  (1)置M为空

  (2)找出一条增广路径P,通过异或操作获得更大的匹配M’代替M

  (3)重复(2)操作直到找不出增广路径为止

时间空间复杂度:
  时间复杂度 邻接矩阵:最坏为O(n^3) 邻接表:O(mn)   空间复杂度 邻接矩阵:O(n^2) 邻接表:O(m+n)


比较直观得了解匹配过程可以参考博客:http://blog.csdn.net/dark_scope/article/details/8880547

个人备忘录:二分图中,左边的顶点个数假设为un,右边的顶点个数假设为vn,g[i][j]表示左边定点i和右边顶点j之间有边相连,用linked[i]表示与右边第i个顶点相匹配的是左边哪个顶点。首先遍历左边定点,为每个左边顶点去匹配右边的顶点,如果发现一个可行匹配,就标记为右边顶点已被匹配,该左边定点的匹配过程到此结束,然后去遍历下一个左边顶点,如果与该左边顶点有边的右边顶点已经被匹配了,假设与它匹配的左边顶点是X,那么就要重新为X去匹配,看能不能把该右边顶点让给当前需要匹配的左边定点。。有点绕。。举个例子 比如左边顶点a 和右边 c,d有边,左边顶点b和右边顶点c有边,一开始匹配的是a,a和c匹配,a的匹配结束,然后去匹配b,发现和b有边相连的右边顶点c已经被匹配了,而与之匹配的顶点是左边顶点a,那么就重新对a进行匹配,看能不能a去匹配另一个顶点,而把c这个顶点“腾出空位来”,发现,a也可以和d匹配,可以腾出一个空来使b和c相匹配。这就是匈牙利算法比较关键的地方。


模板:hdu 2063

/*
匈牙利算法解决二分图最大匹配问题
采用的邻接矩阵的形式,g[i][j]
maxn代表一边最多的顶点数,un代表左边顶点数,vn代表右边顶点数
linked[i]代表右边的第i个点与左边哪个点相匹配
*/

#include <iostream>
#include <stdio.h>
#include <algorithm>
#include <string.h>
using namespace std;
const int maxn=502;
int un,vn;//左边顶点数,右边顶点数
int g[maxn][maxn];//邻接矩阵来存储边
int linked[maxn];//右边的点和左边的哪个点匹配
bool vis[maxn];

bool dfs(int u)
{
    for(int v=1;v<=vn;v++)//遍历右边顶点
    {
        if(g[u][v]&&!vis[v])
        {
            vis[v]=1;
            if(!linked[v]||dfs(linked[v]))//右边顶点还没有被匹配,或者已经匹配的前面左边顶点可以去寻找另一个匹配而把该右边顶点“腾出空位”让给当前左边顶点u
            {
                linked[v]=u;
                return true;
            }
        }
    }
    return false;
}

int hungary()
{
    int ans=0;
    memset(linked,0,sizeof(linked));
    for(int u=1;u<=un;u++)//遍历左边顶点,去寻找与之匹配的右边顶点
    {
        memset(vis,0,sizeof(vis));
        if(dfs(u))//去找u能不能匹配,如果可以匹配的话,ans++
            ans++;
    }
    return ans;
}

int k,m,n;//m为题目中要求输入的左边顶点数,n为题目中要求输入的右边顶点数
int main()
{
    while(scanf("%d",&k)!=EOF&&k)
    {
        scanf("%d%d",&m,&n);
        un=m;vn=n;
        memset(g,0,sizeof(g));
        int u,v;
        for(int i=1;i<=k;i++)
        {
            scanf("%d%d",&u,&v);//u和v有边
            g[u][v]=1;
        }
        printf("%d\n",hungary());
    }
    return 0;
}



你可能感兴趣的:(匈牙利算法)