二分图 【总结】
来源:互联网 发布:泡妞宝鉴天地知我心二 编辑:程序博客网 时间:2024/06/05 11:37
路过发现有bug的地方,还请大神指出。 (ˇˍˇ) 想~
术语:最大匹配、最小路径覆盖、最小点覆盖、最大独立集、最大团、最大(小)权值匹配。
性质:
(1) 最小路径覆盖 = 节点数 - 最大匹配。
(2) 最小点覆盖 = 最大匹配。
(3) 最大独立集 = 节点数 - 最小点覆盖 = 节点数 - 最大匹配 = 补图的最大团。
(4) 最大团 = 补图的最大独立集。
(5) 对二分图染黑、白色,要求相邻节点颜色不同,问最多可以把多少个节点染成黑色 -> 求解最大独立集。
求解最大匹配算法有三个:匈牙利、HK、KM(大材小用了)
匈牙利算法:时间复杂度O(NM)
#include <cstdio>#include <cstring>#include <algorithm>#include <vector>#define MAXN (100+10)using namespace std;int N, M;vector<int> G[MAXN];bool used[MAXN];int match[MAXN];int DFS(int u){ for(int i = 0; i < G[u].size(); i++) { int v = G[u][i]; if(!used[v]) { used[v] = true; if(match[v] == -1 || DFS(match[v])) { match[v] = u; return 1; } } } return 0;}int main(){ while(scanf("%d%d", &N, &M) != EOF) { for(int i = 1; i <= N; i++) G[i].clear(); for(int i = 1; i <= M; i++) { int a, b; scanf("%d%d", &a, &b); G[a].push_back(b); } int ans = 0; memset(match, -1, sizeof(match)); for(int i = 1; i <= N; i++) { memset(used, false, sizeof(used)); ans += DFS(i); } printf("%d\n", ans); } return 0;}
HK:时间复杂度O(sqrt(N)M)
#include <cstdio>#include <cstring>#include <queue>#include <algorithm>#define MAXN 5000+10using namespace std;int mx[MAXN], my[MAXN];//记录X和Y集元素的匹配点int dx[MAXN], dy[MAXN];//记录距离标号bool used[MAXN];//标记是否使用vector<int> G[MAXN];//存储二分图int n, m;//点数 边数int numx, numy;//记录X集元素个数 Y集元素个数int DFS(int u)//同匈牙利算法 多了层次图的判定{ for(int i = 0; i < G[u].size(); i++) { int v = G[u][i]; if(!used[v] && dy[v] == dx[u] + 1) { used[v] = true; if(my[v] == -1 || DFS(my[v])) { my[v] = u; mx[u] = v; return 1; } } } return 0;}void HK_match(){ memset(mx, -1, sizeof(mx)); memset(my, -1, sizeof(my)); int ans = 0; while(1) { bool flag = false;//标记是否找到可增广路径 memset(dx, 0, sizeof(dx)); memset(dy, 0, sizeof(dy)); queue<int> Q; for(int i = 1; i <= numx; i++) if(mx[i] == -1) Q.push(i); while(!Q.empty())//寻找增广路 { int u = Q.front(); Q.pop(); for(int i = 0; i < G[u].size(); i++) { int v = G[u][i]; if(!dy[v]) { dy[v] = dx[u] + 1;//感觉和 Dinic里面建立层次图很像 if(my[v] == -1)//未匹配 说明找到增广路 flag = true; else { dx[my[v]] = dx[u] + 1; Q.push(my[v]); } } } } if(!flag)//未找到增广路 break; memset(used, false, sizeof(used)); for(int i = 1; i <= numx; i++) if(mx[i] == -1)//没有匹配 就开始找匹配 ans += DFS(i); } printf("%d\n", ans);//若X、Y集没有详细划分,则numx = numy = n,最后结果除2。}int main(){ while(scanf("%d%d", &n, &m) != EOF) { getMap();//建图 用G存储 HK_match();//HK算法 } return 0;}
KM 直接上求解最大(小)权匹配:求最小权值匹配,直接将边权取负,最后结果取负就可以了。
时间复杂度O(N*3)
#include <cstdio>#include <cstring>#include <algorithm>#define MAXN 400#define INF 100000000//注意INF值要比所有边权值大using namespace std;int match[MAXN];//匹配int lx[MAXN], ly[MAXN];//顶标int slack[MAXN];//记录T集中节点的松弛量int Map[MAXN][MAXN];//存储原图S-T节点间的关系bool visx[MAXN], visy[MAXN];int nx, ny;//S集中节点数目 T集中节点数目int DFS(int x){ visx[x] = true; for(int y = 0; y < ny; y++) { if(visy[y]) continue; int t = lx[x] + ly[y] - Map[x][y];//判断边<x,y>是否在相等子图里面 if(t == 0)//边<x,y>在相等子图里面 { visy[y] = true; if(match[y] == -1 || DFS(match[y])) { match[y] = x; return 1; } } else if(slack[y] > t)// 更新松弛量 slack[y] = t; } return 0;}int KM(){ memset(match, -1, sizeof(match)); memset(ly, 0, sizeof(ly)); for(int x = 0; x < nx; x++)//lx[x]初始化为Map[x][y]中最大值 { lx[x] = -INF; for(int y = 0; y < ny; y++) { if(Map[x][y] > lx[x]) lx[x] = Map[x][y]; } } for(int x = 0; x < nx; x++)//匹配 { for(int i = 0; i < ny; i++)//初始化T集中所有节点的 松弛量 slack[i] = INF; while(1) { memset(visx, false, sizeof(visx)); memset(visy, false, sizeof(visy)); if(DFS(x)) break;//匹配成功 //匹配失败 int d = INF; //寻找最小的松弛量d for(int i = 0; i < ny; i++) { if(!visy[i] && d > slack[i]) d = slack[i]; } //修改顶标 为了有更大机会完美匹配 //S集里面所有点 全部减去最小松弛量d for(int i = 0; i < nx; i++) { if(visx[i]) lx[i] -= d; } //为了保证S-T的匹配不离开相等子图即对边<x,y>依旧有lx[x] + ly[y] == Map[x][y] //T集里面所有点 全部增加最小松弛量d for(int i = 0; i < ny; i++) { if(visy[i] ly[i] += d; else//节点的松弛量 减少d slack[i] -= d; } } } int res = 0; for(int i = 0; i < ny; i++) { if(match[i] != -1)//有匹配 res += Map[match[i]][i];//累加权值 } return res;}int main(){ int N; while(scanf("%d", &N) != EOF) { nx = ny = N; for(int i = 0; i < N; i++) for(int j = 0; j < N; j++) scanf("%d", &Map[i][j]);//第i个和第j个匹配的权值 int ans = KM(); printf("%d\n", ans); } return 0;}
求解最大团算法:普通DFS 50个点就要跑8、9s,太慢。
优化DFS算法:
#include <cstdio>#include <cstring>#include <algorithm>#define MAXN (100+10)using namespace std;int N, M;int Clique[MAXN];//Clique[i]记录(i-N)这些节点可以构成的最大团int Map[MAXN][MAXN];//存储图int New[MAXN][MAXN];//每次建立从i-N这些节点 DFSint used[MAXN], Rec[MAXN];//Rec记录最大团里面的点int ans;//最大团节点数int DFS(int T, int cnt)//DFS遍历层次 计数{ if(T == 0) { if(ans < cnt) { ans = cnt; memcpy(Rec, used, sizeof(used)); return 1; } return 0; } for(int i = 0; i < T; i++) { if(T - i + cnt <= ans) return 0;//剪枝一 int u = New[cnt][i]; if(Clique[u] + cnt <= ans) return 0;//剪枝二 int num = 0; for(int j = i+1; j < T; j++) if(Map[u][New[cnt][j]]) New[cnt+1][num++] = New[cnt][j]; used[cnt+1] = u;//记录当前节点 if(DFS(num, cnt+1)) return 1; } return 0;}void MaxClique(){ memset(Clique, 0, sizeof(Clique)); ans = 0; for(int i = N; i >= 1; i--) { used[1] = i; int Size = 0; for(int j = i+1; j <= N; j++)//根据后面的节点构建新图 if(Map[i][j]) New[1][Size++] = j; DFS(Size, 1); Clique[i] = ans; }}int main(){ while(scanf("%d%d", &N, &M) != EOF) { memset(Map, 0, sizeof(Map)); for(int i = 1; i <= M; i++) { int a, b; scanf("%d%d", &a, &b); Map[a][b] = Map[b][a] = 1; } MaxClique(); printf("%d\n", ans); for(int i = 1; i <= ans; i++)//输出最大团里面的节点 { if(i > 1) printf(" "); printf("%d", Rec[i]); } printf("\n"); } return 0;}
0 0
- 二分图总结
- 二分图匹配总结
- 二分图总结
- 二分图匹配总结
- 二分图总结
- 二分图匹配总结
- 二分图匹配总结
- 二分图匹配总结
- 二分图总结
- 二分图匹配总结
- 二分图匹配总结
- 二分图 【总结】
- 二分图匹配总结
- 二分图总结
- 二分图总结
- 二分图性质总结
- 二分图匹配算法总结
- 二分图的一些总结
- 项目实践——JS为当前行内节点赋值避免覆盖解决方案
- [LeetCode]Perfect Squares
- EL表达式 参考手册
- 查询本周的所有数据
- LeetCode(128) Longest Consecutive Sequence
- 二分图 【总结】
- 程序员常用的十段代码片段
- margin/padding与块级元素/行内元素的关系
- MyEclipse6.5打jar包
- Android快速SDK(22)友盟升级统计库UmengUpdate【肌肉记忆,分钟接入】
- javac编译的过程
- DATA GUARD 简介
- Run WordCount on Hadoop
- android 内置关系型数据库 SQLite