最近接触的匹配算法有三种:Gale-Shapley算法,匈牙利算法和KM算法 。
Gale-Shapley算法:
是用来求得一个稳定匹配使用的,它又称为 “ 求婚-拒绝算法 ”,可以说这个算法的名字起得也是非常知名达意了。
就拿男人向女人求婚打比方,为了使最后匹配得到的匹配集趋于稳定,就是说当前男人和女人的关系是相对稳定的,不会说存在两对男女,其中一对的男人和另一对的女人之间的好感比他们当前匹配的配偶更高的现象的出现。怎么做到这样的匹配呢?
可能 “ 你喜欢的样子我都有 ” ,存在一种女人让所有男人都对她有很高的好感,也可能“ 萝卜白菜各有所爱 ”,这个男人不喜欢的女人可能恰恰有另一个男人喜欢的要命。所以将男人集合中的每个男人都对女人集合中的所有女人有一个独属于自己的排名,女人也是一样。
这样,对于男生集合中的每一个男人,他按照自己的偏好列表依此向女人求婚,一个还没有男朋友的女人面对追求者的求婚就先假意答应吧,以后有更好的再换一个不就是了?这样,后面的男人如果发现当前他求婚的女人有了男朋友,就问问这个女人,我和你现在的男朋友你更喜欢谁?女人要是对这个男人还没有自己男朋友来感的话就让这个可怜的男人去找别的他喜欢的女人吧,要是觉得这个男人比自己男朋友强点就果断让自己男朋友走吧,然后和这个追求者在一起。
想想这个算法也是挺好玩,哈哈哈,哪个男人不花心,不是先追求自己最喜欢的女人?哪个女人见到比自己男朋友好的男人不会多想想?哈哈哈,玩笑话~算法传递出来的感觉是如此。
看到这儿,基本上就理解了求解稳定匹配的算法了吧,事实上GS算法也是求解稳定匹配的一个好帮手,把这个算法用到婚介所会不会有点意思哇!
GS的关键如下:
bool Gale_Sharply(PARTNER *girls, PARTNER *boys, int count) {
int bid = FindFreePartner(boys, count);
while (bid >= 0) {
int gid = boys[bid].perfect[boys[bid].next];
if (girls[gid].current == -1) {
boys[bid].current = gid;
girls[gid].current = bid;
}
else {
int bpid = girls[gid].current;
if (GetPerfectPosition(&girls[gid], bpid) > GetPerfectPosition(&girls[gid], bid)) {
boys[bpid].current = -1;
girls[gid].current = bid;
boys[bid].current = gid;
}
}
boys[bid].next++;
bid = FindFreePartner(boys, count);
}
return IsAllPartnerMatch(boys, count);
}
匈牙利算法:
对于一个有2n个节点的二分图,其最多可以有n*n条边,这不难想到。怎么样把二分图中不同集合中的节点按照边匹配起来而使剩下来的独立节点最少呢?匈牙利算法 就是一个求解二部图最大匹配的算法。
?这段话看完图解后看哇!!!
也是假设初始有一个自由男人和自由女人的集合,就是大家都是单身,然后从男人出发去找他喜欢的女人(这个男人和女人之间有连线就说他们相互喜欢),如果这个女人恰巧也是单身,他们俩就先凑活着过呗,就先把他们匹配成一对...过了一会出现了这样一种现象,当一个男人A向他喜欢的女人①表明心意的时候发现,诶,她有男朋友了啊,那么男人A就去问这个女人①的男朋友:男人B,你是不是非她不可了鸭?男人B就说那我看看除了她我还喜欢谁,男人B搜索自己喜欢的女人,发现自己还喜欢女人②,他就去看女人②是不是单身,要是单身了,男人B就把自己原来女朋友①让给男人A,自己选择女人②。要是发现女人②也有男朋友,就重复做和男人A一样的事情...可能到最后发现这条路子行不通,那男人B就开始看看自己还有除了女人①和女人②还有没有别的喜欢的女人,重复上个过程...这样的结果就是或者一些男人和女人通过其他的连线配对了而使最初求婚的男人A最终与女人①在一起了,或者始终无法调剂,男人A和女人①最终是无法在一起的。。。按这样的方式一直匹配直到剩下的单身男女数量最少,我们就说通过匈牙利算法得到了一个最大匹配。值得一提的是,当所有男人和女人都匹配成功后,得到的便是最大匹配的一个特例:完美匹配。
用图表示的话就是:
假设当前情况是这样的,男人B和女人①是一对,男人C和女人②是一对,这时到男人A选女朋友了,他只喜欢女人①,但是女人①是有男朋友的,于是男人A问男人B除了①你还喜欢别人吗,B发现自己还喜欢②,就去问②,但是②也是有男朋友的,于是男人B就问女人②的男朋友男人C有没有别的选择,C发现自己还喜欢③,于是问题就迎刃而解了:C把②让出来,自己选择③,B把①让出来自己选择②,最后A成功和①在一起了。原来的两队匹配也成功增加了一对,现在有三对匹配啦!
通过总结我们发现下面这张图中的匹配线,正好是上面匹配线的反,这些有关增广路线的问题就不具体说了。
匈牙利最大匹配算法的代码关键部分是这里:
bool FindAugmentPath(MAX_MATCH *match, int xi) {
for (int yj = 0; yj < UNIT_COUNT; yj++) {
if ((match->edge[xi][yj] == 1) && (!match->supposed_on_path[yj])) {
match->supposed_on_path[yj] = true;
if ((match->real_on_path[yj] == -1) || (FindAugmentPath(match, match->real_on_path[yj]))) {
match->real_on_path[yj] = xi;
return true;
}
}
}
return false;
}
bool Hungry_Match(MAX_MATCH *match) {
for (int xi = 0; xi < UNIT_COUNT; xi++) {
if (FindAugmentPath(match, xi)) {
match->max_match++;
}
Clear_Supposed_On_Path_Sign(match);
}
return (match->max_match == UNIT_COUNT);
}
KM算法:
我们知道图中的线上可能是带有权重的,有时候权重代表花费,有时候代表时延,有时候代表负载率,欸,是不是有点像计算机网络里面网络路由的相关概念呢,计算包从哪路由可以得到更高的响应率,从哪儿有会带来最大的开销,这样就引出了带权图的匹配的最大权最小权问题。
KM是讨论二分图中最大权最小权问题的一种算法,怎么连两个集合中的点,将他们匹配得到的匹配线上的权值之和最大呢,按照贪心的算法,永远找节点之间权值最大的不就好了,这样又带来一个问题:一个节点出现在多个匹配的对中,这是不允许的。那么我们换一种方式:将节点分成左右两部分,给每个点一个特殊的值,左边集合中的每个节点的,这个值等于每个节点出边的最大的权重,右边集合中每个节点的这个值都等于0。设左边节点集合为X,右边节点集合为Y,则将满足这样条件的线和节点提取出来作为一个子图:X[i]+Y[j]==Weight[i][j];然后当匹配出现冲突时用上述的匈牙利增广路线方式避免冲突,只有冲突无可避免时考虑将左边参与冲突的节点的这个值减去一个数x,右边参与冲突的点的这个值加上这个数x。使更多的边加入基本子图构成相等子图再进行调剂。这个值x的选择应该是做出改变后至少有一条线加入进基本子集。哪这样就选在其中的X集合中的节点和不在其中的Y集合中的节点X[i]+Y[j]-Weight的最小值就好了,这就是KM算法的核心~~代码后续更新哇!
希望大家都能理解各种匹配算法哇!!有不对的地方请不吝赐教哒哒哒!