热门标签 | HotTags
当前位置:  开发笔记 > 人工智能 > 正文

二分图及相关问题整理

计算无权二分图的最大匹配匈牙利算法给定一个二分图,



计算无权二分图的最大匹配

匈牙利算法

给定一个二分图,其左部点的个数为

n

n

n,右部点的个数为

m

m

m,边数为

e

e

e,求其最大匹配的边数。

左部点从 1 至

n

n

n 编号,右部点从 1 至

m

m

m 编号。


法1:

枚举每一个左部点

u

u

u,然后枚举该左部点连出的边,对于一个出点

v

v

v,如果它没有被先前的左部点匹配,那么直接将

u

u

u 匹配

v

v

v,否则尝试让

v

v

v 的“原配”左部点去匹配其他右部点,如果“原配”匹配到了其他点,那么将

u

u

u 匹配

v

v

v,否则

u

u

u 失配。(摘自扶苏题解)

#include
#include
#include
#include
using namespace std;
#define rep(i, a, b) for(int i = a; i <= b; ++i)
#define maxn 1005
int n, m, c, ans;
vector <int> e[maxn];
int mtc[maxn], vis[maxn];
inline bool match(int u, int wh)
{
if(vis[u] == wh) return 0;//避免死循环
vis[u] = wh;
if(!e[u].size()) return 0;//注意边界
rep(i, 0, e[u].size() - 1)
if(!mtc[e[u][i]] or match(mtc[e[u][i]], wh))
{
mtc[e[u][i]] = u;
return 1;
}
return 0;
}
int main()
{
scanf("%d %d %d", &n, &m, &c);
rep(i, 1, c)
{
int u, v;
scanf("%d %d", &u, &v);
e[u].push_back(v);
}
rep(i, 1, n)
ans += match(i, i);
printf("%d\n", ans);
return 0;
}

法2:

利用增广路径。





计算匹配问题经常需要使用一个可增广路径的概念。

设 M 是二分图的一个匹配,将 M 中的边所关联的节点称为盖点,其余节点称为未盖点。

若一条路径上属于 M 的边和不属于 M 的边交替出现,则称该路径为一条交错轨(交替路)。

若路径 p 是一条起始点和结束点都是未盖点的交错轨,则称 p 为一条关于 M 的增广路经.即“从一个未匹配点出发,走交替路,以另一个未匹配点为结尾”。

若图存在增广路径,则 M 中的匹配边数增加1。为什么?

显然,图中不存在增广路径时匹配边数是最多的。

(摘自 zhq 老师 ppt)





每次找关于 M 的增广路径,然后交替取舍,就可以比原先的匹配多出一条匹配边。

重复此操作,直至二分图中不存在关于 M 的可增广路径为止。此时得到的匹配 M 就是G的一个最大匹配。

int check(int u) { //u它是X集合中的点
for(int i = 0; i < G[u].size(); i++){ //枚举Y中与U相连的点
int v= G[u][i];
if(!vis[v]){ //标记是否访问过
vis[v] = 1;
if(link[v] == -1 || check(link[v])){//找到Y中未盖点,或,
//如果这点被配对过,那么就从这点配对的X中的点进行新一轮的check()
link[v] = u; //深搜回溯时,取反
return 1;
}
}
}
return 0;
}
int get_maxmatch(){
int num = 0;
for(int i = 1; i <= n; i++){
memset(vis, 0, sizeof(vis));
if(check(i)){
num++; //找到增广路径,num+1
}
}
return num;
}





匹配:在 G 中两两没有公共端点的边集合。

边覆盖:G 中的任意顶点都是边集合 F 中某条边的端点。即最小的边能覆盖所有顶点。

独立集:在 G 中两两互不相联(没有直接连接两者的路径)的顶点集合。

顶点覆盖:G 中任意边都有至少一个端点属于顶点集合 S。






最小点覆盖



  • |最大独立集|+|最小顶点覆盖|=|V|



  • 对于二分图,|最大匹配|=|最小顶点覆盖|



例:

在 N* N 的格子中,有一些小行星,Bessie 有一把枪,能一次消灭一行或一列的行星,求最少需要多少子弹。

解:

把每行每列各看成一个节点,把一颗小行星的坐标

(x,y)

\text{(x,y)}

(x,y) 看成一条 x 到 y 的边。建完图后,题目就变成找到最少的点,使得这些点与所有的边相邻,即最小点覆盖问题。


最大独立集



  • |最大独立集|+|最小顶点覆盖|=|V|

例:

老师带学生出去旅游,但出于特殊的考虑,带出去的学生至少要满足以下要求之中的一个:1.两人身高相差超过40CM;2.同性别;3.喜欢的音乐风格不同; 4.喜欢的运动相同。问最多可以带出去多少学生?

