二分图——匈牙利算法

来源:互联网 发布:dota2天梯淘宝买账号 编辑:程序博客网 时间:2024/05/01 15:45

 

二分图的基本概念
一个无向图G=<V, E>,如果存在两个集合X、Y,使得X∪Y=V,X∩Y=Φ,并且每一条边e={x,y}有x∈X,y∈Y,则称G为一个二分图(bipartitegraph)。常用<X, E,Y>来表示一个二分图。若对X中任一x及Y中任一y恰有一边e∈E,使e = {x, y},则称G为完全二分图(complete bipartite graph)。当|X| = m,|Y| =n时,完全二分图G记为Km,n

二分图的性质:
定理:无向图G为二分图的充分必要条件是,G至少有两个顶点,且其所有回路的长度均为偶数。
匹配:设G=<V,E>为二分图,如果M⊆E,并且M中没有任何两边有公共端点。M=Φ时称M为空匹配。
最大匹配:G的所有匹配中边数最多的匹配称为最大匹配。
完全匹配:若X(Y)中所有的顶点都是匹配M中的端点。则成M为完全匹配。若M既是X-完全匹配又是Y-完全匹配,则称M为G的完全匹配。
注意:最大匹配总是存在但未必唯一;X(Y)-完全匹配及G的完全匹配必定是最大的,但反之则不然;X(Y)-完全匹配未必存在。

下面引入几个术语:
设G=<V, E>为二分图,M为G的一个匹配。

  1. M中边的端点称为M-顶点,其它顶点称为非M-顶点
  2. 增广路径:除了起点和终点两个顶点为非M-顶点,其他路径上所有的点都是M=顶点。而且它的边为匹配边、非匹配边交替出现。

    image

    如上图中图1,就是一个二分图的匹配:[X1,Y2]。图2就是在这个匹配的基础上的两个增广路径:X2→Y2→X1→Y1和X3→Y3。
    我们来验证一下:增广路径 X2→Y2→X1→Y1中,起止点X2、Y1为非M-顶点。而中间点Y2、X1都是M-顶点。
    边{X2, Y2}, {X1, Y1}为非匹配边,而边{Y2, X1}为匹配边,满足匹配边与非匹配边交替出现。
    同理X3→Y3路径也满足增广路径的要求。

    借助这幅图,来描述一下增广路径的性质。

    1. 有奇数条边。
    2. 起点在二分图的左半边,终点在右半边。
    3. 路径上的点一定是一个在左半边,一个在右半边,交替出现。(其实二分图的性质就决定了这一点,因为二分图同一边的点之间没有边相连,不要忘记哦。)
    4. 整条路径上没有重复的点。
    5. 起点和终点都是目前还没有配对的点,而其它所有点都是已经配好对的。
    6. 路径上的所有第奇数条边都不在原匹配中,所有第偶数条边都出现在原匹配中。
    7. 最后,也是最重要的一条,把增广路径上的所有第奇数条边加入到原匹配中去,并把增广路径中的所有第偶数条边从原匹配中删除(这个操作称为增广路径的取反),则新的匹配数就比原匹配数增加了1个。
    ∵增广路径的长度是奇数,我们设为2k+1条
    又∵第一条是非匹配边 且 匹配边与非匹配边交替出现
    ∴非匹配边有K+1条,匹配边有K条。
    非匹配边比匹配边多了1条。
    此时,我们做取反操作(匹配边变成非匹配边,非匹配边变成匹配边),则匹配边的个数就会在原来的基础上增加1条。
    求最大匹配的“匈牙利算法”就是这样做的。
    无论从哪个匹配开始(整个程序的初始状态是从空匹配开始),每次操作都让匹配数增加1条,不断使它得到扩充,直到找不到增广路径。
    这样就得到了最大匹配了。

    对增广路径,还有一种递归的定义,可能不大准确,但揭示了一种寻找增广路径的一般方法:
    从集合X中的一个非M-顶点A出发,通过与A关联的边到达集合Y中的端点B,如果B在M中没有任何边匹配,则B就是该增广路径的终点;如果B已经与C点配对,则这条增广路径就是从A→B→C并加上“从C点出发的增广路径”。并且这条增广路径中不能有重复的点出现。

    image
    比如我们要从上图中找出一条从X3点出发的增广路径,我们需要做以下几步。

    1. 首先从X3出发,它能连接到的点只有点Y3,而Y3已经与X2配对,所以现在的增广路径是X3→Y3→X2在加上从点X2出发的增广路径。
    2. 点X2能连接到Y2,Y3,但Y3与前面的路径重复,而{X2,Y2}这条边也不在原来的匹配中,所以只能连接到Y2。所以现在的增广路径是X3→Y3→X2→Y2→X1在加上从点X1出发的增广路径。
    3. 点X1能连接到的点且不前面路径重复的点只有Y1。并且Y1在原先的匹配中不与其他所有点配对,属于非M-顶点。因此Y1是该增广路径的终点。所以最终的增广路径是X3→Y3→X2→Y2→X1→Y1。

    严格意义上讲,上面提到的从X2出发的增广路径X2→Y2→X1→Y1和从点X1出发的增广路径X1→Y1并不是真正意义上的增广路径,它们不符合第5个性质。它们的起点是已配对的点。这里说它们是增广路径只是为了简化搜索过程,它们都只是中间返回值而已。

