盖尔-沙普利算法(Gale-Shapley algorithm)简称 “GS算法”,也称为 “延迟接受算法”(deferred-acceptance algorithm),是盖尔和沙普利为了寻找一个稳定匹配而设计出的市场机制。市场一方的对象 Ai,i=1,2,...,m 向另一方的对象 Bj,j=1,2,...,n 发出邀约,每个 Bj 会对接到的邀约进行比较,保留自己认为最好的,拒绝其它的。邀约被拒绝的 Ai 继续向其它的 Bj 发出新的邀约,直到没有 Ai 希望再发出邀约为止。此时各 Bj 才最终接受各自保留的邀约。该算法的一个关键之处在于,合意的邀约不会立即被接受,而只是暂时保留不被拒绝,也就是 “延迟接受”。(解释来源于百度)
(例子来源于百度百科)我们用一个案例来说明 Gale-Shapley 算法的具体操作流程。设有 5 位男士 A、B、C、D、E 和 4 位女士 a、b、c、d,他们的择偶偏好顺序如下:
对于以上案例,Gale-Shapley 匹配算法的执行过程如下:
最后实现的匹配是 A-b,B-c,C-d。基于以上执行过程有如下几个结论:
1、Gale-Shapley 算法不一定得到最大匹配。例如 A-a,B-b,C-c,D-d 的匹配能成 4 对,但不是稳定匹配。因为 A 偏好 b 而 b 也偏好 A。稳定匹配条件要求没有配对的两人不能相互 “喜欢”(单方面偏好可以);
2、在算法执行过程中,有可能出现先前保留的邀约后来被拒绝的情况。比如 d 女士开始时保留了 D 先生的邀约,但后来又拒绝他,转而接受 C 先生,体现了算法的延迟选择性。如果不允许延迟选择,必须当即拍板,则问题转化为秘书问题,与 Gale-Shapley 算法假设的情形不同;
3、如果把男女角色对调,女士发出邀约,男士来接受或拒绝,在本案例中得到的匹配结果也是 A-b,B-c,C-d。这是因为本案例中只有一个稳定匹配。当存在多个稳定匹配时,只要邀约的发出方不滥发邀约(一轮只邀约一个人),匹配的结果对邀约的发出方有利。如果滥发邀约(例如每轮邀约所有可接受者),则相当于角色对调,结果对邀约的接收方有利。
以上是百度百科上对这个算法的全部介绍。
分析上面的算法,有人给出了一些分析,同时附赠了python2的代码:https://blog.csdn.net/lonfee88/article/details/6678190
但是考虑到现在普遍是写python3,再加上有个师兄需要用相应的算法来实现一些自己的网络中的算法,所以我花了一点时间来了解并修改一下得到较为适用的的代码。现在我将这份上传至CSDN和GITHUB,有需要的可以自己借鉴一下,有比较完整的注释,如果有什么不清楚的,请邮箱联系。作者不经常上线CSDN。下面来介绍一下上面链接的大佬给出的原思想和我之后经过修改的部分。
大佬的思想:
while 存在男人m是自由的且还没对每个女人都求过婚
选择这个男人m
令w是m的优先表中还没求过婚的最高排名的女人
if w是自由的
(m,w)变成约会状态
else w当前与m1约会
if w更偏爱m1而不爱m
m保持自由
else w更偏爱m而不爱m1
(m,w)变成约会状态
m1变成自由
endif
endif
endwhile
具体的实现也在上面的链接中给出。
但是在上面代码的测试中,我发现了一些不好用的地方:1.求婚男女双方数目要一样;2.每个男人(女人)都必须对其他的女人(男人)有青睐度,这有时候不一定符合要求;3.原代码的返回是基于一方,比如说基于男方,如果里面有男生没有进行配对,也会返回。基于以上三点,我进行了修正,修正后的思想:
while 存在男人m是自由的且还没对每个女人都求过婚
选择这个男人m
令w是m的优先表中还没求过婚的最高排名的女人
if w是自由的 并且w愿意接受m
(m,w)变成约会状态
else w当前与m1约会 并且w愿意接受m
if w更偏爱m1而不爱m
m保持自由
else w更偏爱m而不爱m1
(m,w)变成约会状态
m1变成自由
endif
endif
endwhile
剔除没有进行配对的人
def GS_matching(MP,WP): #MP是男士的择偶排序的集合 WP是女士的 m = len(MP) n = len(WP) #给出男士和女士是否单身的数组用以评价 isManFree = [True]*m isWomenFree = [True]*n #男士是否向女士求过婚的表格 isManProposed = [[False for i in range(n)]for j in range(m)] #最后匹配得出的组合 返回结果 match = [(-1,-1)]*m while(True in isManFree): #找到第一个单身男士的索引值 indexM = isManFree.index(True) #对每个女生求婚 找到男士优先列表中还没找到对象的女士 if(False in isManProposed[indexM]): indexW = -1 #找到还没被求婚的排名靠前的女士的索引 for i in range(len(MP[indexM])): w = MP[indexM][i] if(not isManProposed[indexM][w]): indexW = w break isManProposed[indexM][indexW] = True if(isWomenFree[indexW]):#女士单身且她愿意接受这个男士 isWomenAccept = False for i in range(len(WP[indexW])): if(WP[indexW][i] == indexM): isWomenAccept = True if(isWomenAccept): isWomenFree[indexW] = False isManFree[indexM] = False match[indexM] = (indexM,indexW) else: indexM1 = -1 #与当前女士已匹配的男士的索引 isWomenAccept = False for i in range(len(WP[indexW])):#找到这个人是否能被这个女士接受 if (WP[indexW][i] == indexM): isWomenAccept = True if(isWomenAccept): for j in range(m): if(match[j][1] == indexW): indexM1 = j break if(WP[indexW].index(indexM)有一些须知需要了解一下:
#1.GS_matching的函数必须让list长度较小的一个在前面 #2.允许每个人可以接受的配对数不一样,即每个男士或者女士可以有数目不一样的青睐者 #3.允许男士和女士的数目不一样多 #4.返回的list里面若出现-1,则表明这个配对没有成功 可以在后续将这些进行剔除(已实现) #输入和输出 输入是两个list集 具体的情况参照main方法里面的输入 输出是一个list 给出了具体的配对方式
代码已上传github,欢迎大家来踩踩https://github.com/ZQ1102118381/Gale-ShapleyAlgorithm 有什么不清楚的地方可以留言可以邮箱,邮箱在个人界面有,欢迎指教!