解:

最大独立集:最大的一个集合,其中的每两点之间都不存在边。

首先,根据性别把学生分成男,女两部分。将身高差少于40CM且喜欢音乐相同且喜欢运动不同的男女连边,那么可以带出去的人数应该等于这个二分图的最大独立集。

最大独立集=顶点数(包括X和Y)-最大匹配(利用匈牙利算法)


最小边覆盖



  • 对于不存在孤立点的图,|最大匹配|+|最小边覆盖|=|V|(总点数)

例:

一个地图上有 n 个小镇,以及连接着其中两个小镇的有向边,而且这些边无法形成回路。现在选择一些小镇空降士兵(1 个小镇最多 1 个士兵),士兵能沿着边走到尽头,问最少空降几个士兵,能遍历完所有的小镇。

解:

匈牙利算法求最小路径覆盖:



  1. 在一个有向图中,路径覆盖就是在图中找一些路径,使之覆盖了图中的所有顶点,且任何一个顶点有且只有一条路径与之关联。



  2. 如果把这些路径中的每条路径从它的起始点走到它的终点,那么恰好可以经过图中的每个顶点一次且仅一次。



  3. 解决此类问题可以建立一个二分图模型。把所有顶点i拆成两个:X 结点集中的 i 和 Y 结点集中的 i’,如果有边 i->j,则在二分图中引入边 i->j’,设二分图最大匹配为 m,则结果就是 n-m。




求二分图权值和最大(小)完美匹配





  • 完美匹配

    若二分图 X 部的每一个顶点都与 Y 中的一个顶点匹配,并且 Y 部中的每一个顶点也与 X 部中的一个顶点匹配,则该匹配为完美匹配。

    一对一,所有顶点都匹配,并且 X 部和 Y 部顶点数一样。



  • 完备匹配

    若二分图 X 部的每一个顶点都与 Y 中的一个顶点匹配,或者 Y 部中的每一个顶点与 X 部中的一个顶点匹配,则该匹配为完备匹配。

    匹配数 = min(|X|,|Y|), |X| 表示 X 部的点数,|Y| 表示 Y 部的点数。

    X 部和 Y 部顶点数可能不一样。



  • 最佳匹配

    带权二分图的权值和最大的匹配称为最佳匹配。








KM 算法

对于每个左部点

i

i

i,右部点

j

j

j,都有

l

x

i

lx_i

lxi

l

y

j

ly_j

lyj,表示它们的顶标,保证对于所有的

i

l

e

f

t

,

 

j

r

i

g

h

t

i \in left,\ j \in right

ileft, jright,都有

l

x

i

+

l

y

j

>

=

w

i

,

j

lx_i+ly_j>=w_{i,j}

lxi+lyj>=wi,j

而 KM 算法就是,每次都选取

l

x

i

+

l

y

j

=

w

i

,

j

lx_i + ly_j=w_{i,j}

lxi+lyj=wi,j 的左部点和右部点,构成相等子图,然后看当前子图是否有增广路径,即每个当前左部点都有对应的匹配的右部点。

若有,那么接着去匹配下一个左部点;若没有,则更改顶标。

每次对于遍历过的左部点,都加上更新值,右部点则减去。

更新值的获取方式见代码,作用就是让尽可能少地让新的边可以加入匹配,尝试是否可行。

#include
using namespace std;
#define int long long
#define maxn 105
#define maxm 20005
#define rep(i, a, b) for(int i = a; i <= b; ++i)
int n, c[maxn][maxn];
bool s[maxn], t[maxn];
int lx[maxn], ly[maxn], mtc[maxn];
int ans1, ans2;
inline bool dfs(int u)
{
s[u] = 1;//将遍历过的左部点记录下
rep(v, 1, n) if(!t[v]/*没有遍历过该右部点*/ and lx[u] + ly[v] == c[u][v]/*能够匹配*/)
{
t[v] = 1;//标记
if(!mtc[v] or dfs(mtc[v]))
{
mtc[v] = u;
return true;
}
}
return false;
}
inline void updt()
{
int d = 2147483647;
rep(i, 1, n) if(s[i])
rep(j, 1, n) if(!t[j]) d = min(d, lx[i] + ly[j] - c[i][j]);//寻找最小更新值
rep(i, 1, n)
{
if(s[i]) lx[i] -= d;//左部点顶标值减去更新值
if(t[i]) ly[i] += d;//右部点顶标值加上更新值
}
}
inline void KM()
{
rep(i, 1, n) mtc[i] = lx[i] = ly[i] = 0;//初始清空
rep(i, 1, n) rep(j, 1, n)
lx[i] = max(lx[i], c[i][j]);//初始顶标
rep(i, 1, n) while(1)
{
rep(i, 1, n) s[i] = t[i] = 0;//清空记录路径的数组
if(dfs(i)) break;//如果找到当前图的增广路径,或当前点有匹配
else updt();//没有匹配则更新顶标,重新尝试匹配
}
}
signed main()
{
scanf("%lld", &n);
rep(i, 1, n) rep(j, 1, n)
scanf("%lld", &c[i][j]);
KM();
rep(i, 1, n) ans1 += lx[i] + ly[i];//答案即为所有的顶标值之和
rep(i, 1, n) rep(j, 1, n) c[i][j] *= -1;//取反求最小值
KM();
rep(i, 1, n) ans2 += lx[i] + ly[i];
printf("%lld\n%lld\n", -ans2, ans1);
return 0;
}


