理论: 图论(10): 二分图的最大匹配 (匈牙利算法)

概述

二分图的最大匹配问题例子:

一共有N 个学生和P 门课程, 一个学生可以任选其中的一门或者多门课程,
问是否可以达成:
1.每个学生代表的都是不同的课;
2.每门课都有不同的代表;

我们看到这就是一个典型的二分图, 学生代号为L点集, 科目代号为R点集;

这里写图片描述

现在我们要求的就是最多能形成多少对匹配, 然后比较学生数、科目数、匹配对数这三者的关系即可。

现在的问题就是求二分最大匹配的问题

在这里这种问题有三种常见的解决方法: 匈牙利算法、 最大流算法、 Dinic算法。 在这里我们先讲前两种。

现在我们遇到了一个有向有圈图的问题 无法求出讲解过程的拓扑序, 这也就导致了在下面的讲解中会用到后几篇博客的知识

匈牙利算法

匈牙利算法可以认为是深度优先搜索(DFS)的一个应用。(匈牙利算法有dfs和bfs两种)

在这里我们要明确, 匈牙利算法是用来求二分图的最大匹配的, 他的核心问题就是求增广路。

匈牙利算法的时间复杂的为O(V * E) V是开始点集的点数, E是中间的边数

我们再来复习一下增广路的特性:

(1)有奇数条边。
(2)起点在二分图的左半边,终点在右半边。
(3)路径上的点一定是一个在左半边,一个在右半边,交替出现。
(4)整条路径上没有重复的点。
(5)起点和终点都是目前还没有配对的点,而其它所有点都是已经配好对的。
(6)路径上的所有第奇数条边都不在原匹配中,所有第偶数条边都出现在原匹配中。
(7)最后,也是最重要的一条,把增广路径上的所有第奇数条边加入到原匹配中去,并把增广路径中的所有第偶数条边从原匹配中删除(这个操作称为增广路径的取反),则新的匹配数就比原匹配数增加了1个。

二分图的最大匹配问题是很多图论的基础问题 具体看上一篇博客:
传送门 加粗的文字标出

上代码:

//不定长数组模拟邻接链表实现
//linker[i] = j 表示 i 和 j 连接
//used表示在当前寻找增广路的时候是否寻找过该点
bool dfs(int u)
{
    for (int i = 0; i < G[u].size(); i++)
    {
        int v = G[u][i];
        if (!used[v])//在此次递归的时候V并没有使用过
        {
            used[v] = true;
            if (linker[v] == -1 || dfs(linker(v)))
            {//将v与u链接的条件是   v节点没有使用  ||   现在连接V节点的那个节点除了V之外还有其他选择
                linker[v] == u;
                return true;
            }
        }
    }
    return false;//遍历全部的Y点集的点  仍找不到增广路
}

int hungary(void)
{
    int res;
    memset(linker, -1, sizeof(linker));
    for (int u = 0; u < V; u++)
    {
        memset(used, 0, sizeof(used));
        if (dfs(u))
            res++;
    }
    return res;
}

最大流算法

我们在点集X的左侧加上一份起始点S, 在右侧点集Y的右侧加上一个终点T, 现在二分图的最大匹配就成了如下图的最大流图(每条边的权值为1; 注意是全为1)

这里写图片描述

现在我们使用ford-fulkerson算法的特殊情况(边的权值全为1)

详细的分析见图论的下几篇博客最大流的博客 现在直接上代码

#include<queue>
#include<vector>
#include<cstring>
#include<cstdio>

using namespace std;

vector<int>G[1000];
int match[1000];
bool used[1000];
int V;

void add_edge(int u, int v)
{
    G[u].push_back(v);
    G[v].push_back(u);
}

bool dfs(int v)
{
    used[v] = true;
    for (int i = 0; i < G[v].size(); i++)
    {
        int u = G[v][i];
        int w = match[u];
        if (w < 0 || !used[w] && dfs(w))// 这一步和匈牙利算法很像  但是表示的意义不同
        {//详见最大流算法
            match[v] = u;
            match[u] = v;
            return true;
        }
    }
    return false;
}

int bipartite_mathing()
{
    int res = 0;
    memset(match, 0, sizeof(match));
    for (int v = 0; v < V; v++)
    {
        if (match[v] < 0)
        {
            memset(used, 0, sizeof(used));
            if (dfs(v))
                res++;
        }
    }
    return res;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值