现在就进入我们的正题:用匈牙利算法求最大匹配。
匈牙利算法的基本模式是:

初始时最大匹配为空 while 找得到增广路径 do 把增广路径加入到最大匹配中去
比如我们寻找图1的最大匹配,过程可能如下:
  1. 初始最大匹配为空。
  2. 找到增广路径X1→Y2,把它取反,则匹配数增大到1,最大匹配变成[X1, Y2]。
  3. 找到增广路径X2→Y3,把它取反,则匹配数增大到2,最大匹配变成[X1, Y2],[X2, Y3]。
  4. 找到增广路径X3→Y3→X2→Y2→X1→Y1,把它取反,则匹配数增大到3,最大匹配变成[X1, Y1],[X2,Y2],[X3, Y3]。
  5. 找不出增广路径,程序结束,得到最大匹配数为3。
这只是其中一种可能的过程,还有其他不同的过程,得到的增广路径也可能不同,但最后最大匹配数一定是相同的。

从上面的描述可以看出,搜索增广路径的方法是DFS,写一个递归的函数。当然也可以用BFS。

至此,理论基础部份讲完了。但是要完成匈牙利算法,还需要一个重要的定理:
如果从一个点A出发,没有找到增广路径,那么无论再从别的点出发找到多少增广路径来改变现在的匹配,从A出发都永远找不到增广路径。

有了这个定理,匈牙利算法就成形了。如下:

初始时最大匹配为空 for 二分图左半边的每个点i do从点i出发寻找增广路径。如果找到,则把它取反(即增加了总了匹配数)。

如果二分图的左半边一共有n个点,那么最多找n条增广路径。如果图中共有m条边,那么每找一条增广路径(DFS或BFS)时最多把所有边遍历一遍,所花时间也就是m。所以总的时间大概就是O(n* m)

 

 

 

数据结构:
保存二分图用的是邻接表。
为方便存储和阅读,数组下标从1开始(0那个空间就空着不用)。
因此,第i个表头邻接的是第i个女生愿意与他做partner的男生号。

集合X、Y用一维数组表示。下标同样从1开始。
如果它们暂时没有点配对,则里面保存的是O,否则保存配对的点的下标。
初始状态X、Y都为0。

因为X是从1到n循环,所以只需要判断Y集合中的点是否被访问过。
判断是否被访问过用一个Bool型一维数组,true表示被访问过,false表示未被访问过。

中间记录增广路径用栈结构。因为增广路径的边数是奇数,根据握手定理,它的顶点数一定是偶数。
找到增广路径以后,每次从栈顶取出2个端点,就是现在相互配对的端点了。一直到栈取空为止。

