匈牙利算法是由匈牙利数学家Edmonds于1965年提出,因而得名。匈牙利算法是基于Hall定理中充分性证明的思想,它是部图匹配最常见的算法,该算法的核心就是寻找增广路径,它是一种用增广路径求二分图最大匹配的算法。(来自百度百科)
浅显的讲,就是寻找二部图的最大匹配,先写出几个概念;
二部图:二分图又称作二部图,是图论中的一种特殊模型。 设G=(V,E)是一个无向图,如果顶点V可分割为两个互不相交的子集(A,B),并且图中的每条边(i,j)所关联的两个顶点i和j分别属于这两个不同的顶点集(i in A,j in B),则称图G为一个二分图。(来自百度百科)
当子图中没有任何一个同时连接Xi和Yj的同一顶点的时候我们称为二部图的一个匹配,记为集合M
什么是最大匹配呢 如下图
当匹配集合中的边数达到最大的时候,这就是一个最大匹配,如上图;
再提出几个个概念
未覆盖点:就是在集合M中没有边连接到的点称为未覆盖点。
完美匹配:当M是二部图的最大匹配且没有未覆盖点的时候,则这个集合M就是二部图的完美匹配。上图中的匹配就是完美匹配,在符合最大匹配的同时,M集合中的边覆盖了二部图的所有点。
最后一个最关键的概念:增广路径 ;
匈牙利算法的核心就是不停的寻找增广路径来扩充匹配集合M,什么是增广路经呢?(来自百度百科)
1-P的路径长度必定为奇数。
2-起点在左,终点在右。
3-路径中的点左右交替出现。
4-只有起点和终点是未覆盖点,其他点都配对。
5 -对增广路径编号,所有奇数的边都不在M中,偶数边在M中。
6 -对增广路径取反得到的匹配比原来匹配多一个。
我们给出实例来理解。
我们寻找如上图的最大匹配;首先M集合为空(即没有边在里面),然后开始从X1寻找增广路,遵循2的原则我们只能在Yi中找,找到Y1,(X1,Y1 )这条路径,满足1-5的条件,取反,将(X1,Y1 )这条路径加入到M中。
接着,我们找到X2点,遵循原则,找到Y1,Y1不是未覆盖点,这个时候我们有两种选择,一个是深度搜索,一个是广度搜索,我们采用深度优先,虽然Y1不是未覆盖点,(X2,Y1)不是增广路,但是Y1连着X1,X1又和Y3相连,我们考虑( X2,Y1,X1,Y3 )这条路径,奇数?左右交替?起终点未覆盖?奇路径不属于M偶路径属于?满足所有增广路条件,所以这是一条增广路径,然后取反,得到如下图。
现在M集合中的路径有两条了,由于我们找到了增广路径,使得M中的边数量增加。所以增广路径是匈牙利算法的核心,每找到一条增广路径,意味这M集合中边的数量就会增加1,当找不到增广路径的时候,这个时候M中边的数量就是我们二部图的最大匹配数量。我们是怎样找到这条路径的呢,从X2开始寻找,我们先找到Y1,Y1不是未覆盖点,我们考虑Y1的原有匹配点X1,从X1开始寻找增广路,找到了Y3,当X1有增广路的时候,那么加上(X1,Y1)(X2,Y1)这两条路经,依然满足增广路条件。所以基于我们上面的理解可以给出寻找增广路的伪代码
while(找到Xi的关联顶点Yj){
if(顶点Yj不在增广路径上){
将Yj加入增广路
if(Yj是未覆盖点或者Yj的原匹配点Xk能找到增广路径){ //扩充集合M
将Yj的匹配点改为Xi;
返回true
}
}
返回false
}
从X2开始寻找是基于深度优先的,如果是基于广度优先呢?那么X2就会找到Y2。
给出C语言代码
typedef struct tagMaxMatch{
int edge[COUNT][COUNT];
bool on_path[COUNT];
int path[COUNT];
int max_match;
}GRAPH_MATCH;
void outputRes(int *path){
for (int i = 0 ; i<COUNT; i++) {
printf("%d****%d\n",i,*(path+i)); //Yj在前 Xi在后
}
}
void clearOnPathSign(GRAPH_MATCH *match){
for (int j = 0 ; j < COUNT ; j++) {
match->on_path[j] = false;
}
}
//dfs算法
bool FindAugPath(GRAPH_MATCH *match , int xi){
for (int yj = 0 ; yj < COUNT; yj++) {
if ( match->edge[xi][yj] == 1 && !match->on_path[yj]) { //如果yi和xi相连且yi没有在已经存在的增广路经上
match->on_path[yj] = true;
if (match->path[yj] == -1 || FindAugPath(match,match->path[yj])) { // 如果是yi是一个未覆盖点或者和yi相连的xk点能找到增广路经,
match->path[yj] = xi; //yj点加入路径;
return true;
}
}
}
return false;
}
void Hungary_match(GRAPH_MATCH *match){
for (int xi = 0; xi<COUNT ; xi++) {
FindAugPath(match, xi);
clearOnPathSign(match);
}
outputRes(match->path);
}
int main() {
GRAPH_MATCH *graph = (GRAPH_MATCH *)malloc(sizeof(GRAPH_MATCH));
for (int i = 0 ; i < COUNT ; i++) {
for (int j = 0 ; j < COUNT ; j++) {
graph->edge[i][j] = 0;
}
}
graph->edge[0][1] = 1;
graph->edge[0][0] = 1;
graph->edge[1][1] = 1;
graph->edge[1][2] = 1;
graph->edge[2][1] = 1;
graph->edge[2][0] = 1;
graph->edge[3][2] = 1;
for (int j = 0 ; j < COUNT ; j++) {
graph->path[j] = -1;
graph->on_path[j] = false;
}
Hungary_match(graph);
}