二分图最大匹配——匈牙利算法详解

这篇具有很好参考价值的文章主要介绍了二分图最大匹配——匈牙利算法详解。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

零、前言

关于二分图的基本知识见:二分图及染色法判定


一、红娘牵线

一位红娘近日遇到一群暧昧男女,被请求成全他们,经验丰富的红娘观察到一名男生可能有多名青睐的女生,一名女生也可能有多名青睐的男生,但是出于道德伦理要求,显然只能两两男女配对,为了尽可能使大家满意,她要尽可能地成全多对男女。经过观察,她发现这些男女间地暧昧关系如下(连线代表互相青睐):

二分图最大匹配——匈牙利算法详解,数据结构与算法,算法,数据结构,图论,图搜索算法

红娘根据经验快速地进行了一次配对,男一配女二,男儿配女四,男三配女三。

(下图红色连线代表配对,此时女一和男四没有配对)

二分图最大匹配——匈牙利算法详解,数据结构与算法,算法,数据结构,图论,图搜索算法

配对的三对男女自然很满意,此时女一和男四悻悻地来找红娘,说他们两个怎么办,红娘看二人不愿凑合都想有心仪的归宿,男四只愿跟女二在一起,女一只愿跟男三在一起。

红娘于是只得回头看已经配对的三对男女,发现男一似乎对女三也有意思,但是女三已经跟男三配对了,于是红娘私底下找到男三,问他愿不愿意将女三让给男一,自己可以帮他跟女一牵线,男三一看这敢情好,直接答应,于是男三的对象变为了女一,男一的对象变为了女三,男四趁虚而入,和女二配对,于是有了下面的局面:

二分图最大匹配——匈牙利算法详解,数据结构与算法,算法,数据结构,图论,图搜索算法

至此,每个人都有和自己的心仪对象之一配了对,中间虽有ntr波折,但结局皆大欢喜。


二、二分图最大匹配

2.1概念

“任意两条边都没有公共端点”的边的集合被称为图的一组匹配。在二分图中,包含边数最多的一组匹配被称为二分图的最大匹配

上面的红娘牵线其实就是二分图的最大匹配的形象示例。

我们称匹配的边为匹配边匹配边的两个端点为匹配点,相应的自然有了非匹配边非匹配点的概念。

2.2交替路

从一个非匹配点出发,依次经过非匹配边、匹配边、非匹配边形成的路径叫交替路

2.3增广路

从一个未匹配点出发,走交替路,若能到达另一个未匹配点,则这条交替路称为增广路

增广路显然有如下性质:

  1. 长度len为奇数
  2. 路径上第1、3、5……len条边是非匹配边,第2、4……len - 1条边是匹配边

正因为以上性质,如果我们把路径上所有边的状态取反,原来的匹配边变成非匹配边原来的非匹配边变成匹配边,那么得到的新的边集仍然是一组匹配,并且匹配边数+1.

从而得到以下推论:

二分图的一组匹配是最大匹配,当且仅当图中不存在包含该匹配的增广路。

2.4匈牙利算法

**匈牙利算法(Hungary Algorithm)**又称牛头人算法增广路算法,用于计算二分图的最大匹配。

2.4.1算法原理

算法流程十分简单:

  1. 设匹配边集S = Ø,即所有边都是非匹配边
  2. 找到增广路path,将增广路上所有边状态取反,得到更大的匹配S‘
  3. 重复2,直到没有增广路

算法的关键在于如何找到增广路

我们将二分图的点分为左部节点和右部节点,匈牙利算法依次尝试给给每一个左部节点u寻找一个匹配的右部节点v。右部节点v能和左部节点u匹配需要满足以下两个条件之一:

  1. v本身就是非匹配点
    1. 此时u~v为长度为1的增广路
  2. v已经跟左部节点u’匹配,但是从u‘出发能找到另一个右部节点v’和其匹配。
    1. 此时uvu‘~v’就是一条增广路

在具体的程序实现中,我们采用深度优先搜索的框架,递归的从u出发去找增广路,若找到,则在回溯时,正好把路径上的匹配状态取反。另外,可以用全局标记数组来维护节点的访问情况,避免重复搜索。

