二分图匹配模板(匈牙利算法、hopcroft-carp算法、KM算法)

匈牙利算法(Hungarian method)是由匈牙利数学家Edmonds于1965年提出,因而得名。匈牙利算法是基于Hall定理中充分性证明的思想,它是二分图匹配最常见的算法,该算法的核心就是寻找增广路径,它是一种用增广路径求二分图最大匹配的算法。

二分图的一些性质:

1.最小点覆盖=最大匹配。

定义:点覆盖是图中一些点的集合,且对于图中所有的边,至少有一个端点属于点覆盖,点数最小的覆盖就是最小点覆盖

2.最小边覆盖=最大独立集=图中点的个数-最大匹配。

定义:边覆盖是图中一些边的集合,且对于图中所有的点,至少有一条集合中的边与其相关联,边数最小的覆盖就是最小边覆盖。

3.最小路径覆盖=原图的节点数-新图最大匹配。

(1).有向无环图最小不相交路径覆盖

·定义:用最少的不相交路径覆盖所有顶点。

·定理:把原图中的每个点V拆成Vx和Vy,如果有一条有向边A->B,那么就加边Ax->By。这样就得到了一个二分图,最小路径覆盖=原图的节点数-新图最大匹配。

(2).有向无环图最小可相交路径覆盖

.定义:用最小的可相交路径覆盖所有顶点。

·算法:先用floyd求出原图的传递闭包,即如果a到b有路,那么就加边a->b。然后就转化成了最小不相交路径覆盖问题。


性质转自:https://blog.csdn.net/pbihao/article/details/54912439


匈牙利算法,每一次寻找一条增广路来达到多增加一条边的效果。求二分图最大匹配

代码:

//时间复杂度:O(N*E)
int line[N][N];  //i,j之间是否存在关系,即可以匹配,记得初始化
int used[N];     //每一轮寻找增广路径时第i个妹子是否被使用过
int Next[N];     //Next[i]=x代表第i个妹子和x号男生是匹配的,记得初始化
int n,m;

bool Find(int x)
{
    for(int i=1;i<=m;i++)
    {
        if(line[x][i]&&!used[i])
        {
            used[i] = 1;             //第i个妹子被使用了
            if(Next[i]==0||Find(Next[i]))   //判断第i个妹子是否有了男朋友,没有的话就可以匹配,有的话就把next[i]号男生转移掉再匹配
            {
                Next[i] = x;
                return true;
            }
        }
    }
    return false;
}

int maxMatch()
{
    memset(Next,0,sizeof(Next));
    int sum = 0;
    for(int i=1;i<=n;i++)
    {
        memset(used,0,sizeof(used));
        if(Find(i))
            sum++;
    }
    return sum;
}


2.hopcroft-carp算法

一次通过bfs来找到多条增广路,来更新所有点。求二分图最大匹配

//时间复杂度O(sqrt(n)*E)

const int MAXN=550;// 最大点数
int bmap[MAXN][MAXN];//二分图
int cx[MAXN];//cx[i]表示左集合i顶点所匹配的右集合的顶点序号
int cy[MAXN]; //cy[i]表示右集合i顶点所匹配的左集合的顶点序号
int dis;
int n,m;
int dx[MAXN],dy[MAXN];  //dx表示到x的距离,dy表示到y的距离
int used[MAXN];        //在每次增广中是否使用i点

bool SearchPath()   //bfs寻找增广路集
{
    queue<int>Q;
    dis = INF;  //存每一次增广的距离
    memset(dx,-1,sizeof(dx));
    memset(dy,-1,sizeof(dy));
    for(int i=1;i<=n;i++)
    {
        if(cx[i]==-1)  将未遍历的节点入队,并初始化次节点距离为0
        {
            Q.push(i);
            dx[i] = 0;
        }
    }
    while(!Q.empty())
    {
        int u = Q.front();
        Q.pop();
        if(dx[u]>dis)
            break;
        //取右边的节点
        for(int v=1;v<=m;v++)
        {
            if(bmap[u][v]&&dy[v]==-1)
            {
                dy[v] = dx[u] + 1;  //v的距离为u的对应距离+1
                if(cy[v]==-1)       //如果该点未匹配,增广路形成
                    dis = dy[v];
                else                //如果该点已匹配,那么接着往下搜
                {
                    dx[cy[v]] = dy[v] + 1;
                    Q.push(cy[v]);
                }
            }
        }
    }
    return dis!=INF;
}

