• [置顶] 白话二分匹配之最大匹配+附上hdu2063解题报告


    最近开始学习图论的二分匹配,关于最大匹配做一次小总结,希望自己后面回头来看一目明了,也对刚接触的人有帮助:

    ps:开始有的文字很多....对于很多人来说一看到文字就烦啦...不过这个总结是针对匈牙利算法的总结,后面的匈牙利代码中有详细说明并结合图片说明,相信对刚接触的人会有帮助

    个人觉得对于一个知识点最好是先知道这个”东东“是什么,然后在学习概念比较好,关于先知道是个什么“东东”--就是把这个知识点简单明了化,然后去了解其中令人头疼的概念!

    关于二分匹配的最大匹配:

    如果有G1、G2、G3三个女孩,B1。B2。B3三个男孩,有一天老师说要调整座位,就了解三个女孩的想法(想和哪一个男孩坐在一起----男孩的想法不考虑),G1说和三个男孩任何一个坐一起都喜欢,G2就说只想和B1坐一起,G3只想和 B2坐一起,那么老师改怎么安排座位,让尽可能多的女孩满意?这里就引入匹配:结合图形理解:

    可以看到让G1和B3坐一起(配对/匹配)G2和B1一起,G3和B2一起,那么三个人都会满意,如果换成其他的方案,就不会是三个女孩都满意啦...那么这就是最大匹配

    --------------------------------------------------------------------------------------------------------------------------------------------------------在大概了解了最大匹配是什么东东之后,就有必要了解一些概念啦,推荐:基本概念术语

    下面是关于求最大匹配的匈牙利算法:

    令G = (X,*,Y)是一个二分图,其中,X = {x1,x2,...xm}, Y = {y1,y2,...yn}。令M为G中的任一个匹配。
    1)讲X的所有不与M的边关联的顶点标上(@),并称所有的顶点为未被扫描的。转到 2)。
    2)如果在上一步没有新的标记加到X的顶点上,则停止。否则转到 3)。
    3)当存在X被标记但未被扫描的顶点时,选择一个被标记但未被扫描的X的顶点,比如,xi,用(xi)标记Y的所有顶点,这些顶点被不属于M且尚未标记的边连到xi .现在,顶点xi是被扫描的。如果不存在被标记但未被扫描的顶点,则转到 4)。
    4)如果在步骤 3)没有新的标记被标到Y的顶点上,则停止。否则,转到 5)。
    5)当存在Y被标记但未被扫描的顶点时,选择Y的一个被标记但未被扫描的顶点,比如yi,用(yi)标记X的顶点,这些顶点被属于M且尚未标记的边连到yi.现在,顶点yi是被扫描的。如果不存在被标记但未被扫描的顶点,则转到 2)。
    也可以叙述为:
    [ZZ]匈牙利算法
    关键在于匈牙利算法的递归过程中有很多重复计算的节点,而且这种重复无法避免,他不能向动态规划一样找到一个“序”将递归改为递推

    下面结合hdu2063模板详细解释匈牙利算法:

    题目和上面的座位匹配差不多

    // 1216k 15ms
    #include<stdio.h>
    #include<string.h>
    
    #define MAX 501
    
    int map[MAX][MAX];//map[i][j]=1表示i想和j一起,为0就是没有想一起的想法
    int link[MAX];//link[i]=t就表示和i配对/匹配的是t!这里注意i表示是i号男生和t号女生配对
    int useif[MAX];//useif[i]只有1和0两个值,表示i是不是当前考虑要匹配的,为1就是当前考虑i要匹配
    //看了几个定义之后就从main函数中开始看
    int n,m,k;
    
    bool dfs(int t)
    {
    	for(int i=1;i<=n;i++)//这里对于女孩t,考虑每一个男生能否和t匹配
    	{
    		if(!useif[i] && map[t][i])//i号男生没有被考虑,并且t想和i一起
    		{
    			useif[i]=1;//标记考虑
    			if(link[i] == -1 || dfs(link[i]))//如果i没有匹配的女生(link[i]=-1)或者--这个在开始有点难理解,后面给出一段话结合图片有助理解,这里只要明白这语句的意思是:if(t能和i匹配)
    			{
    				link[i]=t; return true;//能匹配就把link[i]更新为t,返回true
    			}
    		}
    	}
    	return false;
    }
    
    int match()
    {
    	int sum=0;
    	memset(link,-1,sizeof(link));//匹配之前,每个人(题目中是男孩)匹配为-1
    	for(int i=1;i<=m;i++)//这里,对于每一个女孩i,去选一个男孩匹配
    	{
    		memset(useif,0,sizeof(useif));//初始化对于女孩i,没有考虑任何男生
    		if(dfs(i))//看i能不能从男生中选一个匹配,调用dfs
    			sum++;//能匹配就产生一对啦....就加一
    	}
    	return sum;//返回能产生的对数
    }
    
    int main()
    {
    	while(scanf("%d",&k),k)
    	{
    		scanf("%d%d",&m,&n);
    		int i,j;
    		for(i=1;i<=m;i++)
    			for(j=1;j<=n;j++)
    				map[i][j]=0;//初始化都没有想法
    		int a,b;
    		for(i=0;i<k;i++)
    		{
    			scanf("%d%d",&a,&b);//对于每个a想和b一起就赋值map[a][b]=1
    			map[a][b]=1;
    		}
    		printf("%d
    ",match());//开始match匹配,调用match
    	}
    	return 0;
    }

    这里对于上面的if加以说明:

    拿案例来说(左边的是女生的标号,右边是等待女生选择的男生标号)

    1   1
    1    2

    1   3

    2   1

    2   3

    3   1

    最开始我们用女孩一号去匹配得到如下图:

    然后我们对于G2去选择,此时我们依旧从1号男生开始考虑,因为useif[1]在女孩2开始匹配的时候初始化为0啦,并且女孩2也想和男孩1匹配,就标记useif[1]为正在考虑,然后发现link[1]=1(也就是1号男生被1号女生选走啦)但是,确定是否2号女孩能和1号男孩匹配的还有一个条件,就是dfs(link[1]),意思是看能不能把和1号男生匹配的1号女生再去选择另一个男生配对,那么这个函数调用,我们的女1号就选择的2号男生,所以,这里的2号女生就可以和1号男生在一起啦...大笑(有图有真相):

    -----那么,后面就是一样的啦...

    二分匹配之最大匹配学习总结完毕.......

  • 相关阅读:
    Scala: 包对象
    云服务使用技巧
    leetcode上一些常见的链表问题
    数据挖掘的价值
    leetcode上的一些分治算法
    双指针的应用
    KNN算法
    线性回归
    leetcode上的一些单链表
    leetcode上的一些栈、队列问题
  • 原文地址:https://www.cnblogs.com/keanuyaoo/p/3278399.html
Copyright © 2020-2023  润新知