匈牙利算法(Hungarian method)是由匈牙利数学家Edmonds于1965年提出,因而得名。匈牙利算法是基于Hall定理中充分性证明的思想,它是二分图匹配最常见的算法,该算法的核心就是寻找增广路径,它是一种用增广路径求二分图最大匹配的算法。
二分图的一些性质:
1.最小点覆盖=最大匹配。
定义:点覆盖是图中一些点的集合,且对于图中所有的边,至少有一个端点属于点覆盖,点数最小的覆盖就是最小点覆盖
2.最小边覆盖=最大独立集=图中点的个数-最大匹配。
定义:边覆盖是图中一些边的集合,且对于图中所有的点,至少有一条集合中的边与其相关联,边数最小的覆盖就是最小边覆盖。
3.最小路径覆盖=原图的节点数-新图最大匹配。
(1).有向无环图最小不相交路径覆盖
·定义:用最少的不相交路径覆盖所有顶点。
·定理:把原图中的每个点V拆成Vx和Vy,如果有一条有向边A->B,那么就加边Ax->By。这样就得到了一个二分图,最小路径覆盖=原图的节点数-新图最大匹配。
(2).有向无环图最小可相交路径覆盖
.定义:用最小的可相交路径覆盖所有顶点。
·算法:先用floyd求出原图的传递闭包,即如果a到b有路,那么就加边a->b。然后就转化成了最小不相交路径覆盖问题。
性质转自:https://blog.csdn.net/pbihao/article/details/54912439
匈牙利算法,每一次寻找一条增广路来达到多增加一条边的效果。求二分图最大匹配
代码:
//时间复杂度:O(N*E)
int line[N][N]; //i,j之间是否存在关系,即可以匹配,记得初始化
int used[N]; //每一轮寻找增广路径时第i个妹子是否被使用过
int Next[N]; //Next[i]=x代表第i个妹子和x号男生是匹配的,记得初始化
int n,m;
bool Find(int x)
{
for(int i=1;i<=m;i++)
{
if(line[x][i]&&!used[i])
{
used[i] = 1; //第i个妹子被使用了
if(Next[i]==0||Find(Next[i])) //判断第i个妹子是否有了男朋友,没有的话就可以匹配,有的话就把next[i]号男生转移掉再匹配
{
Next[i] = x;
return true;
}
}
}
return false;
}
int maxMatch()
{
memset(Next,0,sizeof(Next));
int sum = 0;
for(int i=1;i<=n;i++)
{
memset(used,0,sizeof(used));
if(Find(i))
sum++;
}
return sum;
}
2.hopcroft-carp算法
一次通过bfs来找到多条增广路,来更新所有点。求二分图最大匹配
//时间复杂度O(sqrt(n)*E)
const int MAXN=550;// 最大点数
int bmap[MAXN][MAXN];//二分图
int cx[MAXN];//cx[i]表示左集合i顶点所匹配的右集合的顶点序号
int cy[MAXN]; //cy[i]表示右集合i顶点所匹配的左集合的顶点序号
int dis;
int n,m;
int dx[MAXN],dy[MAXN]; //dx表示到x的距离,dy表示到y的距离
int used[MAXN]; //在每次增广中是否使用i点
bool SearchPath() //bfs寻找增广路集
{
queue<int>Q;
dis = INF; //存每一次增广的距离
memset(dx,-1,sizeof(dx));
memset(dy,-1,sizeof(dy));
for(int i=1;i<=n;i++)
{
if(cx[i]==-1) 将未遍历的节点入队,并初始化次节点距离为0
{
Q.push(i);
dx[i] = 0;
}
}
while(!Q.empty())
{
int u = Q.front();
Q.pop();
if(dx[u]>dis)
break;
//取右边的节点
for(int v=1;v<=m;v++)
{
if(bmap[u][v]&&dy[v]==-1)
{
dy[v] = dx[u] + 1; //v的距离为u的对应距离+1
if(cy[v]==-1) //如果该点未匹配,增广路形成
dis = dy[v];
else //如果该点已匹配,那么接着往下搜
{
dx[cy[v]] = dy[v] + 1;
Q.push(cy[v]);
}
}
}
}
return dis!=INF;
}
bool DFS(int u)
{
for(int v=1;v<=m;v++)
{
//如果该点没有被使用过,并且距离为上一节点+1
if(!used[v]&&bmap[u][v]&&dy[v]==dx[u]+1)
{
used[v] = 1; //标记使用过该点
//如果该点已经被匹配了并且为最后一个匹配点,那么这条路径不是增广路,即这条路的结点已经匹配
if(cy[v]!=-1&&dy[v]==dis)
continue;
if(cy[v]==-1||DFS(cy[v]))
{
cy[v]=u,cx[u]=v;
return true;
}
}
}
return false;
}
int MaxMatch()
{
int sum = 0;
memset(cx,-1,sizeof(cx));
memset(cy,-1,sizeof(cy));
while(SearchPath()) //当存在增广路,继续松弛
{
memset(used,0,sizeof(used)); //每一次的右边的点是否用过
for(int i=1;i<=n;i++)
{
if(cx[i]==-1&&DFS(i)) //如果当前这个点没连过,且能找到增宽路
sum++;
}
}
return sum;
}
3.KM算法。
btw:怎么求最大边权积……首先我们只会做最大边权和,那么怎么化和为积呢?我想到了log函数的一个性质:log X+log Y = log X*Y 。所以只要把原边权取log,就可以通过求新图的最大边权和得到最大边权积了……
/* KM算法,求带权二分图最大权匹配,复杂度O(nx*nx*ny)
* 匹配的原则是:lx+ly只与权重相同的边匹配,若是找不到边匹配,对此条路径的所有左边顶点-1,
右边顶点+1,再进行匹配,若还是匹配不到,重复+1和-1操作。
* 若求最小权匹配,可将路径权值取相反数,结果取相反数
* 点的编号从0开始
*/
const int N = 310;
const int INF = 0x3f3f3f3f;
int nx,ny; //两边的点数
int g[N][N]; //二分图描述,g[i][j]代表i到j之间的权值,最大匹配初始化为-INF表示无边,同理最小匹配初始化为INF
int linker[N]; //记录y链接的哪个x,-1代表无
int lx[N],ly[N];//y中各点匹配状态,x,y中的点标号
int slack[N]; //slack为修改量
bool visx[N],visy[N];
bool DFS(int x)
{
visx[x] = true;
for(int y = 0; y < ny; y++)
{
if(visy[y])continue; //用过了就继续
int tmp = lx[x] + ly[y] - g[x][y];
if(tmp == 0) //符合匹配要求
{
visy[y] = true;
if(linker[y] == -1 || DFS(linker[y]))
{
linker[y] = x;
return true;
}
}
else if(slack[y] > tmp)
slack[y] = tmp;
}
return false;
}
int KM()
{
memset(linker,-1,sizeof(linker));
memset(ly,0,sizeof(ly)); //初始右边期望值都是0
for(int i = 0;i < nx;i++)
{
lx[i] = -INF; //左边期望值为最大边权
for(int j = 0;j < ny;j++)
if(g[i][j] > lx[i])
lx[i] = g[i][j];
}
//开始解决左边匹配问题
for(int x = 0;x < nx;x++)
{
for(int i = 0;i < ny;i++)
slack[i] = INF; //因为取最小值,初始化为无穷大
while(true)
{
// 为左边解决归宿问题的方法是 :如果找不到就降低期望值,直到找到为止
// 记录每轮匹配中左右两边是否被尝试匹配过
memset(visx,false,sizeof(visx));
memset(visy,false,sizeof(visy));
if(DFS(x)) //找到匹配,退出
break;
//没找到,降低期望值
//最小可降低期望值
int d = INF;
for(int i = 0;i < ny;i++)
if(!visy[i] && d > slack[i])
d = slack[i];
for(int i = 0;i < nx;i++)
if(visx[i])
lx[i] -= d;
for(int i = 0;i < ny;i++)
{
if(visy[i])ly[i] += d;
else slack[i] -= d;
}
}
}
int res = 0;
for(int i = 0;i < ny;i++)
if(linker[i] != -1)
res += g[linker[i]][i];
return res;
}