匈牙利算法的应用场景:二分图
可以理解分为两个集合,集合内部的元素不进行彼此配对,但是两集合之间进行配对
A集合与B集合进行配对,二分图如下
以上即为二分图 ,其中A集合与B集合进行配对,这个图可以刨析为三个部分,A集合为一部分,B集合为一个部分,连接A集合和B集合的线是另一部分,总共这三个部分,如果用代码实现就可以记录这三个部分
连接AB集合的线可以视为对于AB集合的关系
此时带入一个情景
假设你是月老,此时需要对一群男女进行牵红线,但是需要考虑男女之间的意愿,输入的K代表女生所心仪的男生(也就是关系的数量),直到输入0之后停止,M为女生的数量,N代表男生的数量,而你作为月老所需要做的就是尽可能多的对男女进行配对
#include <stdio.h>
bool G[510], B[510], relate[510][510];
//此处的G用来存储女生是否已经有配对的伴侣
//B用来存储男生是否已经被配对
//relate用来记录女生对于男生的心仪情况
//G和B相当于AB集合,而relate就相当于AB之间的关系
int K, M, N;
int main()
{
scanf("%d %d %d", &K, &M, &N);
int girl = 0, boy = 0;
int num = 0;
while (K != -1)
{
K--;
//表示输入关系的数量,每输入一次就对K进行-1
scanf("%d", &girl);
if (girl == 0)
{
break;
}
scanf("%d", &boy);
relate[girl][boy] = true;
//以女生为行,男生为列,相关关系默认为flase,当输入关系里面有时,更改值成为false
if (G[girl] == false)
{
if (B[boy] == false)
{
//当女生没有伴侣,男生也没有伴侣的时候就可以直接将他俩牵起来
//此时需要更改的是男生和女生的配偶状态
B[boy] = true;
G[girl] = true;
num++;
//此时算作是已经是配对成功一对,所以num++
}
else
{
//如果男生1已经暂定配对了一个伴侣
//此时将这个男生1的伴侣先找出来,并检查他的伴侣是否还有其它心仪的男生
//如果这个女生1有其他心仪的对象,且这个男生2也没有伴侣
//那么就将这个男生2配对给这个女生1,boy所记录男生就可以配给girl记录的女生
//如果这个男生1的伴侣没有其他心仪的对象,或者其他心仪对象都有伴侣
//那么没有办法配对了,所以num不进行操作
for (int i = 1; i <= M; i++)
{
if (relate[i][boy] == true && G[i] == true)
{
for (int k = 1; k <= N; k++)
{
if (relate[i][k] == true && B[k] == false && boy != k)
{
G[girl] = true;
B[k] = true;
num++;
break;
}
}
}
}
}
}
}
printf("%d", num);
}
粗略的复述一下这个想法
就是G集合和B集合分别表示女生和男生的配偶情况,如果是G或者B有了对象,就让G和B所对应的值改为true,如图所示
而对于实行配对的逻辑就是
而过山车这道题也可以用这种方式进行解决
#include <stdio.h>
#include <string.h>
int map[1000][1000] = {0};
//map记录两个集合之间的关系
int boy[500] = {0};
int flag[500] = {0};
int n, a, b, M, N, num;
int fun(int z);
int main() {
while (scanf("%d", &n) != EOF && n)
{
num = 0;
memset(map, 0, sizeof(map));
memset(boy, 0, sizeof(boy));
//对两个数组进行初始化,初始化值为0
scanf("%d %d", &M, &N);
while (n--)
{
scanf("%d %d", &a, &b);
map[a][b] = 1;
//此处的循环是为了记录所有的关系
}
for (int i = 1; i <=M; i++)
{
memset(flag, 0, sizeof(flag));
//每循环一次,就将flag数组的值重新进行初始化
num += fun(i);
//这一步就是配对步骤,由fun函数对于关系进行判断
//i依旧是代表女生,传入相当于对没一个女生进行配对
}
printf("%d\n", num);
}
return 0;
}
int fun(int z) {
for (int k = 1; k <= N; k++)
{
//此处的k是在关系网之中的男生
if (map[z][k] == 1 && flag[k] ==0)
//判断条件为筛选有关系的z,k且k没有被访问过
{
flag[k] = 1;
//一旦进入该语句,证明k已经被访问过了,所以更改k的值
if (boy[k] == 0 || fun(boy[k]) )
{
boy[k] = z;
return 1;
}
//此处判断语句,分为两种情况
//第一种情况:k所对应的男生没有配对对象,这种情况下直接对二者进行配对
//第二种情况:k所对应的男生有配对对象,flag已经对此次访问过的对象进行
//了标记,所以再次进行调用,对除了目前这个男生的配对对象进行再次配对,
//一直到寻找到目标男生,或者找不到为止
}
}
return 0;
}
以上是另一种实现方法,此时用boy这个数组来记录的是与之配对的女生的编号,flag在每一次寻找配对对象的过程中避免多次访问,且在函数进行递归的过程中,能够有效的避免再次访问相同的男生,以找到相应的男生。