匈牙利算法
在介绍匈牙利算法之前还是先提一下几个概念,下面M是G的一个匹配。
若 ,其中边 已经在匹配M上。
- M-交错路:p是G的一条通路,如果p中的边为属于M中的边与不属于M但属于G中的边交替出现,则称p是一条M-交错路。
- M-饱和点:对于 ,如果v与M中的某条边关联,则称v是M-饱和点,否则称v是非M-饱和点。
都属于M-饱和点,而其它点都属于非M-饱和点。 - M-可增广路:p是一条M-交错路,如果p的起点和终点都是非M-饱和点,则称p为M-可增广路。如 (不要和流网络中的增广路径弄混了)。
- 求最大匹配的一种显而易见的算法是:先找出全部匹配,然后保留匹配数最多的。但是这个算法的时间复杂度为边数的指数级函数。因此,需要寻求一种更加高效的算法。下面介绍用增广路求最大匹配的方法(称作匈牙利算法,匈牙利数学家Edmonds于1965年提出)。
增广路的定义(也称增广轨或交错轨):
- 若P是图G中一条连通两个未匹配顶点的路径,并且属于M的边和不属于M的边(即已匹配和待匹配的边)在P上交替出现,则称P为相对于M的一条增广路径。
由增广路的定义可以推出下述三个结论:
- (1)P的路径个数必定为奇数,第一条边和最后一条边都不属于M。
- (2)将M和P进行取反操作可以得到一个更大的匹配 。
- (3)M为G的最大匹配当且仅当不存在M的增广路径。
算法轮廓:
- (1)置M为空
- (2)找出一条增广路径P,通过异或操作获得更大的匹配 代替M
- (3)重复(2)操作直到找不出增广路径为止 [1]
复杂度
时间复杂度邻接矩阵:最坏为O(n ^ 3) 邻接表:O(n * m)
空间复杂度 邻接矩阵:O(n ^ 2) 邻接表:O(n * m)
模板题
POJ1469
Sample Input
2
3 3
3 1 2 3
2 1 2
1 1
3 3
2 1 3
2 1 3
1 1
Sample Output
YES
NO
题目大意 一个学生可以学习多门课,一门课可以被多名学生学,但不可以同时学,问是否可以每个学生只学习一门课,每个学生都有一门课可以学。输入给出了学生可以学习的课。
算法模板代码
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
using namespace std;
const int N = 310;
bool line[N][N];
bool used[N];
int net[N];
int n, m;
bool find(int x){
for(int i = 1; i <= n; i++){//遍历学生 查找可以学习x这门课程的学生
if(line[x][i] && !used[i]){//如果i可以学习x这门课程,并且i没有学习过其他的课程
used[i] = 1;
if(!net[i] || find(net[i])){//如果这个学生没有学习过其他的课程或者他学习的课程可以被其他学生学习
net[i] = x;//这名学生学习x这门课程
return true;
}
}
}
return false;
}
int mach(){
int sum = 0;
memset(net, 0, sizeof net);
for(int i = 1; i <= m; i++){//遍历课程 寻找可以学习它的学生
memset(used, false, sizeof used);
if(find(i)) sum++;//找到一个匹配数+1
}
return sum;
}
int main(){
int t;
cin >> t;
while(t--){
memset(line, 0, sizeof line);
scanf("%d%d", &m, &n); //输入m门课, n名学生
for(int i = 1; i <= m; i++){
int x;
scanf("%d", &x);
for(int j = 1; j <= x; j++){
int y;
scanf("%d", &y); //第i门课可以被y学生学
line[i][y] = 1;
}
}
if(mach() == m) cout << "YES" << endl; //如果匹配数等于科程的数这YES
else cout << "NO" << endl;
}
return 0;
}