重要说明:本文是个人学习所作,资料绝大部分来源于网络,侵删!
参考博客:
目录
一、思考一下
问题一:匈牙利算法干什么的?
问题二:匈牙利算法的原理是什么?
二、有关知识准备
对于图论而言,匈牙利算法(Hungarian algorithm)主要用于解决图论中二分图的最大匹配问题,因此涉及到二分图、匹配等有关概念。下面对这些概念做一下简单介绍。
2.1 二分图
二分图(bipartite graph):一类特殊的图,它的点集可分成两个子集,任意一个边的端点各在一个子集。
一个二分图例子:
2.2 匹配
匹配:一组边的集合,边有要求 :不能有公共端点
一个匹配例子:
引申问题:有多少匹配?最大匹配的边数?
对匹配进一步观察,可以定义如下的特殊性的匹配:
完备匹配:一种特殊匹配,覆盖了原图的所有点。
二分图与完备匹配:用映射观点看,不能一对多、多对一,只能双向一对一+满射,即双射。有
个完备匹配 。
完美匹配和最大匹配:完美匹配一定是最大匹配(若完美匹配不是最大匹配,那么还存在更大的,即还能添加新的边,但完美匹配的图中所有点都匹配了),但最大匹配不一定是完美匹配(因为有的图不一定存在完美匹配)。
最优匹配(最大权匹配) :(若边带有权重)最优匹配是指边权值和最大的那个完备匹配。
最优匹配与二分图:若X、Y相等,最优匹配必定是完备匹配;若不等,可通过加点补权为0转化。
2.3 最小覆盖
二分图的最小覆盖分为最小点覆盖和最小路径覆盖,分别解释如下:
最小点覆盖:最少的点使得每个边至少都被关联了一次;二分图最小定点覆盖数=二分图最大匹配边数。
最小路径覆盖:最少的路径覆盖二分图所有顶点;二分图最小路径覆盖数=点数-最大匹配边数。
2.4 独立集、交替路和增广路
最大独立集:一个点集,该集合中任意两点相连路径在原图中无对应;NP完全问题;二分图最大独立集点数=图点数-最大匹配边数。
交替路:给定图及其匹配,交替路是指一个路径:从未匹配点出发,交替经过匹配边和为匹配边。
增广路:一个特殊交替路,其终点为一个未匹配点,且该未匹配点不同于起始的未匹配点。
经过图论学者们的研究,增广路具有一些性质,可参考博客匈牙利算法详解
三、匈牙利算法的图论应用
匈牙利算法主要解决两个问题:最大匹配的边数与最小路径覆盖数。
3.1 最大匹配问题
如何用匈牙利算法解决最大匹配问题呢?下面举个形象的例子介绍匈牙利算法:
如上面这个二分图,我们这样来看待这个图:假设你是红娘,你最多能撮合多少对情侣?(不可能共用顶点,即最大匹配边数问题)
一个可能能行的大致想法:我们还是从遍历所有点的想法开始,只不过可以在遍历过程中设立一些选项使得我们遍历完后得到的边数尽可能大(这里先不考虑是不是理论上最大)
具体解释下:
1.从B1开始遍历,随机给他匹配一个G2
2.遍历到B2时发现,B2只喜欢G2,不给他匹配不就少了个边?于是返回B1发现B1还有备选,果断给B1换个,这样两全其美。
3.B3随机选,如G1;B4只能选G4,但G4已经和B1匹配了,取消这个匹配又会引发一些连锁反应,于是B4注定单身了。
程序实现(c++):
int M, N; //M, N分别表示左、右侧集合的元素数量
int Map[MAXM][MAXN]; //邻接矩阵存图
int p[MAXN]; //记录当前右侧元素所对应的左侧元素
bool vis[MAXN]; //记录右侧元素是否已被访问过
bool match(int i)
{
for (int j = 1; j <= N; ++j)
if (Map[i][j] && !vis[j]) //有边且未访问
{
vis[j] = true; //记录状态为访问过
if (p[j] == 0 || match(p[j])) //如果暂无匹配,或者原来匹配的左侧元素可以找到新的匹配
{
p[j] = i; //当前左侧元素成为当前右侧元素的新匹配
return true; //返回匹配成功
}
}
return false; //循环结束,仍未找到匹配,返回匹配失败
}
int Hungarian()
{
int cnt = 0;
for (int i = 1; i <= M; ++i)
{
memset(vis, 0, sizeof(vis)); //重置vis数组
if (match(i))
cnt++;
}
return cnt;
}
3.2 最小点覆盖问题
解决这个问题,只需要一个定理:一个二分图中的最大匹配数等于这个图中的最小点覆盖数。
四、匈牙利算法的实际应用
匈牙利算法(Hungarian algorithm),由匈牙利数学家Edmonds于1965年提出,它是全局最近邻(Global Nearest Neighbor,GNN)数据关联思想的一种具体实现,其最早用于求解经济学领域中的任务分配问题,后来发展成为图论领域中求解二分图有关问题的算法。
上面已经介绍了匈牙利算法在图论中的应用,下面回归到匈牙利算法最开始提出的时候,探讨一下匈牙利算法在实际中的应用。
4.1 一个指派问题的例子
假设有三位工人A,B和C,需要分配他们每人完成一件工作;对于不同的工作他们所需要花费的时间不同,如下表所示。问题就是要找到一套耗时最小的指派方案。用矩阵表示如下:
4.2 匈牙利算法的流程
4.3 匈牙利算法用于指派问题
第一步:找出每一行中值最小的元素,然后把该行所有元素都减去这一最小值
第二步:对于每一列,找到该列的最小值,然后该列的数都减去这个最小值
第三步:用最少的水平线和垂直线覆盖掉矩阵的所有0元素。如果需要n条线,那么在这些0中就存在最优解。算法结束
第四步:找到没被线覆盖的行列中的最小的元素,记作k。所有没被覆盖的元素都减去k,被覆盖两次的元素加上k
最后:刚好用3条线即可覆盖所有的0,算法结束
即最后指派A拖地,B擦桌,C扫厕所。
4.4 程序实现(python)
from scipy.optimize import linear_sum_assignment
cost =np.array([[4,1,3],[2,0,5],[3,2,2]])
row_ind,col_ind=linear_sum_assignment(cost)
print(row_ind)#开销矩阵对应的行索引
print(col_ind)#对应行索引的最优指派的列索引
print(cost[row_ind,col_ind])#提取每个行索引的最优指派列索引所在的元素,形成数组
print(cost[row_ind,col_ind].sum())#数组求和
由于时间有限,先做到这里了。更完整内容见参考博客就行啦。