bool DFS(int u)
{
    for(int v=1;v<=m;v++)
    {
        //如果该点没有被使用过,并且距离为上一节点+1
        if(!used[v]&&bmap[u][v]&&dy[v]==dx[u]+1)
        {
            used[v] = 1;  //标记使用过该点
            //如果该点已经被匹配了并且为最后一个匹配点,那么这条路径不是增广路,即这条路的结点已经匹配
            if(cy[v]!=-1&&dy[v]==dis)
                continue;
            if(cy[v]==-1||DFS(cy[v]))
            {
                cy[v]=u,cx[u]=v;
                return true;
            }
        }
    }
    return false;
}

int MaxMatch()
{
    int sum = 0;
    memset(cx,-1,sizeof(cx));
    memset(cy,-1,sizeof(cy));
    while(SearchPath()) //当存在增广路,继续松弛
    {
        memset(used,0,sizeof(used));   //每一次的右边的点是否用过
        for(int i=1;i<=n;i++)
        {
            if(cx[i]==-1&&DFS(i))    //如果当前这个点没连过,且能找到增宽路
                sum++;
        }
    }
    return sum;
}

3.KM算法。

btw:怎么求最大边权积……首先我们只会做最大边权和,那么怎么化和为积呢?我想到了log函数的一个性质:log X+log Y = log X*Y 。所以只要把原边权取log,就可以通过求新图的最大边权和得到最大边权积了……

/*  KM算法,求带权二分图最大权匹配,复杂度O(nx*nx*ny)
 *  匹配的原则是:lx+ly只与权重相同的边匹配,若是找不到边匹配,对此条路径的所有左边顶点-1,
                   右边顶点+1,再进行匹配,若还是匹配不到,重复+1和-1操作。
 *  若求最小权匹配,可将路径权值取相反数,结果取相反数
 *  点的编号从0开始
 */

const int N = 310;
const int INF = 0x3f3f3f3f;
int nx,ny;    //两边的点数
int g[N][N];  //二分图描述,g[i][j]代表i到j之间的权值,最大匹配初始化为-INF表示无边,同理最小匹配初始化为INF
int linker[N]; //记录y链接的哪个x,-1代表无
int lx[N],ly[N];//y中各点匹配状态,x,y中的点标号
int slack[N];        //slack为修改量
bool visx[N],visy[N];

bool DFS(int x)
{
    visx[x] = true;
    for(int y = 0; y < ny; y++)
    {
        if(visy[y])continue;     //用过了就继续
        int tmp = lx[x] + ly[y] - g[x][y];
        if(tmp == 0)   //符合匹配要求
        {
            visy[y] = true;
            if(linker[y] == -1 || DFS(linker[y]))
            {
                linker[y] = x;
                return true;
            }
        }
        else if(slack[y] > tmp)
            slack[y] = tmp;
    }
    return false;
}
int KM()
{
    memset(linker,-1,sizeof(linker));
    memset(ly,0,sizeof(ly));      //初始右边期望值都是0
    for(int i = 0;i < nx;i++)
    {
        lx[i] = -INF;             //左边期望值为最大边权
        for(int j = 0;j < ny;j++)
            if(g[i][j] > lx[i])
                lx[i] = g[i][j];
    }

    //开始解决左边匹配问题
    for(int x = 0;x < nx;x++)
    {
        for(int i = 0;i < ny;i++)
            slack[i] = INF;        //因为取最小值,初始化为无穷大
        while(true)
        {
            // 为左边解决归宿问题的方法是 :如果找不到就降低期望值,直到找到为止

            // 记录每轮匹配中左右两边是否被尝试匹配过
            memset(visx,false,sizeof(visx));
            memset(visy,false,sizeof(visy));
            if(DFS(x))    //找到匹配,退出
                break;
            //没找到,降低期望值
            //最小可降低期望值
            int d = INF;
            for(int i = 0;i < ny;i++)
                if(!visy[i] && d > slack[i])
                    d = slack[i];
            for(int i = 0;i < nx;i++)
                if(visx[i])
                    lx[i] -= d;
            for(int i = 0;i < ny;i++)
            {
                if(visy[i])ly[i] += d;
                else slack[i] -= d;
            }
        }
    }
    int res = 0;
    for(int i = 0;i < ny;i++)
        if(linker[i] != -1)
            res += g[linker[i]][i];
    return res;
}

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值