匈牙利算法的正确性基于贪心策略,它的一个重要特点是:当一个节点成为匹配点后,至多因为找到增广路而更换匹配对象,但是绝不会再变回非匹配点。
对于每个左部节点,寻找增广路最多遍历整张二分图一次。因此,该算法的时间复杂度为O(nm),其中n为点数目,m为边数目。

2.4.2算法示例

有二分图如下,左部节点1、2、3、4,右部节点1、2、3、4

二分图最大匹配——匈牙利算法详解,数据结构与算法,算法,数据结构,图论,图搜索算法

左1匹配右1

二分图最大匹配——匈牙利算法详解,数据结构与算法,算法,数据结构,图论,图搜索算法

左2尝试匹配右1失败

二分图最大匹配——匈牙利算法详解,数据结构与算法,算法,数据结构,图论,图搜索算法

左2匹配右3

二分图最大匹配——匈牙利算法详解,数据结构与算法,算法,数据结构,图论,图搜索算法

左3匹配右2

二分图最大匹配——匈牙利算法详解,数据结构与算法,算法,数据结构,图论,图搜索算法

左4尝试匹配右3,递归左2尝试匹配右1失败

二分图最大匹配——匈牙利算法详解,数据结构与算法,算法,数据结构,图论,图搜索算法

左2继续尝试匹配右4,成功找到增广路

二分图最大匹配——匈牙利算法详解,数据结构与算法,算法,数据结构,图论,图搜索算法

回溯时把增广路取反,左4得以匹配右3

二分图最大匹配——匈牙利算法详解,数据结构与算法,算法,数据结构,图论,图搜索算法

2.4.3代码实现
bool dfs(int u)
{
    for (int i = head[u]; ~i; i = edges[i].nxt)
    {
        int v = edges[i].v;
        if (vis[v])
            continue;
        vis[v] = 1;
        if (!match[v] || dfs(match[v]))
        {
            match[v] = u;
            return true;
        }
    }
    return false;
}
//main
for (int i = 1; i <= n; i++)
{
	memset(vis, 0, sizeof(vis));
	if (dfs(i))
		cnt++;
}

3.5完备匹配

给定一张二分图, 其左部、右部节点数相同,均为N个节点。如果该二分图的最大匹配包含N条匹配边,则称该二分图具有完备匹配

3.6多重匹配

给定一张包含N个左部节点、M个右部节点的二分图。从中选出尽量多的边,使第i(1≤i≤N) 个左部节点至多与kli条选出的边相连,第j(1≤j≤M)个右部节点至多与kri条选出的边相连。该问题被称为二分图的多重匹配

当kli = krj=1时,上述问题就简化为二分图最大匹配。因此,多重匹配是一个广义的“匹配”问题,每个节点可以与不止一-条“匹配”边相连,但不能超过一个给定的限制。

3.6.1多重匹配的解决方案
  1. 拆点。把第i个左部节点拆成kli 个不同的左部节点,第j个右部节点拆成krj个右部节点。对于原图中的每条边(i , j), 在 i 拆成的所有节点与 j 拆成的所有节点之间连边。然后求解二分图最大匹配。
  2. 如果所有的kli=1,或者所有的krj=1,即只有一侧是“多重”匹配,不妨设左部是“多重”的,那么可以直接在匈牙利算法中让每个左部节点执行kli次dfs。
  3. 在第2种方案中,当然也可以交换左右两部,设“右部”是多重的,修改匈牙利算法的实现,让右部节点可以匹配krj次,超过匹配次数后,就要依次尝试递归当前匹配的krj个左部节点,看能否找到增广路。
  4. 网络流。这是最一般也是最高效的解决方法。

3.OJ练习

二分图匹配的模型有两个要素
1.节点能分成独立的两个集合,每个集合内部有0条边。
2.每个节点只能与1条匹配边相连。
我们把它简称为“0要素”和“1要素”。在把实际问题抽象成二分图匹配时,我们就要寻找题目中具有这种“0”和“1”性质的对象,从而发现模型构建的突破口。

3.1模板

P3386 【模板】二分图最大匹配 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

洛谷模板题,检验以下自己的匈牙利算法板子是否正确,以便于在后续问题中使用。