以上内容整理自 zhq 老师《二分图》PPT,并作补充。



推荐阅读
  • 本文介绍如何利用动态规划算法解决经典的0-1背包问题。通过具体实例和代码实现,详细解释了在给定容量的背包中选择若干物品以最大化总价值的过程。 ... [详细]
  • 本文详细探讨了Java中的24种设计模式及其应用,并介绍了七大面向对象设计原则。通过创建型、结构型和行为型模式的分类,帮助开发者更好地理解和应用这些模式,提升代码质量和可维护性。 ... [详细]
  • 在金融和会计领域,准确无误地填写票据和结算凭证至关重要。这些文件不仅是支付结算和现金收付的重要依据,还直接关系到交易的安全性和准确性。本文介绍了一种使用C语言实现小写金额转换为大写金额的方法,确保数据的标准化和规范化。 ... [详细]
  • 在给定的数组中,除了一个数字外,其他所有数字都是相同的。任务是找到这个唯一的不同数字。例如,findUniq([1, 1, 1, 2, 1, 1]) 返回 2,findUniq([0, 0, 0.55, 0, 0]) 返回 0.55。 ... [详细]
  • 本文探讨了卷积神经网络(CNN)中感受野的概念及其与锚框(anchor box)的关系。感受野定义了特征图上每个像素点对应的输入图像区域大小,而锚框则是在每个像素中心生成的多个不同尺寸和宽高比的边界框。两者在目标检测任务中起到关键作用。 ... [详细]
  • 网络攻防实战:从HTTP到HTTPS的演变
    本文通过一系列日记记录了从发现漏洞到逐步加强安全措施的过程,探讨了如何应对网络攻击并最终实现全面的安全防护。 ... [详细]
  • 本文深入探讨了Linux系统中网卡绑定(bonding)的七种工作模式。网卡绑定技术通过将多个物理网卡组合成一个逻辑网卡,实现网络冗余、带宽聚合和负载均衡,在生产环境中广泛应用。文章详细介绍了每种模式的特点、适用场景及配置方法。 ... [详细]
  • 本文探讨了如何在给定整数N的情况下,找到两个不同的整数a和b,使得它们的和最大,并且满足特定的数学条件。 ... [详细]
  • 深度学习理论解析与理解
    梯度方向指示函数值增加的方向,由各轴方向的偏导数综合而成,其模长表示函数值变化的速率。本文详细探讨了导数、偏导数、梯度等概念,并结合Softmax函数、卷积神经网络(CNN)中的卷积计算、权值共享及池化操作进行了深入分析。 ... [详细]
  • 机器学习中的相似度度量与模型优化
    本文探讨了机器学习中常见的相似度度量方法,包括余弦相似度、欧氏距离和马氏距离,并详细介绍了如何通过选择合适的模型复杂度和正则化来提高模型的泛化能力。此外,文章还涵盖了模型评估的各种方法和指标,以及不同分类器的工作原理和应用场景。 ... [详细]
  • 自学编程与计算机专业背景者的差异分析
    本文探讨了自学编程者和计算机专业毕业生在技能、知识结构及职业发展上的不同之处,结合实际案例分析两者的优势与劣势。 ... [详细]
  • 2023年京东Android面试真题解析与经验分享
    本文由一位拥有6年Android开发经验的工程师撰写,详细解析了京东面试中常见的技术问题。涵盖引用传递、Handler机制、ListView优化、多线程控制及ANR处理等核心知识点。 ... [详细]
  • 深入浅出:Google工程师的算法学习指南
    通过Google工程师的专业视角,带你系统掌握算法的核心概念与实践技巧。 ... [详细]
  • 本文深入探讨了 Python 列表切片的基本概念和实际应用,通过具体示例展示了不同切片方式的使用方法及其背后的逻辑。 ... [详细]
  • 本文详细介绍了K-Medoids聚类算法,这是一种基于划分的聚类方法,适用于处理大规模数据集。文章探讨了其优点、缺点以及具体实现步骤,并通过实例进行说明。 ... [详细]
author-avatar
暗蓝语依_431
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有