二分图之匈牙利算法

主要内容:二分图的最大匹配,完美匹配,最小路径覆盖数,匈牙利算法
二分图相关概念

1.二分图:把一个图的顶点划分为两个不相交集U和V,使每一条边都分别连接U、V中的顶点。
无向图G为二分图的充分必要条件是,G至少有两个顶点,且其所有回路的长度均为偶数。即是说,一个图要么没有环,要么有偶数边的环。所以二分图的另一个定义为:不含有「含奇数条边的环」的图。
这里写图片描述

2.匹配:在图论中,一个「匹配」(matching)是一个边的集合,其中任意两条边都没有公共顶点。例如,图 3、图 4 中红色的边就是图 2 的匹配。
这里写图片描述
其中红线为匹配边,匹配边链接的连个点就是匹配点

3.最大匹配:一个图所有匹配中,所含匹配边数最多的匹配,称为这个图的最大匹配。图 4 是一个最大匹配,它包含 4 条匹配边。最大匹配的匹配边的数目即为最大匹配数

4.完美匹配:如果一个图的某个匹配中,所有的顶点都是匹配点,那么它就是一个完美匹配。图 4 是一个完美匹配。完美匹配一定是最大匹配,但并非每个图都存在完美匹配。

5.最小点覆盖数:选取最少的点,使任意一条边至少有一个端点被选择。最小覆盖要求用最少的点(XX集合或YY集合的都行)让每条边都至少和其中一个点关联。可以证明:最少的点(即覆盖数)=最大匹配数

6.最大独立数:选取最多的点,使任意所选两点均不相连。

7.最小路径覆盖数:对于一个 DAG(有向无环图),选取最少条路径,使得每个顶点属于且仅属于一条路径,路径长可以为 0(即单个点)。也就是说用尽量少的不相交简单路径覆盖DAG的所有结点。解决此类问题可以建立一个二分图模型。把所有顶点i拆成两个:X结点集中的iY结点集中的i′,如果有边i→j,则在二分图中引入边i→j′,设二分图最大匹配为m,则结果就是n-m。

从上述概念描述中可以得到三个定理:

定理1:最大匹配数 = 最小点覆盖数(这是 Konig 定理)

定理2:最大匹配数 = 最大独立数

定理3:最小路径覆盖数 = 顶点数 - 最大匹配数

匈牙利算法
1.概述:求解最大匹配问题的一个算法是匈牙利算法
用白话说:就是从二分图中找出一条路径来,让路径的起点和终点都是还没有匹配过的点,并且路径经过的连线是一条没被匹配、一条已经匹配过,再下一条又没匹配这样交替地出现。找到这样的路径后,显然路径里没被匹配的连线比已经匹配了的连线多一条,于是修改匹配图,把路径里所有匹配过的连线去掉匹配关系,把没有匹配的连线变成匹配的,这样匹配数就比原来多1个。不断执行上述操作,直到找不到这样的路径为止。

这里写图片描述

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

3.增广路:从一个未匹配点出发,走交替路,如果途径另一个未匹配点(出发的点不算),则这条交替路称为增广路(agumenting path)。例如,图 5 中的一条增广路如图 6 所示(图中的匹配点均用红色标出)

这里写图片描述

广路有一个重要特点:非匹配边比匹配边多一条。因此,研究增广路的意义是改进匹配。只要把增广路中的匹配边和非匹配边的身份交换即可。由于中间的匹配节点不存在其他相连的匹配边,所以这样做不会破坏匹配的性质。交换后,图中的匹配边数目比原来多了 1 条。

4.匈牙利算法核心:不停地找增广路来增加匹配中的匹配边和匹配点。找不到增广路时,达到最大匹配(这是增广路定理)。

5.匈牙利树一般由 BFS 构造(类似于 BFS 树)。从一个未匹配点出发运行 BFS(唯一的限制是,必须走交替路),直到不能再扩展为止。

例如,由图 7,可以得到如图 8 的一棵 BFS 树:

这里写图片描述
这棵树存在一个叶子节点为非匹配点(7 号),但是匈牙利树要求所有叶子节点均为匹配点,因此这不是一棵匈牙利树。如果原图中根本不含 7 号节点,那么从 2 号节点出发就会得到一棵匈牙利树。这种情况如图 9 所示(顺便说一句,图 8 中根节点 2 到非匹配叶子节点 7 显然是一条增广路,沿这条增广路扩充后将得到一个完美匹配)。

BFS&DFS代码实现
匈牙利算法的要点如下:

1.从左边第 1 个顶点开始,挑选未匹配点进行搜索,寻找增广路。
__ 1.如果经过一个未匹配点,说明寻找成功。更新路径信息,匹配边数 +1,停止搜索。
__ 2.如果一直没有找到增广路,则不再从这个点开始搜索。事实上,此时搜索后会形成一棵匈_____牙利树。我们可以永久性地把它从图中删去,而不影响结果。

2.由于找到增广路之后需要沿着路径更新匹配,所以我们需要一个结构来记录路径上的点。DFS 版本通过函数调用隐式地使用一个栈,而 BFS 版本使用 prev 数组。

DFS_CODE

const int MAX=1010;
int k,n,m,ma[MAX][MAX],vis[MAX],match[MAX];
//k,n,m分别为边的个数,集合U的个数,集合V的个数。ma[][]记录边联通的边,vis记录访问过的点,match记录已经匹配的点
bool DFS(const int &p){
    for(int i=1;i<=m;i++){
        if(!vis[i] && ma[p][i]){
            vis[i]=1;
            if(!match[i]||DFS(match[i])) //判断是否是增广路
            {
                match[i]=p;   //更新路径//也是取反的过程
                return true;
            }
        }
    }
    return false;
}
int main()
{
    //....do something //初始化//用邻接矩阵记录边
    int ans=0;
    for(int i=1;i<=n;i++){
        memset(vis,0,sizeof(vis));  //刷新访问
        if(DFS(i))  //如果找到增广路,所匹配的边数加一
            ans++;
    }
    //....do something
    return 0;
}

REF:

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值