最大二分匹配问题——“相亲巧妙解匈牙利算法”
二分图
1,什么是二分图
二分图又称作二部图,是图论中的一种特殊模型。
设G=(V, E)是一个无向图。如果顶点集 V可分割为两个互不相交的子集X和Y,并且图中每条边连接的两个顶点一个在 X中,另一个在 Y中,则称图G为二分图。
2,二分图相关性质
定理:当且仅当无向图G的每一个环
的结点数均是偶数时,图G才是一个二分图。如果无环,也视为二分图。
3,二分图的判定
如果一个图是连通的,可以用如下的染色法判定是否二分图:
我们把X部的结点颜色设为0,Y部的颜色设为1。
从某个未染色的结点u开始,做BFS或者DFS 。把u染为0,枚举u的儿子v。如果v未染色,就染为与u相反的颜色,如果已染色,则判断u与v的颜色是否相同,相同则不是二分图。
如果一个图不连通,则在每个连通块中作判定。如图所示
二分图的判定结合这道模板题看下:
给定一个 n 个点 m 条边的无向图,图中可能存在重边和自环。
请你判断这个图是否是二分图。
输入格式
第一行包含两个整数 n 和 m。
接下来 m 行,每行包含两个整数 u 和 v,表示点 u 和点 v 之间存在一条边。
输出格式
如果给定图是二分图,则输出 Yes,否则输出 No。
数据范围
1≤n,m≤105
输入样例:
4 4
1 3
1 4
2 3
2 4
输出样例:
Yes
题目来源:染色法判断二分图
推荐 这道题的题中相关解释是根据我后面将的“男女相亲”来陈述的,可以先去看看男女相亲巧解匈牙利
#include<cstring>
#include<iostream>
using namespace std;
const int N = 100010, M = 200010;
int n, m;
int h[N], e[M], ne[M], idex;//因为邻接表存的是无向图,不仅要存ab也要存ba所以M的范围是N的两倍
int st[N];//判断点是否染色以及染的颜色是否相同
void add(int x, int y)
{
//表示的是x->y的边
e[idex] = y;
ne[idex] = h[x];
h[x] = idex++;
}
bool dfs(int u, int c)//表示将点u染成颜色c
{
st[u] = c;
for (int i = h[u]; i != -1; i = ne[i])
{
int j = e[i];
if (!st[j])//如果未染色
{
if (!dfs(j, 3 - c))
return false;
}
else if (st[j] == c) return false;//如果染色但是染色相同那么也是没用的
}
return true;
}
int main()
{
cin >> n >> m;
memset(h, -1, sizeof h);
while (m--)
{
int u, v;
cin >> u >> v;
add(u, v), add(v, u);
}
bool flag = false;
for (int i = 1; i <= n; i++)//之所以循环n次是因为并非所有图都是连通的
{
if (!st[i])
{
if (!dfs(i, 1))//每次dfs都是将与i点连通的点染色,不连通的点不会染色
{
flag = true;
break;
}
}
}
if (flag) printf("No\n");
else printf("Yes\n");
return 0;
}
二分图的匹配
匹配:这个图的一个边的集合,集合中任意两条边都没有公共的顶点,则称这个边集为一个匹配。我们称匹配中的边为匹配边,边中的点为匹配点;未在匹配中的边为非匹配边,边中的点为未匹配点。
二分图的最大匹配:一个图中所有匹配中,所含匹配边数最大的匹配称为最大匹配。
完美匹配:
如果一个图的某个匹配中,图的所有顶点都是匹配点,那么这个匹配就是完美匹配。很显然,完美匹配一定是最大匹配,但是并不是所有的图都存在完美匹配。
Fig.2是某个二分图。Fig.3是该二分图中的某个匹配,匹配中的边用红色标明了。Fig.4是该二分图的某个最大匹配,同时也是完美匹配。
最小点覆盖 = 最大匹配问题证明(koning定理)证明!一定看得懂!
König定理的内容是,一个二分图中的最大匹配数等于这个图中的最小点覆盖数。假如已知最大匹配M,由最大匹配的定义可知,二分图K中的两个集合A,B中已经取出了最多个匹配点,即取出了最多个不共用一个点的路径。如果K中除了M中的点以外再没有其他任何点,那么显然K中所有路径已经被覆盖。如果K中除了M中的点还有其他点时,我们把这些点组成的集合定为L。对于任意x,y∈L,如果x∈A,y∈B,x,y间不可能存在路径,因为x,y∉M,x,y不能匹配。所以任意x∈L,如果x与其它点z连通,则z一定属于M。所以路径x–z也已经被z覆盖。由此可见,M中的点覆盖了所有存在的边,即最小点覆盖=最大匹配。
男女相亲巧解匈牙利!
匈牙利算法:
看了很多博客,都用什么交替路,增广路,我觉得好绕口,我下面用一组“男女相亲”表示一下匈牙利算法具体过程。
oj题
1,小行星
先看一道板子题目:
小行星
直接给出代码:
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 510, M = 10010;
int h[N], e[M], ne[M], idex;
bool st[N];//判断女生是否已经被男生选择
int match[N];//表示的是匹配。如果左边的点i和右边的点j相匹配,那么match[j] = i
int n, k;
int res;//表示的是最大匹配数
void add(int a, int b)
{
e[idex] = b;
ne[idex] = h[a];
h[a] = idex++;
}
bool find(int x)
{
for (int i = h[x]; i != -1; i = ne[i])
{
int j = e[i];
if (!st[j])
{
st[j] = true;//检查完毕一定要记得将st[j] = true;
if (match[j] == 0 || find(match[j]))
{
match[j] = x;
return true;
}
}
}
return false;
}
int main()
{
memset(h, -1, sizeof h);
cin >> n >> k;
int a[N];
memset(a, 0, sizeof a);
int q = 0;
while (k--)
{
int r, c;
cin >> r >> c;
a[q++] = r;
add(r, c);
}
sort(a, a + q);
int j = 1,sp = 0;
while(j<q)
{
if (a[j] != a[sp])
{
a[++sp] = a[j++];
}
else
j++;
}
j--;
for (int i = 0; i < j; i++)
{
memset(st, false, sizeof st);//将所有女孩子清空,表示都还未被选择过
if (find(a[i]))
res++;
}
cout << res << endl;
return 0;
}
2,泥泞的领域
题目描述:
雨打湿了奶牛场,一个由 R 行和 C 列组成的矩形网格 (1 <= R <= 50, 1 <= C <= 50)。虽然对草有好处,但雨水使一些裸露的土地变得很泥泞。奶牛是一丝不苟的食草动物,它们不想在吃东西的时候弄脏它们的蹄子。
为了防止那些泥泞的蹄子,农夫约翰将在奶牛场的泥泞部分放置一些木板。每块板的宽度为 1 个单位,长度可以是任意长度。每块板必须平行于场地的一侧对齐。
Farmer John 希望尽量减少覆盖泥泞点所需的木板数量,其中一些可能需要不止一块木板才能覆盖。这些板可能不会覆盖任何草并剥夺奶牛的放牧区,但它们可以相互重叠。
计算 FJ 覆盖现场所有泥土所需的最少板数。
输入
-
第 1 行:两个以空格分隔的整数:R 和 C
-
第 2…R+1 行:每行包含一串 C 字符,‘*’ 代表泥泞的补丁,‘.’ 代表一片草地。不存在空格。
输出 -
第 1 行:代表 FJ 需要的板数的单个整数。
样本输入
4 4
..
.***
**.
….
样本输出
4
暗示
输出细节:
板 1、2、3 和 4 放置如下:
1.2。
.333
444.
…2.
板 2 与板 3 和 4 重叠。
试题来源:POJ2226
在线测试:泥泞的领域
试题分析:
图论的题目其实敲代码是不难的,难的是建模,我乍一看这道题的时候,我以为直接是行列建模,就是题中有很明显的行列表示,那么肯定可以用二分图表示,二分图本身就是两个不相交的点集,然后两个点集之间有边,刚开始我将泥泞的横坐标看作是左半部分点集合,泥泞的右半部分看作是右半部分的集合,然后根据这个二分图直接求最小点覆盖,但我完全就是凭感觉,毫无依据,不出意外的wa了。
正规分析一下,由题意可以知道,我们的木板有两种,横着的木板和竖着的木板,我们把行里面连着的坑洼连起来视为一个点,即是一块横木板,编上序号,那么此时转化为
1 0 2 0
0 3 3 3
4 4 4 0
0 0 5 0
板子数为r
竖着也是一样操作
1 0 4 0
0 3 4 5
2 3 4 0
0 0 4 0
所有横向板子即第一个点集,所有竖向板子就是第二个点集,然后再确定边关系,也就是哪些点之间有边,该边就代表了一个洼点,也就是横着的木板和竖着的木板重合的时候。然后将对应的点连起来。建完图就二分图最大匹配就行了。可以把这个二分图里的点理解为是板子,然后边理解为是洼地,要求用最少的板子,也就是求最少用最少的点来覆盖所有边。
程序清单:
#include<iostream>
#include<cstring>
using namespace std;
const int N = 2501,M = 51;
int g[N][N];//构造图g[i][j]代表的是i->j之间有边
int a[M][M];//构造男孩点集
int b[M][M];//构造女孩点集合
int st[N];//判断是否检查女孩
int r, c;//行,列
char s[M][M];
int n1;//代表的男孩点集的个数
int n2;//女孩点集个数
int res;//代表的是最大匹配数
int match[N];//如果男孩i匹配的是女孩j 那么match[j] = i
void build_map()
{
//构造男孩图
for (int i = 1; i <= r; i++)
{
for (int j = 1; j <= c; j++)
{
if (s[i][j] == '*')
{
if (s[i][j - 1] == '*') a[i][j] = a[i][j - 1];
else
a[i][j] = ++n1;
}
}
}
//构造女孩图
for (int j = 1; j <= c; j++)
{
for (int i = 1; i <= r; i++)
{
if (s[i][j] == '*')
{
if (s[i - 1][j] == '*') b[i][j] = b[i - 1][j];
else
b[i][j] = ++n2;
g[a[i][j]][b[i][j]] = 1;//表明此时男孩和女孩之间有一个边
}
}
}
}
bool find(int x)
{
for (int i = 1; i <= n2; i++)
{
if (g[x][i] && !st[i])//如果男女之间有变并且女生还未被查看
{
st[i] = true;
if (match[i] == 0 || find(match[i]))//match[i] = 0代表此时女生没对象,后者代表的是女生现任对象有备胎
{
match[i] = x;
return true;
}
}
}
return false;
}
int main()
{
memset(match, 0, sizeof match);
memset(g, 0, sizeof g);
cin >> r >> c;
for (int i = 1; i <=r; i++)
cin >> s[i]+1;//此时行和列的下标都是从1开始的,为了防止建图操作时的内存越界
build_map();
//建造完图就是匈牙利算法算最大匹配数了
for (int i = 1; i <= n1; i++)
{
memset(st, false, sizeof st);//每次寻找女孩之前都得重新赋值为false
if (find(i))
res++;
}
cout << res << endl;
return 0;
}