#include <iostream>
#include <cstring>
#include <queue>
#include <algorithm>
#include <string>
using namespace std;
using pii = pair<int, int>;
#define N 510
#define M 50010
struct edge
{
    int v, nxt;
} edges[M << 1];
int head[N], match[N]{0}, idx = 0, n, m, e, a, b, cnt = 0;
bool vis[N];
void addedge(int u, int v)
{
    edges[idx] = {v, head[u]};
    head[u] = idx++;
}
bool dfs(int u)
{
    for (int i = head[u]; ~i; i = edges[i].nxt)
    {
        int v = edges[i].v;
        if (vis[v])
            continue;
        vis[v] = 1;
        if (!match[v] || dfs(match[v]))
        {
            match[v] = u;
            return true;
        }
    }
    return false;
}
signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    // freopen("in.txt", "r", stdin);
    // freopen("out.txt", "w", stdout);
    memset(head, -1, sizeof(head));
    cin >> n >> m >> e;
    for (int i = 1; i <= e; i++)
    {
        cin >> a >> b;
        addedge(a, b);
    }
    for (int i = 1; i <= n; i++)
    {
        memset(vis, 0, sizeof(vis));
        if (dfs(i))
            cnt++;
    }
    cout << cnt;
    return 0;
}
3.2棋盘覆盖

372. 棋盘覆盖 - AcWing题库

首先对于一个矩阵而言,我们根据行列坐标相加的奇偶性可以对其进行二染色,并且任何一个格子和其四个方向上的相邻格子颜色不同。

二分图最大匹配——匈牙利算法详解,数据结构与算法,算法,数据结构,图论,图搜索算法

这样我们就可以将问题抽象为二分图匹配问题。

0要素:同色格子之间无边 1要素:每个格子只能被一张骨牌覆盖

一个骨牌一定是覆盖了两个颜色不同的方格,我们按照颜色将格子分为左部点和右部点,被骨牌覆盖的两个左右部点即为一个匹配,求最多的骨牌数目就是求最大匹配。

基本上还是板子题,由于数据量很小所以用了邻接矩阵,由于有的格子不能放置,所以要加个条件。

奇数格子还是偶数格子作为左部点没有区别。

直接看代码:

#include <iostream>
#include <cstring>
#include <queue>
#include <algorithm>
#include <string>
using namespace std;
using pii = pair<int, int>;
#define N 110
#define M 50010
int n, t, a, b, cnt = 0, dir[5]{1, 0, -1, 0, 1};

pii match[N][N];
bool g[N][N], vis[N][N];
bool dfs(int x, int y)
{
    for (int i = 0; i < 4; i++)
    {
        int nx = x + dir[i], ny = y + dir[i + 1];
        int pos = (nx - 1) * n + ny;
        if (nx < 1 || ny < 1 || nx > n || ny > n || vis[nx][ny] || g[nx][ny])
            continue;
        vis[nx][ny] = 1;
        if (match[nx][ny].first == -1 || dfs(match[nx][ny].first, match[nx][ny].second))
        {
            match[nx][ny] = {x, y};
            return true;
        }
    }
    return false;
}
signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);

    memset(g, 0, sizeof(g));
    memset(match, -1, sizeof(match));
    cin >> n >> t;
    while (t--)
    {
        cin >> a >> b;
        g[a][b] = 1;
    }
    for (int i = 1; i <= n; i++)
    {
        for (int j = (i & 1) ? 1 : 2; j <= n; j += 2)
        {
            if (!g[i][j])
            {
                memset(vis, 0, sizeof(vis));
                if (dfs(i, j))
                    cnt++;
            }
        }
    }
    cout << cnt;
    return 0;
}
3.3車的放置

373. 車的放置 - AcWing题库

1要素:每行每列只能有一个车,对于(i,j)放置车,相当于i行j列都被占用,即i行和j列连边

0要素:一个车不能既在第i行又在第j行,所以行与行之间无边

#include <iostream>
#include <cstring>
#include <queue>
#include <algorithm>
#include <string>
using namespace std;
using pii = pair<int, int>;
#define N 210
#define M 50010
int n, m, t, a, b, cnt = 0, dir[5]{1, 0, -1, 0, 1};

