运筹学匈牙利算法示例
- 格式:ppt
- 大小:634.50 KB
- 文档页数:25
匈牙利算法计算机题目摘要:1.匈牙利算法的概述2.匈牙利算法的应用领域3.匈牙利算法在计算机题目中的应用实例4.如何使用匈牙利算法解决计算机题目5.结论正文:一、匈牙利算法的概述匈牙利算法(Hungarian algorithm)是一种求解二分图最大匹配问题的算法,由匈牙利数学家Mátéi Kálmán 于1937 年提出。
该算法采用贪心策略,通过不断迭代寻找图中未被匹配的顶点,并将其与待匹配的顶点配对,直到所有顶点都被匹配或者无法找到匹配的顶点为止。
匈牙利算法在计算机科学、运筹学、图论等领域具有广泛的应用。
二、匈牙利算法的应用领域1.任务分配:在项目管理中,可以将任务分配给具有不同技能和时间的团队成员,以实现最优任务分配。
2.资源调度:在资源有限的情况下,通过匈牙利算法可以实现最优资源调度,提高资源利用率。
3.数据融合:在数据融合领域,匈牙利算法可以应用于数据融合和数据挖掘,提高数据分析效果。
4.社交网络:在社交网络中,匈牙利算法可以用于寻找最优好友推荐,提高用户满意度。
三、匈牙利算法在计算机题目中的应用实例以N 个学生和N 个课程为例,每个学生可以选择修习若干课程,每个课程有一定的容量限制。
要求通过匈牙利算法找到学生和课程之间的最大匹配数,使得每个学生的课程总容量不超过其限制,同时所有课程都被充分使用。
四、如何使用匈牙利算法解决计算机题目1.构建邻接矩阵:根据题目中给出的学生和课程信息,构建一个N×N 的邻接矩阵,表示学生和课程之间的选择关系。
2.初始化匹配矩阵:初始化一个N×N 的匹配矩阵,用于记录学生和课程之间的匹配情况。
3.迭代寻找匹配:根据邻接矩阵和匹配矩阵,采用贪心策略迭代寻找未被匹配的顶点,并将其与待匹配的顶点配对,更新匹配矩阵。
4.判断匹配情况:当所有顶点都被匹配或者无法找到匹配的顶点时,迭代结束。
若所有顶点都被匹配,输出匹配矩阵;否则,输出无法找到匹配的顶点。
运筹学匈牙利法运筹学匈牙利法(Hungarian Algorithm),也叫匈牙利算法,是解决二部图最大(小)权完美匹配(也称作二分图最大权匹配、二分图最小点覆盖)问题的经典算法,是由匈牙利数学家Kuhn和Harold W. Kuhn发明的,属于贪心算法的一种。
问题描述在一个二分图中,每个节点分别属于两个特定集合。
找到一种匹配,使得所有内部的节点对都有连边,并且找到一种匹配方案,使得该方案的边权和最大。
应用场景匈牙利算法的应用场景较为广泛,比如在生产调度、货车调度、学生对导师的指定、电影的推荐等领域内,都有广泛的应用。
算法流程匈牙利算法的伪代码描述如下:进行循环ɑ、选择一点未匹配的点a作为起点,它在二分图的左边β、找出a所有未匹配的点作为下一层节点ɣ、对下一层的每个节点,如果它在右边未匹配,直接匹配ɛ、如果遇到一个已经匹配的节点,进入下一圈,考虑和它匹配的情况δ、对已经匹配的点,将它已经匹配的点拿出来,作为下一层节点,标记这个点作为已被搜索过ε、将这个点作为当前层的虚拟点,没人配它,看能否为它找到和它匹配的点ζ、如果能匹配到它的伴侣,令它们成对被匹配最后输出最大权匹配。
算法优缺点优点:相比于暴力求解二分图最大权匹配来说,匈牙利算法具有优秀的解决效率和高效的时间复杂度,可以在多项式时间(O(n^3))内解决二分图最大权匹配问题。
缺点:当二分图较大时,匈牙利算法还是有很大的计算复杂度,复杂度不佳,算法有效性差。
此时就需要改进算法或者使用其他算法。
总结匈牙利算法是一个常见的解决二分图最大权匹配问题的算法,由于其简洁、易用、效率优秀等特性,广泛应用于学术和实际问题中。
匈牙利算法虽然在处理较大规模问题时效率不佳,但仍然是一种值得掌握的经典算法。
匈牙利算法详细步骤例题
嘿,朋友们!今天咱就来讲讲这神奇的匈牙利算法。
你可别小瞧它,这玩意儿在好多地方都大有用处呢!
咱先来看个具体的例子吧。
就说有一堆任务,还有一堆人,要把这
些任务分配给这些人,怎么分才能最合理呢?这时候匈牙利算法就闪
亮登场啦!
第一步,咱得弄个表格出来,把任务和人之间的关系都给标上。
比
如说,这个人干这个任务合适不合适呀,合适就标个高分,不合适就
标个低分。
这就好像给他们牵红线似的,得找到最合适的搭配。
然后呢,开始试着给任务找人。
从第一个任务开始,找个最合适的人。
要是这个人还没被别的任务占着,那太好了,直接就配对成功啦!要是已经被占了呢,那就得看看能不能换一换,让大家都更合适。
就好比是跳舞,你得找到最合适的舞伴,跳起来才带劲嘛!要是随
便找个人就跳,那多别扭呀。
这中间可能会遇到一些麻烦,比如好几个人都对同一个任务感兴趣,那可咋办?这就得好好琢磨琢磨啦,得权衡一下,谁更合适。
有时候你会发现,哎呀,怎么这么难呀,怎么都找不到最合适的搭配。
别急别急,慢慢来,就像解一道难题一样,得一点点分析。
咱再说说这算法的奇妙之处。
它就像是一个聪明的红娘,能把最合适的任务和人牵到一起。
你想啊,要是没有它,那咱不得乱点鸳鸯谱呀,那可不行,得把资源都好好利用起来才行呢。
比如说,有五个任务,五个。
匈⽛利算法0 - 相关概念0.1 - 匈⽛利算法 匈⽛利算法是由匈⽛利数学家Edmonds于1965年提出,因⽽得名。
匈⽛利算法是基于Hall定理中充分性证明的思想,它是⼆部图匹配最常见的算法,该算法的核⼼就是寻找增⼴路径,它是⼀种⽤增⼴路径求⼆分图最⼤匹配的算法。
0.2 - ⼆分图 若图G的结点集合V(G)可以分成两个⾮空⼦集V1和V2,并且图G的任意边xy关联的两个结点x和y分别属于这两个⼦集,则G是⼆分图。
1 - 基本思想1. 找到当前结点a可以匹配的对象A,若该对象A已被匹配,则转⼊第3步,否则转⼊第2步2. 将该对象A的匹配对象记为当前对象a,转⼊第6步3. 寻找该对象A已经匹配的对象b,寻求其b是否可以匹配另外的对象B,如果可以,转⼊第4步,否则,转⼊第5步4. 将匹配对象b更新为另⼀个对象B,将对象A的匹配对象更新为a,转⼊第6步5. 结点a寻求下⼀个可以匹配的对象,如果存在,则转⼊第1步,否则说明当前结点a没有可以匹配的对象,转⼊第6步6. 转⼊下⼀结点再转⼊第1步2 - 样例解析 上⾯的基本思想看完肯定⼀头雾⽔(很⼤程度是受限于我的表达能⼒),下⾯通过来就匈⽛利算法做⼀个详细的样例解析。
2.1 - 题⽬⼤意 农场主John有N头奶⽜和M个畜栏,每⼀头奶⽜需要在特定的畜栏才能产奶。
第⼀⾏给出N和M,接下来N⾏每⾏代表对应编号的奶⽜,每⾏的第⼀个数值T表⽰该奶⽜可以在多少个畜栏产奶,⽽后的T个数值为对应畜栏的编号,最后输出⼀⾏,表⽰最多可以让多少头奶⽜产奶。
2.1 - 输⼊样例5522532342153125122.2 - 匈⽛利算法解题思路2.2.1 - 构造⼆分图 根据输⼊样例构造如下⼆分图,蓝⾊结点表⽰奶⽜,黄⾊结点表⽰畜栏,连线表⽰对应奶⽜能在对应畜栏产奶。
2.2.2 - 模拟算法流程为结点1(奶⽜)分配畜栏,分配畜栏2(如图(a)加粗红边所⽰)为结点2(奶⽜)分配畜栏,由于畜栏2已经被分配给结点1(奶⽜),所以寻求结点1(奶⽜)是否能够分配别的畜栏,以把畜栏2腾给结点2(奶⽜)。
数学建模匈牙利算法
【最新版】
目录
一、匈牙利算法的概念与基本原理
二、匈牙利算法的应用实例
三、匈牙利算法的优缺点
正文
一、匈牙利算法的概念与基本原理
匈牙利算法(Hungarian algorithm)是一种求解二分图最大匹配问题的算法,由匈牙利数学家 Mátyásovszky 于 1937 年首次提出。
该算法的基本思想是:通过不断循环寻找图中的偶数长度路径,并将路径中的顶点依次匹配,直到找不到这样的路径为止。
此时,图中的所有顶点都已匹配,即得到了二分图的最大匹配。
二、匈牙利算法的应用实例
匈牙利算法广泛应用于任务分配、资源调度、数据融合等领域。
下面举一个简单的例子来说明匈牙利算法的应用。
假设有 5 个工人和 8 个任务,每个工人完成不同任务的效率不同。
我们需要为每个任务分配一个工人,使得总效率最大。
可以用一个二分图来表示这个问题,其中顶点分为两类:工人和任务。
边表示任务与工人之间的效率关系。
匈牙利算法可以用来求解这个问题,找到最优的任务分配方案。
三、匈牙利算法的优缺点
匈牙利算法的优点是简单、高效,可以解决二分图的最大匹配问题。
然而,它也存在一些缺点:
1.匈牙利算法只能解决无向图的匹配问题,对于有向图,需要将其转
换为无向图才能使用匈牙利算法。
2.当图中存在环时,匈牙利算法无法找到最大匹配。
这时需要使用其他算法,如 Euclidean algorithm(欧几里得算法)来解决。
3.匈牙利算法在实际应用中可能存在数值稳定性问题,即在计算过程中可能出现精度误差。
运筹学课程设计指派问题的匈牙利法专业:姓名:学号:1.算法思想:匈牙利算法的基本思想是修改效益矩阵的行或列,使得每一行或列中至少有一个为零的元素,经过修正后,直至在不同行、不同列中至少有一个零元素,从而得到与这些零元素相对应的一个完全分配方案。
当它用于效益矩阵时,这个完全分配方案就是一个最优分配,它使总的效益为最小。
这种方法总是在有限步內收敛于一个最优解。
该方法的理论基础是:在效益矩阵的任何行或列中,加上或减去一个常数后不会改变最优分配。
2.算法流程或步骤:1.将原始效益矩阵C的每行、每列各元素都依次减去该行、该列的最小元素,使每行、每列都至少出现一个0元素,以构成等价的效益矩阵C’。
2.圈0元素。
在C’中未被直线通过的含0元素最少的行(或列)中圈出一个0元素,通过这个0元素作一条竖(或横)线。
重复此步,若这样能圈出不同行不同列的n个0元素,转第四步,否则转第三步。
3.调整效益矩阵。
在C’中未被直线穿过的数集D中,找出最小的数d,D中所有数都减去d,C’中两条直线相交处的数都加的d。
去掉直线,组成新的等价效益矩阵仍叫C’,返回第二步。
X=0,这就是一种最优分配。
最低总4.令被圈0元素对应位置的X ij=1,其余ij耗费是C中使X=1的各位置上各元素的和。
ij算法流程图:3.算法源程序:#include<iostream.h>typedef struct matrix{float cost[101][101];int zeroelem[101][101];float costforout[101][101];int matrixsize;int personnumber;int jobnumber;}matrix;matrix sb;int result[501][2];void twozero(matrix &sb);void judge(matrix &sb,int result[501][2]);void refresh(matrix &sb);void circlezero(matrix &sb);matrix input();void output(int result[501][2],matrix sb);void zeroout(matrix &sb);matrix input(){matrix sb;int m;int pnumber,jnumber;int i,j;float k;char w;cout<<"指派问题的匈牙利解法:"<<endl;cout<<"求最大值,请输入1;求最小值,请输入0:"<<endl;cin>>m;while(m!=1&&m!=0){cout<<"请输入1或0:"<<endl;cin>>m;}cout<<"请输入人数(人数介于1和100之间):"<<endl;cin>>pnumber;while(pnumber<1||pnumber>100){cout<<"请输入合法数据:"<<endl;cin>>pnumber;}cout<<"请输入工作数(介于1和100之间):"<<endl;cin>>jnumber;while(jnumber<1||jnumber>100){cout<<"请输入合法数据:"<<endl;cin>>jnumber;}cout<<"请输入"<<pnumber<<"行"<<jnumber<<"列的矩阵,同一行内以空格间隔,不同行间以回车分隔,以$结束输入:\n";for(i=1;i<=pnumber;i++)for(j=1;j<=jnumber;j++){cin>>sb.cost[i][j];sb.costforout[i][j]=sb.cost[i][j];}cin>>w;if(jnumber>pnumber)for(i=pnumber+1;i<=jnumber;i++)for(j=1;j<=jnumber;j++){sb.cost[i][j]=0;sb.costforout[i][j]=0;}else{if(pnumber>jnumber)for(i=1;i<=pnumber;i++)for(j=jnumber+1;j<=pnumber;j++){sb.cost[i][j]=0;sb.costforout[i][j]=0;}}sb.matrixsize=pnumber;if(pnumber<jnumber)sb.matrixsize=jnumber;sb.personnumber=pnumber;sb.jobnumber=jnumber;if(m==1){k=0;for(i=1;i<=sb.matrixsize;i++)for(j=1;j<=sb.matrixsize;j++)if(sb.cost[i][j]>k)k=sb.cost[i][j];for(i=1;i<=sb.matrixsize;i++)for(j=1;j<=sb.matrixsize;j++)sb.cost[i][j]=k-sb.cost[i][j];}return sb;}void circlezero(matrix &sb){int i,j;float k;int p;for(i=0;i<=sb.matrixsize;i++)sb.cost[i][0]=0;for(j=1;j<=sb.matrixsize;j++)sb.cost[0][j]=0;for(i=1;i<=sb.matrixsize;i++)for(j=1;j<=sb.matrixsize;j++)if(sb.cost[i][j]==0){sb.cost[i][0]++;sb.cost[0][j]++;sb.cost[0][0]++;}for(i=0;i<=sb.matrixsize;i++)for(j=0;j<=sb.matrixsize;j++)sb.zeroelem[i][j]=0;k=sb.cost[0][0]+1;while(sb.cost[0][0]<k){k=sb.cost[0][0];for(i=1;i<=sb.matrixsize;i++){if(sb.cost[i][0]==1){for(j=1;j<=sb.matrixsize;j++)if(sb.cost[i][j]==0&&sb.zeroelem[i][j]==0)break;sb.zeroelem[i][j]=1;sb.cost[i][0]--;sb.cost[0][j]--;sb.cost[0][0]--;if(sb.cost[0][j]>0)for(p=1;p<=sb.matrixsize;p++)if(sb.cost[p][j]==0&&sb.zeroelem[p][j]==0){sb.zeroelem[p][j]=2;sb.cost[p][0]--;sb.cost[0][j]--;sb.cost[0][0]--;}}}for(j=1;j<=sb.matrixsize;j++){if(sb.cost[0][j]==1){for(i=1;i<=sb.matrixsize;i++)if(sb.cost[i][j]==0&&sb.zeroelem[i][j]==0)break;sb.zeroelem[i][j]=1;sb.cost[i][0]--;sb.cost[0][j]--;sb.cost[0][0]--;if(sb.cost[i][0]>0)for(p=1;p<=sb.matrixsize;p++)if(sb.cost[i][p]==0&&sb.zeroelem[i][p]==0){sb.zeroelem[i][p]=2;sb.cost[i][0]--;sb.cost[0][p]--;sb.cost[0][0]--;}}}}if(sb.cost[0][0]>0)twozero(sb);elsejudge(sb,result);}void twozero(matrix &sb){int i,j;int p,q;int m,n;float k;matrix st;for(i=1;i<=sb.matrixsize;i++)if(sb.cost[i][0]>0)break;if(i<=sb.matrixsize){for(j=1;j<=sb.matrixsize;j++){st=sb;if(sb.cost[i][j]==0&&sb.zeroelem[i][j]==0){sb.zeroelem[i][j]=1;sb.cost[i][0]--;sb.cost[0][j]--;sb.cost[0][0]--;for(q=1;q<=sb.matrixsize;q++)if(sb.cost[i][q]==0&&sb.zeroelem[i][q]==0){sb.zeroelem[i][q]=2;sb.cost[i][0]--;sb.cost[0][q]--;sb.cost[0][0]--;}for(p=1;p<=sb.matrixsize;p++)if(sb.cost[p][j]==0&&sb.zeroelem[p][j]==0){sb.zeroelem[p][j]=2;sb.cost[p][0]--;sb.cost[0][j]--;sb.cost[0][0]--;}k=sb.cost[0][0]+1;while(sb.cost[0][0]<k){k=sb.cost[0][0];for(p=i+1;p<=sb.matrixsize;p++){if(sb.cost[p][0]==1){for(q=1;q<=sb.matrixsize;q++)if(sb.cost[p][q]==0&&sb.zeroelem[p][q]==0)break;sb.zeroelem[p][q]=1;sb.cost[p][0]--;sb.cost[0][q]--;sb.cost[0][0]--;for(m=1;m<=sb.matrixsize;m++)if(sb.cost[m][q]=0&&sb.zeroelem[m][q]==0){sb.zeroelem[m][q]=2;sb.cost[m][0]--;sb.cost[0][q]--;sb.cost[0][0]--;}}}for(q=1;q<=sb.matrixsize;q++){if(sb.cost[0][q]==1){for(p=1;p<=sb.matrixsize;p++)if(sb.cost[p][q]==0&&sb.zeroelem[p][q]==0)break;sb.zeroelem[p][q]=1;sb.cost[p][q]--;sb.cost[0][q]--;sb.cost[0][0]--;for(n=1;n<=sb.matrixsize;n++)if(sb.cost[p][n]==0&&sb.zeroelem[p][n]==0){sb.zeroelem[p][n]=2;sb.cost[p][0]--;sb.cost[0][n]--;sb.cost[0][0]--;}}}}if(sb.cost[0][0]>0)twozero(sb);elsejudge(sb,result);}sb=st;}}}void judge(matrix &sb,int result[501][2]){int i,j;int m;int n;int k;m=0;for(i=1;i<=sb.matrixsize;i++)for(j=1;j<=sb.matrixsize;j++)if(sb.zeroelem[i][j]==1)m++;if(m==sb.matrixsize){k=1;for(n=1;n<=result[0][0];n++){for(i=1;i<=sb.matrixsize;i++){for(j=1;j<=sb.matrixsize;j++)if(sb.zeroelem[i][j]==1)break;if(i<=sb.personnumber&&j<=sb.jobnumber)if(j!=result[k][1])break;k++;}if(i==sb.matrixsize+1)break;elsek=n*sb.matrixsize+1;}if(n>result[0][0]){k=result[0][0]*sb.matrixsize+1;for(i=1;i<=sb.matrixsize;i++)for(j=1;j<=sb.matrixsize;j++)if(sb.zeroelem[i][j]==1){result[k][0]=i;result[k++][1]=j;}result[0][0]++;}}else{refresh(sb);}}void refresh(matrix &sb){int i,j;float k;int p;k=0;for(i=1;i<=sb.matrixsize;i++){for(j=1;j<=sb.matrixsize;j++)if(sb.zeroelem[i][j]==1){sb.zeroelem[i][0]=1;break;}}while(k==0){k=1;for(i=1;i<=sb.matrixsize;i++)if(sb.zeroelem[i][0]==0){sb.zeroelem[i][0]=2;for(j=1;j<=sb.matrixsize;j++)if(sb.zeroelem[i][j]==2){sb.zeroelem[0][j]=1;}}for(j=1;j<=sb.matrixsize;j++){if(sb.zeroelem[0][j]==1){sb.zeroelem[0][j]=2;for(i=1;i<=sb.matrixsize;i++)if(sb.zeroelem[i][j]==1){sb.zeroelem[i][0]=0;k=0;}}}}p=0;k=0;for(i=1;i<=sb.matrixsize;i++){if(sb.zeroelem[i][0]==2){for(j=1;j<=sb.matrixsize;j++){if(sb.zeroelem[0][j]!=2)if(p==0){k=sb.cost[i][j];p=1;}else{if(sb.cost[i][j]<k)k=sb.cost[i][j];}}}}for(i=1;i<=sb.matrixsize;i++){if(sb.zeroelem[i][0]==2)for(j=1;j<=sb.matrixsize;j++)sb.cost[i][j]=sb.cost[i][j]-k;}for(j=1;j<=sb.matrixsize;j++){if(sb.zeroelem[0][j]==2)for(i=1;i<=sb.matrixsize;i++)sb.cost[i][j]=sb.cost[i][j]+k;}for(i=0;i<=sb.matrixsize;i++)for(j=0;j<=sb.matrixsize;j++)sb.zeroelem[i][j]=0;circlezero(sb);}void zeroout(matrix &sb){int i,j;float k;for(i=1;i<=sb.matrixsize;i++){k=sb.cost[i][1];for(j=2;j<=sb.matrixsize;j++)if(sb.cost[i][j]<k)k=sb.cost[i][j];for(j=1;j<=sb.matrixsize;j++)sb.cost[i][j]=sb.cost[i][j]-k;}for(j=1;j<=sb.matrixsize;j++){k=sb.cost[1][j];for(i=2;i<=sb.matrixsize;i++)if(sb.cost[i][j]<k)k=sb.cost[i][j];for(i=1;i<=sb.matrixsize;i++)sb.cost[i][j]=sb.cost[i][j]-k;}}void output(int result[501][2],matrix sb) {int k;int i;int j;int p;char w;float v;v=0;for(i=1;i<=sb.matrixsize;i++){v=v+sb.costforout[i][result[i][1]];}cout<<"最优解的目标函数值为"<<v;k=result[0][0];if(k>5){cout<<"解的个数超过了限制."<<endl;k=5;}for(i=1;i<=k;i++){cout<<"输入任意字符后输出第"<<i<<"种解."<<endl;cin>>w;p=(i-1)*sb.matrixsize+1;for(j=p;j<p+sb.matrixsize;j++)if(result[j][0]<=sb.personnumber&&result[j][1]<=sb.jobnumber)cout<<"第"<<result[j][0]<<"个人做第"<<result[j][1]<<"件工作."<<endl;}}void main(){result[0][0]=0;sb=input();zeroout(sb);circlezero(sb);output(result,sb);}4. 算例和结果:自己运算结果为:->⎥⎥⎥⎥⎦⎤⎢⎢⎢⎢⎣⎡3302102512010321->⎥⎥⎥⎥⎦⎤⎢⎢⎢⎢⎣⎡330110241200032034526635546967562543----⎥⎥⎥⎥⎦⎤⎢⎢⎢⎢⎣⎡可以看出:第1人做第4件工作;第2人做第1件工作;第3人做第3件工作;第4人做第2件工作。
匈牙利算法示例范文匈牙利算法是一种解决二分图最大匹配问题的经典算法,也称为增广路径算法。
它的核心思想是通过不断寻找增广路径来不断增加匹配数,直到无法找到新的增广路径为止。
下面我将通过一个示例来详细介绍匈牙利算法的流程和原理。
假设有一个二分图G=(V,E),其中V={U,V}是图的两个顶点集合,E 是边集合。
我们的目标是找到一个最大的匹配M,即图中不存在更多的边可以加入到匹配中。
首先,我们需要为每个顶点u∈U找到一个能和它相连的顶点v∈V,这个过程称为初始化匹配。
我们可以将所有的顶点u初始化为null,表示还没有和它相连的顶点v。
然后,我们选择一个u∈U的顶点作为起始点,尝试寻找一条增广路径。
增广路径是指一条交替经过未匹配边和已匹配边的路径。
我们从起始点u开始,按照深度优先的方式扩展路径,不断寻找下一个可以加入路径的顶点v。
1. 如果u没有和任何顶点相连,那么说明找到了一条增广路径。
我们将路径上的未匹配边变为已匹配边,已匹配边变为未匹配边,然后返回true,表示找到了一条增广路径。
2. 如果u有和一些顶点v相连,但是v还没有和其他顶点相连,即v的匹配点为null,那么说明找到了一条增广路径。
我们将路径上的未匹配边变为已匹配边,已匹配边变为未匹配边,并将v设置为u的匹配点。
然后返回true,表示找到了一条增广路径。
3. 如果v已经和其他顶点相连,那么我们尝试寻找一条从v的匹配点u'出发的增广路径。
如果找到了一条增广路径,我们将路径上的未匹配边变为已匹配边,已匹配边变为未匹配边,并将v设置为u'的匹配点。
然后返回true,表示找到了一条增广路径。
4. 如果无法找到可行的增广路径,返回false。
通过不断重复上述过程,直到无法找到新的增广路径为止。
此时,我们得到的匹配M就是最大匹配。
下面我们通过一个具体的例子来演示匈牙利算法的运行过程。
假设我们有一个二分图,U={1,2,3},V={4,5,6},边集E={(1,4),(1,5),(2,4),(3,5),(3,6)}。
匈牙利法解决人数与任务数不等的指派问题于凯重庆科技学院经济管理学院物流专业重庆沙坪坝区摘要:本文将讨论运筹学中的指派问题,而且属于非标准指派问题,即人数与任务数不相等的指派问题,应当视为一个多目标决策问题,首先要求指派给个人任务数目两两之间相差不能超过1,其次要求所需总时间最少,并且给出了该类问题的求解方法。
关键词:运筹学指派问题匈牙利算法系数矩阵解矩阵引言:在日常的生产生活中常遇到这样的问题:有n项任务,有n个人员可以去承担这n 项任务,但由于每位人员的特点与专长不同,各对象完成各项任务所用的时间费用或效益不同;有因任务性质要求和管理上需要等原因,每项任务只能由一个人员承担来完成,这就涉及到应该指派哪个人员去完成哪项任务,才能使完成n项任务花费总时间最短,总费用最少,产生的总效益最佳。
我们把这类最优匹配问题称为指派问题或分配问题。
1.指派问题的解法——匈牙利法早在1955年库恩(,该方法是以匈牙利数学家康尼格(koning)提出的一个关于矩阵中0元素的定理为基础,因此得名匈牙利法(The Hungonrian Method of Assignment)1.1匈牙利解法的基本原理和解题思路直观的讲,求指派问题的最优方案就是要在n阶系数矩阵中找出n个分布于不用行不同列的元素使得他们的和最小。
而指派问题的最优解又有这样的性质:若从系数矩阵C(ij)的一行(列)各元素都减去该行(列)的最小元素,得到新矩阵CB(ij),那么以CB(ij)为系数矩阵求得的最优解和原系数矩阵C(ij)求得的最优解相同。
由于经过初等变换得到的新矩阵CB(ij)中每行(列)的最小元素均为“○”,因此求原指派问题C(ij)的最优方案就等于在新矩阵CB(ij)中找出n个分布于不同行不同列的“○”元素(简称为“独立○元素”),这些独立○元素就是CB(ij)的最优解,同时与其对应的原系数矩阵的最优解。
1.2匈牙利法的具体步骤第一步:使指派问题的系数矩阵经过变换在各行各列中都出现○元素。
匈牙利算法代码解析匈牙利算法又称作增广路算法,主要用于解决二分图最大匹配问题。
它的基本思想是在二分图中查找增广路,然后将这条增广路上的边反转,这样可以将匹配数增加一个,由此不断寻找增广路并反转边直到无法找到为止,最后所找到的就是二分图的最大匹配。
匈牙利算法的流程如下:1. 从左边开始选择一个未匹配的节点,将其标记为当前节点;2. 再从当前节点出发,依次寻找与它相连的未匹配节点;3. 如果找到了一个未匹配节点,则记录该节点的位置,并将当前节点标记为该节点;4. 如果当前节点的所有连边都不能找到未匹配节点,则退回到上一个节点,再往其他的连接点继续搜索;5. 如果到达已经匹配节点,则将该节点标记为新的当前节点,返回步骤4;6. 如果找到了一条增广路,则将其上的边反转,并将匹配数+1;7. 重复以上步骤,直至无法找到增广路为止。
在匈牙利算法中,增广路的查找可以使用DFS或BFS,这里我们以DFS为例进行解释。
匈牙利算法的时间复杂度为O(nm),n和m分别表示左边和右边的节点数,因为每条边至多遍历两次,所以最多需要执行2n次DFS。
以下为匈牙利算法的Python代码:```Pythondef findPath(graph, u, match, visited):for v in range(len(graph)):if graph[u][v] and not visited[v]:visited[v] = Trueif match[v] == -1 or findPath(graph, match[v], match, visited):# 如果v没有匹配或者匹配的右匹配节点能找到新的匹配match[v] = u # 更新匹配return Truereturn Falsedef maxMatching(graph):n = len(graph)match = [-1] * n # 右部节点的匹配数组,初始化为-1表示没有匹配count = 0return match, count```其中,findPath函数是用来查找增广路的DFS函数,match数组是右边节点的匹配数组,初始化为-1表示没有匹配,count则表示匹配数。
匈牙利法解决人数与任务数不等的指派问题于凯重庆科技学院经济管理学院物流专业重庆沙坪坝区摘要:本文将讨论运筹学中的指派问题,而且属于非标准指派问题,即人数与任务数不相等的指派问题,应当视为一个多目标决策问题,首先要求指派给个人任务数目两两之间相差不能超过1,其次要求所需总时间最少,并且给出了该类问题的求解方法。
关键词:运筹学指派问题匈牙利算法系数矩阵解矩阵引言:在日常的生产生活中常遇到这样的问题:有n项任务,有n个人员可以去承担这n 项任务,但由于每位人员的特点与专长不同,各对象完成各项任务所用的时间费用或效益不同;有因任务性质要求和管理上需要等原因,每项任务只能由一个人员承担来完成,这就涉及到应该指派哪个人员去完成哪项任务,才能使完成n项任务花费总时间最短,总费用最少,产生的总效益最佳。
我们把这类最优匹配问题称为指派问题或分配问题。
1.指派问题的解法——匈牙利法早在1955年库恩(w.w.ku.hn)就提出了指派问题的解法,该方法是以匈牙利数学家康尼格(koning)提出的一个关于矩阵中0元素的定理为基础,因此得名匈牙利法(The Hungonrian Method of Assignment)1.1匈牙利解法的基本原理和解题思路直观的讲,求指派问题的最优方案就是要在n阶系数矩阵中找出n个分布于不用行不同列的元素使得他们的和最小。
而指派问题的最优解又有这样的性质:若从系数矩阵C(ij)的一行(列)各元素都减去该行(列)的最小元素,得到新矩阵CB(ij),那么以CB(ij)为系数矩阵求得的最优解和原系数矩阵C(ij)求得的最优解相同。
由于经过初等变换得到的新矩阵CB(ij)中每行(列)的最小元素均为“○”,因此求原指派问题C(ij)的最优方案就等于在新矩阵CB(ij)中找出n个分布于不同行不同列的“○”元素(简称为“独立○元素”),这些独立○元素就是CB(ij)的最优解,同时与其对应的原系数矩阵的最优解。
匈⽛利算法(⼆分图)---------------------------------------------------------------------题材⼤多来⾃⽹络,本篇由神犇整理基本概念—⼆分图⼆分图:是图论中的⼀种特殊模型。
若能将⽆向图G=(V,E)的顶点V划分为两个交集为空的顶点集,并且任意边的两个端点都分属于两个集合,则称图G为⼀个为⼆分图。
匹配:⼀个匹配即⼀个包含若⼲条边的集合,且其中任意两条边没有公共端点。
如下图,图3的红边即为图2的⼀个匹配。
1 最⼤匹配在G的⼀个⼦图M中,M的边集中的任意两条边都不依附于同⼀个顶点,则称M是⼀个匹配。
选择这样的边数最⼤的⼦集称为图的最⼤匹配问题,最⼤匹配的边数称为最⼤匹配数.如果⼀个匹配中,图中的每个顶点都和图中某条边相关联,则称此匹配为完全匹配,也称作完备匹配。
如果在左右两边加上源汇点后,图G等价于⼀个⽹络流,最⼤匹配问题可以转为最⼤流的问题。
解决此问的匈⽛利算法的本质就是寻找最⼤流的增⼴路径。
上图中的最⼤匹配如下图红边所⽰:2 最优匹配最优匹配⼜称为带权最⼤匹配,是指在带有权值边的⼆分图中,求⼀个匹配使得匹配边上的权值和最⼤。
⼀般X和Y集合顶点个数相同,最优匹配也是⼀个完备匹配,即每个顶点都被匹配。
如果个数不相等,可以通过补点加0边实现转化。
⼀般使⽤KM算法解决该问题。
3 最⼩覆盖⼆分图的最⼩覆盖分为最⼩顶点覆盖和最⼩路径覆盖:①最⼩顶点覆盖是指最少的顶点数使得⼆分图G中的每条边都⾄少与其中⼀个点相关联,⼆分图的最⼩顶点覆盖数=⼆分图的最⼤匹配数;②最⼩路径覆盖也称为最⼩边覆盖,是指⽤尽量少的不相交简单路径覆盖⼆分图中的所有顶点。
⼆分图的最⼩路径覆盖数=|V|-⼆分图的最⼤匹配数;4 最⼤独⽴集最⼤独⽴集是指寻找⼀个点集,使得其中任意两点在图中⽆对应边。
对于⼀般图来说,最⼤独⽴集是⼀个NP完全问题,对于⼆分图来说最⼤独⽴集=|V|-⼆分图的最⼤匹配数。
浅析指派问题的匈牙利解法胡小芹数学科学学院数学与应用数学学号:040414057指导教师:苏孟龙摘要:对于指派问题,可以利用许多理论进行建模并加以解决,但匈牙利解法是解决指派问题的一种非常简单有效的方法,并且可以解决多种形式的指派问题,但匈牙利算法本身存在着一些问题,本文主要介绍了匈牙利算法的基本思想,基本步骤,以及它的改进方法.在匈牙利算法的基础上,本文还介绍了两种更简便实用的寻找独立零元素的方法——最小零元素消耗法和对角线法.关键词:指派问题;匈牙利解法;最小零元素消耗法;对角线法0 引言在现实生活中经常会遇到把几个任务分派给几个不同的对象去完成,由于每个对象的条件不同,完成任务的效率和效益亦不同.指派问题的目标就是如何分派使所消耗的总资源最少(或总效益最优),如给工人分派工作,给车辆分配道路,给工人分配机床等等,同时许多网络问题(如旅行问题,任务分配问题,运输问题等),都可以演化成指派问题来解决.在现实生活中,指派问题是十分常见的问题,而匈牙利解法是解决指派问题的一种非常简单有效的方法.本文主要介绍匈牙利解法的基本原理及思想,解题步骤,不足与改进,以使匈牙利法更能有效地解决指派问题.1 指派问题及其数学模型指派问题是指由m项任务,需要n个人来承担,每人只能承担一项任务,且每项任务只能有一人来承担,由于各人的专长不同,各人完成的任务不同,导致其效率也各不相同.因此,就产生怎样科学地指派任务,才能使完成各项任务所消耗的总资源最少(或总成本最低等),由于n m ,不同,指派问题可分为以下三种情况:第一、当n m =时,即为每人指派一项任务.第二、当n m >时,即任务数〉人数,这时可虚设)(n m -个人构成m m ⨯的 效率矩阵,并且这)(n m -个人在执行这m 项任务时的效率应该是效率最高. 第三、当n m <时,即配置人数〉任务数,这时应虚设)(m n -项任务,并且这n 个人在执行这)(m n -项任务时的成本最低.通过虚设任务或人,指派问题的效率矩阵都可以转化成方阵.匈牙利解法要求指派问题最小化,其数学模型为设用0ij c >(,1,2,,)i j n =表示指派第i 个人去完成第j 项任务时所用的时间,定义决策变量 10ij i j x i j ⎧=⎨⎩表示第个人完成第项任务, 表示不指派第个人完成第项任务.则问题可转化为0-1线性规划问题:∑∑===n j ij n i c Z 11mint s ⋅ 111,1,2,,,1,1,2,,,01,i,j 1,2,,n nij i nij j ijx j n x i n x ==⎧==⎪⎪⎪==⎨⎪⎪==⎪⎩∑∑或.如果指派问题要求的是最大化问题如F max ,则可以转化为最小化问题,一般方法是:取max (,1,2),ij M c i j n ==令(,1,2,)ij ij b M c i j n =-=,则11min ,n n ij i j f b ===∑∑,max F nM f F =-有从而求. 2 指派问题的解法——匈牙利解法“匈牙利解法”最早是由匈牙利数学家 D.Konig 用来求矩阵中0元素的一种方法,由此他证明了“矩阵中独立0元素的最大个数等于能覆盖所有0元素的最少直线数”.1955年由W.W.Knhu 在求解著名的指派问题时引用了这一结论,并对具体解法做了改进,仍称为匈牙利解法.2.1 匈牙利解法的基本原理和解题思想从根本上讲,求指派问题的最优解就是要在n 阶方阵中找到n 个这样的元素,它们分布在不同行不同列上,并且这些元素之和为最小,而要使这些元素之和为最小,就要使其中的每一个元素尽可能的小——最好这些元素都是其所在行所在列上的最小元素.而指派问题的最优解又具有这样的性质(定理1):若从系数矩阵)(ij c 的每行(列)各元素中分别减去该行(列)的最小元素,得到的新矩阵()ij b ,那么以()ij b 为系数矩阵求得的最优解与用()ij c 求得最优解相同.由于新矩阵()ij b 中每行每列都有最小元“0”,因此,求原指派问题的最优解转化为在()ij b 中指出n 个分布在不同行不同列上的“0”元素(简称为独立0元素),而根据考尼格(Konig )证明的定理(定理2):矩阵中独立元素的0最多个数等于能覆盖所有零元素的最少直线数,利用这一定理,就可以通过寻找“能覆盖所有零元素的最少直线数”来确定()ij b 中独立元0素的具体数量.若()ij b 中独立0元素的个数少于矩阵的阶数n ,就要继续对矩阵()ij b 进行化简,直到找到n 个独立0元素为止,这就是匈牙利解法的基本原理和解题思想.2.2 匈牙利解法的解题步骤第一步:变换效益矩阵,使新矩阵中的每行每列至少有一个0.(1)行变换:找出每行最小元素,再从该行各元素中减去这个最小元素.(2)列变换:从所得新矩阵中找出每列中的最小元素,再从该列各元素中减去这个最小元素.第二步:进行试指派,以寻找最优解.(1)逐行检查从第一行开始,如果该行只有一个零元素,就对这个零元素打上括号,划去与打括号零元素同在一列的其他零元素,如果该行没有零元素,或有两个或多个零元素(已划去的不计在内),则转下行.打括号的意义可理解为该项任务已分配给某人.如果该行只有一个0,说明只能有唯一分配.划掉同列括号零元素可理解为该任务已分配,此后不再考虑分配给他人.当该行有两个或更多的零元素时,不计括号内的,其理由是至少有两个分配方案,为使以后分配时具有一定的灵活性,故暂不分配.(2)逐列检查在行检查的基础上,由第一列开始检查,如果该列只有一个零元素,就对这个零元素打括号,再划去与打括号零元素同行的其它零元素.若该列没有零元素或有两个以上零元素,则转下一列.(3)重复(1)、(2)两步后,可能出现以下三种情况:①每行都有一个零元素标出括号,显然打括号的零元素必然位于不同行不同列,因此得到最优解.②每行每列都有两个或两个以上的零,这表示对这个人可以分配不同任务中的任意一个.这时可以从所剩零元素最少的行开始,比较这行各零元素所在列中元素的个数,选择零元素少的那列这个零元素打括号,划掉同行同列的其他它零元素,然后重复前面的步骤,直到所有0都作了标记.③矩阵中所有零都作了标记,但标有(0)的元素个数少于m个,则转下步.第三步:做最少的直线覆盖所有零元素,以确定该系数矩阵中能找到最多的独立0元素.·对没有()的行打√;·对打√的行上所有含0元素的列打√;·再对打√的列上有()的行打√;·重复上两步,直到过程结束;·对没有打√的行划横线,对所有打√的列划垂线.这就得到了能覆盖矩阵中所有0元素的最少直线数.第四步:非最优阵的变换——零元素的移动.(1)在没有被直线覆盖的所有元素中,找出最小元素;(2)所有未被直线覆盖的元素都减去这个最小元素;(3)覆盖线十字交叉处的元素都加上这个最小元素;(4)只有一条直线覆盖的元素的值保持不变.第五步:回到第二步,反复进行,直到矩阵中每行每列都有一个打()的0元素为止,即找到最优解.2.3 匈牙利算法的一点注记(1)匈牙利算法第一步变换效益矩阵使每行每列都出现零元素,一般方法是先行变换,再列变换,但先列变换再行变换最优解不变.(2)有些时候先行后列变换后不能得到最优解,但先列后行变换时却能直接找到最优解,从而减少了计算步骤,使计算简明.如下所示:先行变换,再列变换得2109715414813141611415139B ⎡⎤⎢⎥⎢⎥=→⎢⎥⎢⎥⎣⎦(0)82511(0)5423(0)001145B ⎡⎤⎢⎥⎢⎥=⎢⎥⎢⎥⎣⎦只找到三个独立0元素,不是最优解.那么先列变换,后行变换得2109715414813141611415139B ⎡⎤⎢⎥⎢⎥=→⎢⎥⎢⎥⎣⎦06(0)013(0)51763(0)(0)920B ⎡⎤⎢⎥⎢⎥=⎢⎥⎢⎥⎣⎦ 每行每列都有“(0)”,得到最优解.3 匈牙利法的不足与改进3.1 不足和改进(一)以上是匈牙利法的常规解题方法,虽然能有效地解决指派问题,但是其解题过程中还存在以下两点不足:第一、在匈牙利算法的第二步中用“试指派”的方法寻找独立0元素,道理简单,也能很快找到独立0元素,但对于一个n 阶方阵,这些独立0元素在矩阵中可能有n 种排列方式,这样在矩阵中标出来的独立0元素排列很乱,没有规律.另外,对于某些矩阵0元素排列的比较有规律,本可以用别的更简单的方法寻找独立0元素,而用这种“一般性”的方法比较麻烦.第二、在匈牙利算法的第三步“做最少的直线覆盖所有0元素”过程中,一会儿针行,一会儿针对列,一会儿对没有()的打√,一会儿又对有()的打√,一会对没有打√号的(行)划线,一会又对打√号的(列)划线,这样变来变去令人晕头转向,稍不注意就会出错,特别是对于这种画法每一步的意图以及内在原理,大多数人难以理解,具体步骤很难记住,难以掌握,这就影响了这一理论在实际工作中的应用.算法的改进:第一、寻找独立0元素的一种新方法——对角线法.对角线法的原理:根据要在指派问题化简后的系数矩阵(ijb)中找出n个独立0元素,而这些独立0元素又分布在方阵的不同行不同列上,利用这一特点,我们可以对矩阵(ijb)的行(或列)进行某种调换,就一定可以使这些独立0元素按矩阵的对角线排列,这样凡是出现在对角线上的零元素一定是独立0元素,而非对角线上的零元素也一定是非独立0元素,这样我们既能很快找到所有独立0元素,又能直观地发现矩阵中独立0元素的个数.方法:从0元素数量最少的行开始,选择该列第一个0元素,根据该0元素所在行的位置确定该列元素在新矩阵中列的位置(若该0元素所在行是第i行,那么就将该0元素所在列放在新矩阵的第i列),并在原矩阵中划去这一列,并划掉同行其它0元素,再在剩下的矩阵中重新比较各列0元素的数量,并按前面方法进行,直至完成.这样就可以将所有独立0元素集中放在新矩阵的对角线上,当对角线上全为0时,就得到了指派问题的最优解,如果对角线上某一处不为0,说明该矩阵中独立0元素数量不足,这时需要对矩阵作进一步化简.应用示例: 寻找矩阵B中的独立0元素B=5050200304400401⎡⎤⎢⎥⎢⎥⎢⎥⎢⎥⎣⎦→B=5050200304400401⎡⎤⎢⎥⎢⎥⎢⎥⎢⎥⎣⎦→10505003244004010B⎡⎤⎢⎥⎢⎥=⎢⎥⎢⎥⎣⎦(第一列与第四列交换)或25050 2300 0044 0104B⎡⎤⎢⎥⎢⎥=⎢⎥⎢⎥⎣⎦(第二列与第四列交换)第二、最少直线的一种简单画法.画法原理:根据定理“矩阵中独立0元素的最多个数恰好等于能覆盖所有0元素的最少直线数”,也就是说矩阵中有多少个独立0元素,就需要多少条直线将所有0元素覆盖,而从匈牙利法解题步骤看矩阵中独立0元素的数量m 及位置,在划线前是已知,所以覆盖0元素的最少直线数m 也是已知的.由于任意两个独立0元素“(0)”都不同行且不同列,而盖0线也只能按矩阵的行或列来划,因此,一条盖0线只能覆盖一个“(0)”,要覆盖m 个“(0)”就需要m 个直线.基于“盖0线”与独立0元素“(0)”之间数量的一对一关系,只要我们始终围绕着独立0元素“(0)”来画线,首先就能用最少的直线覆盖所有的独立0元素.另外,由于矩阵中的非独立0元素总是和某个独立0元素在同一行或同一列,因此画盖“(0)”线时,只要我们能使每条盖0线都尽可能多的附带非独立0元素,那我们就能实现用最少的直线覆盖所有0元线.具体画法:(1)标记——对“(0)”所在行和列上的0元素数量在行列旁予以标注,对没有“(0)”的行与列的打×号予以标记.(2)画线——从有“(0)”的行与列中找出0元素最多的行(或列)画一横(或纵)线覆盖这些0元素.(3)修正——从“(0)”所在行与列上的0元素数量减去已被覆盖的0元素数.(4)重复上述过程,直至覆盖所有0元素.上述方法的关键是紧紧抓住独立0元素其所在行与列上的0元素的多少决定盖0线的画法(即画线的先后和方向,横向还是纵向),使每条线都能覆盖尽可能多的0元素.另外,因某些非独立0元素处在可被两条盖0线同时覆盖的交叉点上,为减少重复性覆盖,每画一条线后都要对各行各列0元素数予以修正(从中减去已被覆盖的0元素数).示例: 试用最少的直线覆盖下列矩阵中的所有0元素5(0)202230(0)0(0)1057298(0)0406365B ⎡⎤⎢⎥⎢⎥⎢⎥=⎢⎥⎢⎥⎢⎥⎣⎦→ 5(0)2022230(0)03(0)10572198(0)04206365B ⎡⎤⎢⎥⎢⎥⎢⎥=⎢⎥⎢⎥⎢⎥⨯⎣⎦2 1 23 ×5(0)2022230(0)00(0)10572198(0)04206365B ⎡⎤⎢⎥⎢⎥⎢⎥=⎢⎥⎢⎥⎢⎥⨯⎣⎦→ 5(0)2020230(0)00(0)10572198(0)04206365B ⎡⎤⎢⎥⎢⎥⎢⎥=⎢⎥⎢⎥⎢⎥⨯⎣⎦→ 2 1 1 × × 2 × 1 × ×5(0)2020230(0)00(0)10572198(0)04006365B ⎡⎤⎢⎥⎢⎥⎢⎥=⎢⎥⎢⎥⎢⎥⨯⎣⎦ 2 × × × ×注意:当矩阵中的行和列旁标的0元素的数量相等时,我们从中任意选择一个画线,但是当此行(或列)中的0元素所对列(或行)中无“(0)”时,只能先划去此行(或列).比如上面的矩阵B 中,第二行和第四列中0元素相等,都是最多(3个),若先划去第四列,则第二行中第五列中的0元素用这种方法无法划去,若要划去这个0元素,则直线增多.3.2 不足和改进(二)用于解决指派问题的匈牙利解法,在运算过程中存在着两个问题:第一、算法不完善.从未标零元素最少的行取定0元素,不能保证0元素所在的列未标零元素最少.因此,系数矩阵已存在最大匹配,但无法取得最大匹配,而算法中途停止.第二、计算步骤烦琐.当0元素不够又没有未标零元素时,作直线覆盖,移动零元素,然后再取定0元素,反复进行.一 改进算法1 完善匈牙利算法选取零元素时,若被划行未标零元素多于一个,则要保证所标的零元素所在的列未标零元素最少.利用此方法可以解决问题一.2 改进算法步骤(1)利用匈牙利算法,由系数矩阵()ij c 得到各行各列都有零元素的等效矩阵()ij b .(2)若有未划零元素,则选有未划零元素最多的行或列作直线覆盖,直到没有未划零元素为止.(3)若直线数目小于n ,则在未划元素中选取最小元素,未划的各元素减去最小元素,直线交叉处的元素均加最小元素,转2.否则转4.(4)选取有未标零元素最少的行(或列)取未标零元素,记(0),同行同列的未标零元素记 φ;若该行(或列)未标零元素多于一个,则选列(或行)中未标零元素最少的本行未标零元素为(0),直到(0)元素个数等于n 为止.(5)结束算法,得到最大匹配.令对应于(0)的1ij x =,其余的0ij x =,并计算目标函数值.二 定义及定理定义1 作直线覆盖时,被直线覆盖的元素,称为已划元素.未被直线覆盖的元素,称为未划元素.定义2 求最大匹配时,已作记号(0)和φ的零元素称为已标元素,未作记号的零元素称为未标零元素.定理 矩阵()ij b 中已存在最大匹配的充要条件是直线覆盖其中全部零元素的最少数目为n .证明 必要性 若()ij b 中有最大匹配,则()ij b 中存在n 个不同行不同列的零元素,故至少n 条直线才能覆盖全部元素.充分性 若覆盖全部零元素的最少直线数目为n ,则()ij b 中有n 个不同行不同列的零元素.否则可以用少于n 条的直线能覆盖全部元素.此算法的优点是,先求具有最大匹配的等效矩阵,再求最大匹配.求解过程中可以减少迭代次数,降低计算量,并且对某些用匈牙利算法无法得到最大匹配的矩阵利用此改进的算法可以得到最大匹配,例如效益矩阵010()002003ij b ⎡⎤⎢⎥=⎢⎥⎢⎥⎣⎦. 3.3 不足和改进(三)3.3.1 匈牙利法存在的问题我们用“匈牙利法”处理了许多与分配问题相关的运输、调度问题,大多数情况下算法是收敛的,得到了最优解,而处理一些特殊数据时算法不收敛,无法找出最优解,矩阵阶数越大的分配问题,不收敛的情况越多.试验中,发现了一个较小阶不收敛的系数矩阵,下面用“匈牙利法”对该矩阵进行分析2 2 4 1 4 4 4 4 12 2 1 1 23 3 3 12 3 1 4 4 2 4 3 42 1 1 4 13 1 1 23 1 1 1 2 2 1 3 11 3 1 42 23 1 33 4 2 1 1 1 3 3 13 24 1 3 1 4 1 21 4 12 1 43 1 4经过算法第一步,得到修正的系数矩阵1 1 3 0 3 3 3 3 01 1 0 0 12 2 2 01 2 0 3 3 1 3 2 31 0 0 3 02 0 0 12 0 0 0 1 1 0 2 00 2 0 3 1 1 2 0 22 3 1 0 0 0 2 2 02 13 0 2 0 3 0 10 3 0 1 0 3 2 0 3进行算法第二步,做分配方案,最终第2行无(0)标记,使得第2行第2列没有分配1 1 3 (0) 3 3 3 3 ×1 1 ×× 12 2 2 ×1 2 (0) 3 3 1 3 2 31 ×× 3 (0)2 ×× 12 ××× 1 1 (0) 2 ×(0) 2 × 3 1 1 2 × 22 3 1 ××× 2 2 (0)2 13 × 2 × 3 × 1× 3 × 1 × 3 2 (0) 3进行算法第三步,最终所有的行和所有的列都有∨标记,所有的行末画一条横线,所有的列都画上了竖线,各行列作标记的顺序如下∨∨∨∨∨∨∨∨∨⒄⑿⑵⑶⑻⑼⒀⒁⑷1 1 3 (0) 3 3 3 3 ×⑹∨1 1 ×× 12 2 2 ×⑴∨1 2 (0) 3 3 1 3 2 3 ⑸∨1 ×× 3 (0)2 ×× 1 ⑽∨2 ××× 1 1 (0) 2 ×⒂∨(0) 2 × 3 1 1 2 × 2 ⒅∨2 3 1 ××× 2 2 (0) ⑺∨2 13 × 2 (0) 3 × 1 ⑾∨× 3 × 1 × 3 2 (0) 3 ⒃∨进行算法第四步,没有找到未被直线覆盖的元素,所以也就无法找到最小的元素,也就不能产生新的效能矩阵,算法进入死循环.这就否定了“匈牙利法”主要过程的算法基础.若算法第二步未完成一个完全分配时,则一定能生成一个新的效能矩阵,出现新的零元素,再重新进行完全分配,最终一定能得到一个完全的最优解.3.3.2 匈牙利算法的改进为了使“匈牙利法”对任意数据都能有效的找到最优解,我们在原算法的基础上增加第六步,以及在第三步后面增加判断功能:若生成n条直线,无法生成新的效能矩阵,则退出“匈牙利法”,进行第六步.第六步:产生新的小系数矩阵,使原矩阵分解.(1)从第二步算法中,记下有(0)标记的行号和列号,作为部分解进行保存.(2)从系数矩阵中抽取行列都没有(0)标记的数据,组成一个新的小系数矩 阵:[]ij E ,1,2,,,1,2,,,i m j m m n ==<.(3)将小系数矩阵再代入“匈牙利法”, 继续计算, 得到小系数矩阵的分配解, 把它添加到部分解的结果中去,组合得到一个新的解,如果新的解仍然不是完全解,则转(1)继续处理.(4)对已分配的元素,两两一对组成任意一个矩形的斜对角顶点, 相加后生成一个基础量, 再将矩形的另一对斜对角顶点的元素相加,生成一个改变量,有n 个已分配的元素将产生12321n n ++++-+-对基础量和改变量.(5)对于每对基础量和改变量,如果存在基础量大于改变量的情况,则找出基础量与改变量差值最大的一对,由改变量取代基础量,转(4)继续处理.(6)得到整个分配的最优解,输出分配结果.在上例中,新的小系数矩阵仅为第2行和第2列,m 为1.另外,也没有找到改变量小于基础量的两对值,所以无须进行优化,直接将该元素添加到部分解中去,通常新的小系数矩阵的阶数都很小.文献[]5中给出了一个2222*的系数矩阵,存在上述问题,利用改进的匈牙利算法最终得到最优解.3.4 匈牙利算法的推广匈牙利算法的关键是在变换后的系数矩阵中找出n 个分布在不同行不同列的独立0元素,而匈牙利算法计算过程比较麻烦,文献[]8,文献[]9在匈牙利算法的基础上提出了两种比较简单的寻找独立0元素的方法:最小零元素消耗法及对角线法.3.4.1 最小零元素消耗法基本思想:对于一个给定的矩阵,其中0元素的数目是一定的,这一定的0元素最多能分配出多少个0元素来?当分配出一个0元素时,该0元素所在行和列上的其它0元素就无法再分配,失去了作用,也就是说,分配出该0元素要用去0元素的个数为:该0元素本身+该0元素所在行上的其它0元素+该0元素所在列上的其它0元素,称此数为该0元素的0消耗数.对于给定矩阵中的所有0元素,可以很容易地找出其0消耗数,根据动态规划的最优化原理,如果第1次分配出0消耗数最少的0元素,将消耗的0元素去掉,在余下的0元素中再分配0消耗最少的0元素,重复这样的过程,最终结果必将得到最多次数的分配即最大分配,这就是最小0元素消耗法.基本方法:利用匈牙利算法变换系数矩阵,使每行每列出现0元素,计算出各个0元素的消耗数,分配处0消耗数最少的那个0元素,以(0)标记.此0元素所在行所在列以不起作用,再计算余下的个各个0元素的消耗数,重复上述步骤,直至每行每列的0元素都标记完,最终得到最大匹配,若标记(0)的个数等于矩阵阶数,得到最优解.在应用此方法的时候,当遇到0消耗相同的0元素不止一个,此时应先分配哪个0元素呢?有以下三种情况:(1)0消耗数相同,但0元素在同一行或同一列上,如下所示:120(,)0(,)i j ij120(,)0(,)i j ij现就0元素在同一行的情况说,如果分配其任何一个,另一个则被消耗掉,他们的0消耗数实际上是相同的,只能表示一个分配,要么分配10(,)i j ,要么分配20(,)i j ,但第i 行则可以确定下来,等其它0元素分配完以后,如12,j j 列中有一列确定,则第i 行可以具体确定出0元素,若12,j j 列仍未确定,则说明最大分配小于n ,或者有多种最优分配方案.对于同一列的情况,可以确定第j 列,具体分配哪一个,视其它0元素的分配而定.(2)0消耗数相同且互相影响,但0元素不同行不同列,此时0元素的消耗数必共同消耗一个或两个0元素,如下所示:11220(,)00(,)i j ij 11220(,)000(,)i j ij110(,)i j 、220(,)i j 的0消耗相同,设为q ,他们的0消耗因共有0元素而相互影响,若先分配110(,)i j ,则在余下的0元素中220(,)i j 的0消耗数为1q -或2q -,必为最小,应予与分配.反之也是,因此这种情况两者应同时分配.(3)0消耗数相同但互不影响,此时必为下图所示:11220(,)0(,)i j ij此时若先分配110(,)i j ,则在余下的0元素中,220(,)i j 的0消耗必为最少,应予与分配,反之也是.这种情况可将它们同时分配.综合上述三种情况,当遇到0消耗数相同的0元素时,如果它们不在同一行同一列,可以将其同时分配;如果在同一行同一列上,可将该行或该列分配,分配数加1,具体分配视其它0元素分配结果而定.3.4.2 对角线法匈牙利算法的实质就是寻找n 个分布在不同行不同列的零元素,而匈牙利算法本身计算过程较复杂,下面给出一种使系数矩阵对角线为零的算法——对角线法.基本步骤:第一步:变换系数矩阵使每行每列都出现零元素,同匈牙利算法第一步.第二步:进行行排序,即(1)在变换后的系数矩阵()ij b 的第j 列元素中(1,2,,)j n =,从第,1,,j j n +行中选取最小元素,记为0b i j .(2)将0i 行元素与j 行进行交换.通过步骤二可使每列中的零元素或最小元素尽量出现在对角线上.若这样选取的最小元素有两个以上时,则取这些最小元素所在行中第n 个元素大者所对应的元素为最小元素;若第n 个元素也相同,则取第1n -个元素中大者对应的元素为最小元素,以此类推;若这些最小元素后各行对应元素都相同,则取这些最小元素所在行中第1j -个元素小者对应元素为最小元素;若第1j -个元素相同,则取再前一个元素中小者对应元素为最小元素,以此类推.若这些最小元素所在行的对应元素均相同,则可任选一最小元素所在行与第j 行元素交换.第三步:检验——若0ii b =(1,2,,)i n =,则以得最优解,过程结束.否则进行第四步.第四步:进行行排序,排序过程类似于第二步,只须将第二步中的“列”改为“行”.而“行”改为“列”,“后”改为“下”,“前”改为“上”即可.第五步:若0ii b =(1,2,,)i n =,则已得到最优解,迭代过程停止,否则进行第六步.第六步:,1,2,,ij ij ii b b b j n =-=. 第七步:若0,1,2,,ij b j n ≥=,则返回第五步.否则,若0ik b <,则令,1,2,,jk jk ik b b b j n =-=,返回第一步.从上述过程来看,整个过程包括两个基本部分,第一部分是通过一系列矩阵的行列变换,把零元素排列在对角线上;第二部分是最解的检验,即所有0ii b =时,已得到最优解.对于此算法的收敛性分析,文献[]9中给出了几个定理,并进行了证明,可以看出此改进算法的收敛性极好,可以有效地解决指派问题.4 对最大化指派问题的匈牙利解法的一点改进对最大化的指派问题的解法,一般方法是找出系数矩阵()ij c 中的最大元素,即1nij i M max c ==∑),,2,1(n j =,做矩阵B ,其元素为),,2,1;,,2,1(n j n i b ij ==且ij ij c M b -=,然后利用匈牙利解法最小化求B 的最优解.下面我们在此基础上对其提出一点改进,直接在原系数矩阵上进行修改.修改的原则是:在最小化问题中,系数矩阵不能出现负数,在最大化问题中,修改后的不能出现正元素,可出现零.基本步骤:第一步:修改系数矩阵.。
匈牙利算法基本原理概述匈牙利算法是一种用于解决二分图最大匹配问题的经典算法。
它是由匈牙利数学家Dénes Kőnig在20世纪30年代提出的。
该算法的基本原理是通过不断地寻找增广路径,来不断扩大二分图的匹配,直到无法找到增广路径为止。
二分图在了解匈牙利算法之前,首先要明确什么是二分图。
二分图是指一个图中的顶点可以分为两个不相交的集合,且图中的边仅连接两个不同集合中的顶点。
可以将一个二分图形象地表示为两列顶点,其中一列的顶点与另一列的顶点之间有边相连。
匹配与增广路径在二分图中,匹配是指图中的一组边,使得每个顶点最多与匹配中的一条边相连。
而增广路径是指一条连接未匹配顶点的交替路径,即该路径依次经过匹配边和非匹配边。
增广路径上的非匹配边可以用来改变原有的匹配状态。
匈牙利算法的步骤匈牙利算法的执行过程可以分为以下几个步骤:步骤一:初始化匹配首先,将图中的所有边标记为未匹配状态。
同时初始化一个大小与二分图中顶点个数相同的数组,用于记录每个顶点的匹配状态。
步骤二:寻找增广路径从二分图的一侧选择一个未匹配顶点作为起始点,然后通过深度优先搜索(DFS)或广度优先搜索(BFS)等算法寻找增广路径。
在寻找增广路径的过程中,记录每个顶点的前一个顶点,以便在后续步骤中进行路径的还原和匹配的更新。
步骤三:更新匹配如果找到了增广路径,则将路径上的匹配边和非匹配边进行交替匹配。
即将路径上的匹配边从匹配状态中移除,将路径上的非匹配边加入匹配状态中。
更新完匹配后,继续寻找下一个增广路径。
步骤四:返回结果重复步骤二和步骤三,直到无法找到增广路径为止。
最终得到的匹配即为二分图的最大匹配。
举例说明图的表示方式在实际应用中,二分图的顶点和边可以用矩阵或邻接表的形式表示。
下面是图的一个示例:考虑一个二分图,其中左侧顶点集合为{A,B,C},右侧顶点集合为{X,Y,Z},边集合为{(A,X),(A,Z),(B,Y),(C,X),(C,Y)}. 用邻接表的形式表示如下:A: X ZB: YC: X Y匈牙利算法实现下面是一个简化版的匈牙利算法的伪代码:function HungarianAlgorithm(graph):初始化匹配状态和前一个顶点数组for each 未匹配的顶点 u:if DFS(u):增加匹配数目返回最大匹配数目function DFS(u):for each 与顶点 u 相连的顶点 v:if v 未访问过:标记 v 为已访问if v 为未匹配顶点或者 DFS(v) 不是匹配顶点的前一个顶点:更新匹配状态返回 True返回 False小结匈牙利算法是一种经典的用于解决二分图最大匹配问题的算法。
什么是二分图,什么是二分图的最大匹配,二分图的最大匹配有两种求法,第一种是最大流(我在此假设读者已有网络流的知识);第二种就是我现在要讲的匈牙利算法。
这个算法说白了就是最大流的算法,但是它跟据二分图匹配这个问题的特点,把最大流算法做了简化,提高了效率。
匈牙利算法其实很简单,但是网上搜不到什么说得清楚的文章。
所以我决定要写一下。
最大流算法的核心问题就是找增广路径(augment path)。
匈牙利算法也不例外,它的基本模式就是:初始时最大匹配为空while 找得到增广路径do 把增广路径加入到最大匹配中去可见和最大流算法是一样的。
但是这里的增广路径就有它一定的特殊性,下面我来分析一下。
(注:匈牙利算法虽然根本上是最大流算法,但是它不需要建网络模型,所以图中不再需要源点和汇点,仅仅是一个二分图。
每条边也不需要有方向。
)图1是我给出的二分图中的一个匹配:[1,5]和[2,6]。
图2就是在这个匹配的基础上找到的一条增广路径:3->6->2->5->1->4。
我们借由它来描述一下二分图中的增广路径的性质:(1)有奇数条边。
(2)起点在二分图的左半边,终点在右半边。
(3)路径上的点一定是一个在左半边,一个在右半边,交替出现。
(其实二分图的性质就决定了这一点,因为二分图同一边的点之间没有边相连,不要忘记哦。
)(4)整条路径上没有重复的点。
(5)起点和终点都是目前还没有配对的点,而其它所有点都是已经配好对的。
(如图1、图2所示,[1,5]和[2,6]在图1中是两对已经配好对的点;而起点3和终点4目前还没有与其它点配对。
)(6)路径上的所有第奇数条边都不在原匹配中,所有第偶数条边都出现在原匹配中。
(如图1、图2所示,原有的匹配是[1,5]和[2,6],这两条配匹的边在图2给出的增广路径中分边是第2和第4条边。
而增广路径的第1、3、5条边都没有出现在图1给出的匹配中。
)(7)最后,也是最重要的一条,把增广路径上的所有第奇数条边加入到原匹配中去,并把增广路径中的所有第偶数条边从原匹配中删除(这个操作称为增广路径的取反),则新的匹配数就比原匹配数增加了1个。