算法实现:
实现匈牙利算法分为4个步骤:

  1. 初始化:
    集合X、Y清空
    图清空
  2. 输入:
    输入集合X、Y的大小size_x和size_y,以及所有的边
  3. 计算:
    i从1循环到size_x,深搜寻找Xi的增广路径
    每次寻找前要先清空栈,并且初始Visit数组为false
    在深搜的过程中,要注意,如果寻找增广路径失败,要记得把刚加入的那一个顶点从栈中删除
    如果增广路径查找成功,就开始读栈,对X、Y里相互配对的端点进行标记,直到栈为空
  4. 输出:
    统计X(Y)集合中有配对的端点个数,即最大匹配数。
    输出最大匹配数

 

题目--谈恋爱

Description

在大学生涯中,谈恋爱是一个亘古不变的真理。女生恋爱时主要看你兜里有没有钱,家里有没有权。人脉够不够好,模样够不够帅。为此有人发明了一个指数a,表示一个人的综合魅力指数。
在校园中,每个女孩子都会有一个指数区间,即是只有在这个区间的男生才能引起她的注意力。而对于不在该区间范围的不予考虑。有一天m个漂亮美眉迁到了ACM组.这时组里的n个光棍们兴奋了。已经通过各种渠道获取了这批美眉的指数区间,以及光棍们的魅力指数。剩下的事情便交给你了,问最多可以成多少对。

Input

多组测试数据。
第一行 :m n(美眉数 光棍数)m,n不大于500
接下来m行:美眉们的指数区间 x y
最后一行有n个数:对应的魅力值。

Output

最多所能成的对数

Sample Input

3 2
1 3
2 4
2 5
1 3

Sample Output

2

#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
const int N = 505;
struct MM
{
int x,y;
}mm[N];
int gg[N];
int n,m;
bool vis[N];
int lik[N];
int dfs(int t)
{
for(int i=0;i<m;i++)
{
if(!vis[i]&& mm[i].x<=gg[t]&&mm[i].y>=gg[t])
{
vis[i]=1;
if(lik[i]==-1 || dfs(lik[i]))
{
lik[i]=t;
return1;
}
}
}
return 0;
}
void max_num()
{
int ans=0;
for(int i=0;i<n;i++)
{
memset(vis,0,sizeof(vis));
if(dfs(i))ans++;
}
printf("%d\n",ans);
}
int main()
{
while(~scanf("%d%d",&m,&n))
{
memset(lik,-1,sizeof(lik));
for(inti=0;i<m;i++)
scanf("%d%d",&mm[i].x,&mm[i].y);
for(inti=0;i<n;i++)scanf("%d",&gg[i]);
max_num();
}
return 0;
}
 

 

 

 

二分图最大匹配问题的匈牙利算法:

 

#define N 202

int useif[N]; //记录y中节点是否使用

int link[N]; //记录当前与y节点相连的x的节点

int mat[N][N];//记录连接x和y的边,如果i和j之间有边则为1,否则为0

int gn,gm; //二分图中x和y中点的数目

int can(int t)

{

int i;

for(i=1;i<=gm;i++)

{

if(useif[i]==0 &&mat[t][i])

{

useif[i]=1;

if(link[i]==-1 || can(link[i]))

{

link[i]=t;

return 1;

}

}

}

return 0;

}

int MaxMatch()

 

{

int i,num;

num=0;

memset(link,0xff,sizeof(link));

for(i=1;i<=gn;i++)

{

memset(useif,0,sizeof(useif));

if(can(i)) num++;

}

return num;

}

算法思想:

 

算法的思路是不停的找增广轨,并增加匹配的个数,增广轨顾名思义是指一条可以使匹配数变多的路径,在匹配问题中,增广轨的表现形式是一条"交错轨",也就是说这条由图的边组成的路径,它的第一条边是目前还没有参与匹配的,第二条边参与了匹配,第三条边没有..最后一条边没有参与匹配,并且始点和终点还没有被选择过.这样交错进行,显然他有奇数条边.那么对于这样一条路径,我们可以将第一条边改为已匹配,第二条边改为未匹配...以此类推.也就是将所有的边进行"反色",容易发现这样修改以后,匹配仍然是合法的,但是匹配数增加了一对.另外,单独的一条连接两个未匹配点的边显然也是交错轨.可以证明,当不能再找到增广轨时,就得到了一个最大匹配.这也就是匈牙利算法的思路.


0 0
原创粉丝点击