int match[N]{0};
bool g[N][N]{0}, vis[N];
bool dfs(int i)
{
    for (int j = 1; j <= m; j++)
    {
        if (g[i][j] || vis[j])
            continue;
        vis[j] = 1;
        if (!match[j] || dfs(match[j]))
        {
            match[j] = i;
            return true;
        }
    }
    return false;
}
signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    cin >> n >> m >> t;
    while (t--)
    {
        cin >> a >> b;
        g[a][b] = 1;
    }
    for (int i = 1; i <= n; i++)
    {
        memset(vis, 0, sizeof(vis));
        if (dfs(i))
            cnt++;
    }
    cout << cnt;
    return 0;
}
3.4导弹防御塔

374. 导弹防御塔 - AcWing题库

1要素:没个炮弹只能打一个敌人,每个敌人只能被一个炮弹打(注意是炮弹而非炮台)

0要素:敌人不能打敌人,炮弹不能打炮弹

一个炮台是可以发出多枚炮弹的,也就是说一个炮台可以和多个敌人连边,这就是多重匹配的问题了。

题目又要求最短时间,我们发现这个可行时间具有单调性,所以可以用二分查找来逼近。

我们二分可行时间,每个可行时间对应每个炮台最多打出的炮弹数目p,我们将每个炮台拆为p个点,然后每个点都有自己的发射时间,对每个敌人枚举炮弹,如果能被打就连边

然后跑匈牙利,根据每个敌人能否被打到来收缩区间。文章来源地址https://www.toymoban.com/news/detail-809800.html

#include <iostream>
#include <cmath>
#include <cstring>
#include <vector>
#include <string>
#include <cmath>
using namespace std;
using pii = pair<int, int>;
#define sc scanf
#define N 55
#define int long long
#define precision 1e-9
int n, m;
double t1, t2, v, cost[N][N];
int match[N * N]{0};
bool vis[N * N], f;
pii a[N], b[N];
vector<int> g[N];
bool dfs(int u)
{
    for (auto v : g[u])
    {
        if (vis[v])
            continue;
        vis[v] = true;
        if (!match[v] || dfs(match[v]))
        {
            match[v] = u;
            return true;
        }
    }
    return false;
}

signed main()
{
    // ios::sync_with_stdio(false);
    // cin.tie(0), cout.tie(0);
    // freopen("in.txt", "r", stdin);
    sc("%lld%lld%lf%lf%lf", &n, &m, &t1, &t2, &v);
    t1 /= 60;
    for (int i = 1; i <= m; i++)
        sc("%lld%lld", &b[i].first, &b[i].second);
    for (int i = 1; i <= n; i++)
        sc("%lld%lld", &a[i].first, &a[i].second);
    for (int i = 1; i <= m; i++)
        for (int j = 1; j <= n; j++)
            cost[i][j] = sqrt((b[i].first - a[j].first) * (b[i].first - a[j].first) + (b[i].second - a[j].second) * (b[i].second - a[j].second)) / v;
    double l = 0, r = 1e6;
    while (r - l > precision)
    {
        double mid = (l + r) / 2;
        int p = (mid + t2) / (t1 + t2); 
        p = min(m, p);                          
        for (int i = 1; i <= m; i++)
        {
            g[i].clear(); 
            for (int j = 1; j <= n; j++)
                for (int k = 1; k <= p; k++)
                    if (cost[i][j] + t1 * k + t2 * (k - 1) <= mid)
                        g[i].push_back((j - 1) * p + k);
        }
        f = true;
        memset(match, 0, sizeof(match));
        for (int i = 1; i <= m && f; i++)
            memset(vis, 0, sizeof(vis)) , f = dfs(i);
        
        f ? r = mid : l = mid;
    }
    printf("%.6lf", r);

    return 0;
}

