二分图算法总结
程序员文章站
2022-03-03 11:37:24
...
二分图算法总结
目录
- 判断二分图(染色问题)
- 匈牙利算法
- Kuhn-Munkres算法
- Hopcroft-Karp 算法(匈牙利算法优化)(鸽了,爽)
判断二分图(染色问题)
普通DFS即可解决,先假设第一个点染的是第一种颜色,然后把它的邻接点都染上不同的颜色,发现某个邻接点染的颜色和自己相同的话,直接返回错,如果都染色成功了,那么就返回对。
代码
Nowcoder 215B AC确认,该题基本模板,只需要判断某仙人掌图是不是二分图输出2或3即可
#include <bits/stdc++.h>
using namespace std;
/*
* 本板子为潘骏同志的二分染色模板
*/
vector<int> egs[100005];
int col[100005];
bool dfs(int num,int fa)
{
if(col[num]!=-1 && col[num]==col[fa])
{
return false;//和爸爸颜色相同,返回错误
}
else if(col[num]!=-1 && col[num]!=col[fa])
{
return true;//已经访问过且和爸爸颜色不同,返回正确
}
if(col[num]==-1)
{
if(fa==0)//没访问过,第一个节点,那么就标上第一种颜色
col[num]=1;
else//没访问过,不是第一个节点,标上和爸爸相反的节点
col[num]=!col[fa];
}
for(int i=0;i<egs[num].size();i++)
{
if(egs[num][i]!=fa)
{
if(!dfs(egs[num][i],num))
{
return false;//有一个子节点出错就有问题
}
}
}
return true;//全染色成功返回正确
}
int main()
{
int n,m;
while(cin>>n>>m)
{
memset(col,-1,sizeof col);
for(int i=1;i<=n;i++)
{
egs[i].clear();
}
for(int i=0;i<m;i++)
{
int a,b;
cin>>a>>b;
egs[a].push_back(b);
egs[b].push_back(a);
}
if(dfs(1,0))
{
cout<<"2"<<endl;
}
else
{
cout<<"3"<<endl;
}
}
return 0;
}
匈牙利算法
很贪心的DFS过程,拿到一个节点,先和能和自己匹配的节点匹配一个,然后退出,再拿到下一个节点以后也是和自己能匹配的节点先匹配一个,如果发现之前的节点已经匹配过这个节点了,那就让上一个人再去匹配下一个节点,如果上一个人可以匹配下一个节点,那就让给自己,否则自己再去找找下一个。直到所有的点都遍历完了。适用于没有边权的二分图。题目如果没有保证是二分图,可能会错。
代码
HDU 5943 AC确认,此处为通用版
#include <iostream>
#include <vector>
#include <cstring>
/*
* 本板子为潘骏同志模拟的匈牙利算法
*/
using namespace std;
vector<int> egs[100005];
bool vis[100005];
int linker[100005];
bool finder(int num) {
if (num == -1)//没有匹配过就直接返回正确
{
return true;
}
for (int i = 0; i < egs[num].size(); i++) {
int nxt = egs[num][i];
if (!vis[nxt]) {
vis[nxt] = true;
if (finder(linker[nxt]))//如果上一个节点能让出来去找其他的节点匹配,那就匹配
{
linker[nxt] = num;
return true;
}
}
}
return false;//匹配失败
}
int hungary(int n) {
int ans = 0;
memset(linker, -1, sizeof linker);
for (int i = 1; i <= n; i++) {
memset(vis, 0, sizeof(vis));
if (finder(i))
ans++;
}
return ans;
}
int main() {
int n, m;
cin >> n >> m;
for (int i = 0; i < m; i++) {
int a, b;
cin >> a >> b;
egs[a].push_back(b);
}
cout << hungary(n) << endl;
return 0;
}
Kuhn-Munkres算法
KM算法用于带权的二分图匹配。和匈牙利算法的思想类似,也是一个个尝试匹配,如果匹配失败就回去把上一个匹配了的换一个人匹配,使用贪心的思想处理边权。首先给所有的选择方赋予一个期望值,初值为与该节点相连的所有边的最大权值,再将所有被选择方的期望值赋为0,这样一开始所有选择方节点只能与权值最大者匹配。一旦尝试匹配发生冲突,即匹配失败的情况,就把所有此次匹配涉及到的(就是被DFS访问过,存在冲突矛盾的节点)选择方期望增加,把所有被选择方的期望值降低,增加/降低的值就是此时选择失败的节点和其连接的最近可能匹配上的节点的期望值之差(好拗口啊,看代码吧)。再次尝试,直到匹配或该点期望降到0为止。
HDU 2255 AC确认,纯裸板题,输入的是邻接矩阵(我转成了链式前向星),没有改动
#include <iostream>
#include <vector>
#include <cstring>
#define INF 0x3f3f3f3f
using namespace std;
/*
* 本板子为潘骏同志模拟的KM算法
*/
struct edge {
int from;
int weight;
int to;
int nxt;
edge(int f, int t, int w, int n) : from(f), weight(w), to(t), nxt(n) {
}
edge() {}
};
vector<edge> edges;
int egs[605],expect[605],linker[605],match[605],need[605];
bool vis[605];
void addedge(int f,int t,int w) {
edges.emplace_back(f, t, w, egs[f]);
egs[f] = edges.size() - 1;
}
bool dfs(int num) {
if (num == -1) {//没有匹配过就直接返回正确
return true;
}
vis[num] = true;
for (int i = egs[num]; i != -1; i = edges[i].nxt) {
int nxter = edges[i].to;
if (vis[nxter]) {//每个被选择的只遍历一次
continue;
}
int temp=expect[num] + expect[nxter] - edges[i].weight;
if (temp==0) {//选择方和被选择方的期望相加为边权,说明被选择方为当前最优解。
vis[nxter] = true;
if (dfs(match[nxter])) {//如果上一个节点能让出来去找其他的节点匹配,那就匹配
match[nxter] = num;
linker[nxter] = i;
return true;
}
} else {
need[nxter] = min(need[nxter], temp);
}
}
return false;//匹配失败
}
int KuhnMunkres(int n) {
memset(expect, 0, sizeof expect);
memset(linker, -1, sizeof linker);
memset(match, -1, sizeof match);
for (int i = 0; i < edges.size(); i++) {
expect[edges[i].from] = max(expect[edges[i].from], edges[i].weight);
}
for (int i = 1; i <= n; i++) {
memset(need, 0x3f, sizeof need);//需求量是相对当前选择方的需求量
memset(vis, 0, sizeof vis);
while (!dfs(i) && expect[i] > 0) {
int miner = INF;
for (int j = n; j <= 2 * n; j++) {
if (!vis[j])
miner = min(need[j], miner);//求每个期望的改变量,即下一个可匹配点的需求量
}
for (int j = 1; j <= 2 * n; j++) {
if (vis[j]) {
if (j <= n)
expect[j] -= miner;
else
expect[j] += miner;
} else if(j>n)
need[j] -= miner;
//未匹配的选择方已经降低了期望,所以未匹配的被选择方可以降低需求
}
memset(vis, 0, sizeof vis);
}
}
int ans = 0;
for (int i = n; i <= 2 * n; i++) {
if (linker[i] != -1) {
ans += edges[linker[i]].weight;
}
}
return ans;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
int n;
while (cin >> n) {
edges.clear();
memset(egs, -1, sizeof egs);
for (int i = 1; i <= n; i++) {
for (int j = n + 1; j <= 2 * n; j++) {
int w;
cin >> w;
addedge(i, j, w);
}
}
cout << KuhnMunkres(n) << endl;
}
return 0;
}
下一篇: 关于日期正则表达式的思路详解