一. 设计目的
盖尔-沙普利算法(Gale-Shapley算法)的设计目的是为了解决稳定匹配问题,即在给定一组男性和女性的偏好列表的情况下,找到一个稳定的匹配。这里的“稳定”指的是不存在任何一对男性和女性,他们彼此都比当前的匹配对象更偏好对方,从而可能破坏现有的匹配。
二. 设计内容
2.1 背景说明
盖尔-沙普利(Gale-Shapley)稳定匹配算法是美国数学家 David Gale 和 Lloyd Shapley在1962年提出的一种寻找稳定婚姻的策略。这种匹配方式的特点在于:不管需要匹配的总人数有多少、不管他们各自的偏好如何,只要男女人数相等,并且男女双方每个人都能在心中给对方打分,那么应用这种策略后总能得到一个稳定的婚姻搭配。换句话说,他们证明了稳定的婚姻搭配总是存在的。并且这种策略反映了现实生活中的很多真实情况,也不仅仅局限于婚姻匹配。
2.2 问题描述
有N男N女需要寻找结婚对象,并假设他们的性取向全部正常——即婚姻的搭配方式只有男&女这一种。要求是帮助这N男N女中的每个人都成功匹配一个婚姻的对象,并且这个对象必须是稳定的。
什么是稳定呢?举个例子说明:
假设有两对夫妻M1&F2、M2&F1。M1心中更喜欢F1,但是他和F2结婚了,M2心目中更喜欢F2,但是他和F1结婚了,显然这样的婚姻是不稳定的,因为随时都可能发生M1和F1私奔或者M2和F2私奔的情况。所以在男女双方做匹配时(也就是结婚的时候)需要做出稳定的选择,以防这种情况的发生。
三. 步骤
首先,男生需要按照希望与之交往的顺序给所有女生排序,即最理想的女友排在最前、最不理想的放在最后。同样,每个女生也需要给男生排序。接着,男生将按照自己的名单一轮一轮地去追求喜欢的女生,女生也将按照自己的名单接受或拒绝对方的追求。
第一轮,每个男生都向自己名单上排在首位的女生表白。此时,一个女生可能面对的情况有三种:没有人跟她表白、只有一人跟她表白、有不止一人跟她表白。在第一种情况下,这个女生什么都不做,继续等待即可;在第二种情况下,女生接受那个人的表白,答应暂时和他做男女朋友;在第三种情况下,女生从所有追求者中选择自己最喜欢的那一位,答应和他暂时做男女朋友,并拒绝其他所有的追求者。
第一轮结束后,有些男生已经有女朋友了而有些男生仍然是单身狗。在第二轮表白行动中,每个单身男都会从所有还没拒绝过自己的女生中选出自己最喜欢的那一个,并向她表白,不管她现在是否是单身。和第一轮一样,每个被表白的女生需要从表白者中选择最喜欢的男生,并拒绝其他追求者。注意,如果这个女生当前已经有男朋友了,当她遇到了更好的追求者时,她将毫不犹豫地和现男友分手,投向新追求者的怀抱。这样以来,一些单身狗将脱单,而一些倒霉的恩爱狗(男)也会被分手,重新进入单身狗的行列。
在以后的每一轮中,单身狗们将发扬愈挫愈勇的顽强精神,继续追求列表中的下一个女生;女生则从包括现男友在内的所有追求者中选择最好的一个,并给其他所有追求者发好人卡。
最后直到某个时刻所有人都不再单身,那么下一轮将不会有任何新的表白,每个人的对象也都将固定下来,整个过程自动结束——此时的搭配就一定是稳定的了。
四. 代码
4.1伪代码
4.2 java实现
import java.util.*;
class GaleShapley {
public static class Person {
String name;
List<String> preferences;
Person engagedTo;
boolean isMan;
Person(String name, List<String> preferences, boolean isMan) {
this.name = name;
this.preferences = new ArrayList<>(preferences);
this.isMan = isMan;
this.engagedTo = null;
}
}
public static void stableMatching(List<Person> men, List<Person> women) {
Map<String, Person> womanEngagedTo = new HashMap<>();
Queue<Person> freeMen = new LinkedList<>();
for (Person man : men) {
freeMen.add(man);
}
while (!freeMen.isEmpty()) {
Person man = freeMen.poll();
int currentPreferenceIndex = 0;
// Continue proposing to the next woman in the man's preference list
while (currentPreferenceIndex < man.preferences.size()) {
String womanName = man.preferences.get(currentPreferenceIndex);
Person woman = women.stream()
.filter(w -> w.name.equals(womanName))
.findFirst()
.orElse(null);
if (womanEngagedTo.containsKey(womanName) && womanEngagedTo.get(womanName).engagedTo != null) {
Person currentEngagedMan = womanEngagedTo.get(womanName).engagedTo;
// Compare preferences
int womanPrefIndexForCurrentEngagedMan = woman.preferences.indexOf(currentEngagedMan.name);
int womanPrefIndexForProposingMan = woman.preferences.indexOf(man.name);
if (womanPrefIndexForProposingMan < womanPrefIndexForCurrentEngagedMan) {
// Current engagement is broken
currentEngagedMan.engagedTo = null;
freeMen.add(currentEngagedMan);
womanEngagedTo.put(womanName, woman);
woman.engagedTo = man;
man.engagedTo = woman;
break;
} else {
// Proposal is rejected, try next woman
currentPreferenceIndex++;
}
} else {
// Woman is not engaged, so man proposes
womanEngagedTo.put(womanName, woman);
woman.engagedTo = man;
man.engagedTo = woman;
break;
}
}
// If man has no more preferences, he is no longer free to propose
if (currentPreferenceIndex == man.preferences.size()) {
freeMen.add(man);
}
}
}
public static void printMatching(List<Person> men, List<Person> women) {
for (Person man : men) {
if (man.engagedTo != null) {
System.out.println(man.name + " is engaged to " + man.engagedTo.name);
}
}
for (Person woman : women) {
if (woman.engagedTo != null) {
System.out.println(woman.name + " is engaged to " + woman.engagedTo.name);
}
}
}
public static void main(String[] args) {
List<Person> men = new ArrayList<>();
List<Person> women = new ArrayList<>();
// Men's preferences
men.add(new Person("m1", Arrays.asList("w1", "w2", "w3"), true));
men.add(new Person("m2", Arrays.asList("w2", "w1", "w3"), true));
men.add(new Person("m3", Arrays.asList("w1", "w3", "w2"), true));
// Women's preferences
women.add(new Person("w1", Arrays.asList("m2", "m1", "m3"), false));
women.add(new Person("w2", Arrays.asList("m1", "m2", "m3"), false));
women.add(new Person("w3", Arrays.asList("m3", "m1", "m2"), false));
// GS and print
stableMatching(men, women);
printMatching(men, women);
}
}
五. 体会
现实生活中的匹配不会和GS算法描述的流程保持一致,但即使如此,这种策略所反映的结果却和生活经验高度吻合。如果说还能有什么启示,个人觉得不应该在遇到更心仪的人的时候,就果断放弃现在陪你的人。当然单纯从算法层面来讲,GS算法通过不断迭代找到了一个最优稳定匹配,时间复杂度为O(n^2),是实现稳定匹配的一个经典算法。