到了这里,关于二分图最大匹配——匈牙利算法详解的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【算法基础:搜索与图论】3.6 二分图(染色法判定二分图&匈牙利算法)

    https://oi-wiki.org/graph/bi-graph/ 二分图是图论中的一个概念, 它的所有节点可以被分为两个独立的集合,每个边的两个端点分别来自这两个不同的集合 。 换句话说, 二分图中不存在连接同一集合内两个节点的边 。 如何判断一个图是二分图? 当且仅当图中不含奇数环 。(奇数

    2024年02月16日
    浏览(43)
  • 第三章 图论 No.11二分图,匈牙利算法与点覆盖

    257. 关押罪犯 - AcWing题库 最大最小问题,一眼二分 答案的范围在 [ 1 , 1 e 9 ] [1, 1e9] [ 1 , 1 e 9 ] 之间,二分答案,check(mid) check:将所有权值大于mid的边进行二分,若能得到二分图,返回true,否则返回false 最终将得到最优解ans,所有大于ans的边组成的图为二分图,若是图中有边

    2024年02月12日
    浏览(42)
  • 算法基础复盘笔记Day07【搜索与图论】—— Prim、Kruskal、染色体判定二分图、匈牙利算法

    ❤ 作者主页:欢迎来到我的技术博客😎 ❀ 个人介绍:大家好,本人热衷于 Java后端开发 ,欢迎来交流学习哦!( ̄▽ ̄)~* 🍊 如果文章对您有帮助,记得 关注 、 点赞 、 收藏 、 评论 ⭐️⭐️⭐️ 📣 您的支持将是我创作的动力,让我们一起加油进步吧!!!🎉🎉 1. 题目

    2024年02月02日
    浏览(50)
  • DETR代码学习(五)之匈牙利匹配

    匈牙利匹配先前在损失函数那块已经介绍过,但讲述了并不清晰,而且准确来说,匈牙利匹配所用的cost值与损失函数并没有关系,因此今天我们来看一下匈牙利匹配这块的代码与其原理。 前面已经说过,DETR将目标检测看作集合预测问题,在最后的预测值与真实值匹配过程,

    2024年02月09日
    浏览(41)
  • 目标跟踪:Deepsort--卡尔曼滤波、匈牙利匹配、马氏距离、欧氏距离、级联匹配、reid

    本篇文章供自己学习回顾,其中错误希望指出! 先把目标跟踪中涉及到的名词抛出来: 1、卡尔曼滤波、 2、匈牙利匹配:https://blog.csdn.net/DeepCBW/article/details/124740092 3、马氏距离、 4、欧氏距离、 5、级联匹配、 6、代价矩阵 sort过程比较简单,先搬个图: 基于上图展开对sort的

    2024年02月05日
    浏览(50)
  • 指派问题与匈牙利算法

    有n项不同的工作或任务,需要n个人去完成,要求每人只完成一项工作。由于每人的知识、能力、经验等不同,故各人完成不同任务所需的时间不同。问应指派何人完成何项工作,使完成n项工作总耗时最少。这就是指派问题,指派问题也是整数规划问题。 目标函数是最小化问

    2024年02月05日
    浏览(40)
  • 搜索与图论:匈牙利算法

    将所有点分成两个集合,使得所有边只出现在集合之间,就是二分图 二分图:一定不含有奇数个点数的环;可能包含长度为偶数的环, 不一定是连通图

    2024年02月08日
    浏览(65)
  • 数学建模(四)整数规划—匈牙利算法

    目录 一、0-1型整数规划问题 1.1 案例 1.2 指派问题的标准形式 2.2 非标准形式的指派问题 二、指派问题的匈牙利解法  2.1 匈牙利解法的一般步骤 2.2 匈牙利解法的实例 2.3 代码实现 投资问题: 有600万元投资5个项目,收益如表,求利润最大的方案? 设置决策变量: 模型: 指派

    2024年02月11日
    浏览(42)
  • DETR | 基于匈牙利算法的样本分配策略

    如有错误,恳请指出。 前不久,沐神对DETR进行了讲解,其实之前也对DETR进行了介绍,见:论文阅读笔记 | 目标检测算法——DETR。现对DETR的核心内容进行重温,也就是其所提出的目标检测的end-to-end框架,输入的是一张图像,输出的直接是最后的预测标注结果,不再需要后处

    2024年02月11日
    浏览(40)
  • 图论及其应用(匈牙利算法)---期末胡乱复习版

    T1:从下图中给定的 M = {x1y4,x2y2,x3y1,x4y5},用 Hungariam算法【匈牙利算法】 求出图中的 完美匹配 ,并写出步骤。 关于匈牙利算法: 需要注意的是,匈牙利算法仅适用于 二分图 ,并且能够找到完美匹配。 什么是 交替路 ?从一个未匹配点出发,依次经过非匹配边–匹配边

    2024年02月01日
    浏览(68)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包