problem address:http://202.120.106.94/onlinejudge/problemshow.php?pro_id=162
首先说明:本文不是讨论婚姻问题的,而是一篇以日常生活的婚姻问题为例子说明一个有趣的算法:Gale-Shapley算法(延迟认可算法),如果你为此感到失望的话,我将表示我歉意,但是你如果有兴趣的话,还是建议你看一下,尤其是对于目前还没有个GF或BF的朋友以及就要结婚的朋友,在讲解算法的实现过程中,你会感到大有裨益。
问题的描述:Stable Marriage Problem
某社团中有n位女士和n位男士。假定每位女士按照其对每位男士作为配偶的偏爱程度排名次,无并列。也就是说,这种排列是纯顺序的,每位女士对这些男士的排列可以看成一个与自然数对应的sequence, 1,2,3,.....,n。类似的,男士也会对女士进行这种排序。
我们知道,在这个社团里配对成完备婚姻的方式有n!种。假定某种婚姻匹配中存在女士A和B,男士a和b,并且满足如下条件:
1:A和a已结婚
2:B和b已结婚
3:A更偏爱b而非a(名次优先)
4:b更偏爱A而非B
那么,我们认为该完备婚姻是不稳定的。因为在这种假设下,A和b可能会背着别人相伴私奔,他们都认为,与当前配偶相比每个都更偏爱自己的新伴侣。
如果完备婚姻不是不稳定的,我们则称其为稳定的完备婚姻。
问题1:是否存在稳定的完备婚姻(对社团内的所有成员)。
问题2:如果存在的话,该如何找到一个稳定的完备婚姻。
对于以上两个的问题的答案当然是肯定的:即存在一个稳定的完备婚姻策略,而如何得到这个完备的稳定婚姻就是我下文要解释的算法:Gale-Shapley算法
Gale-Shapley算法介绍:该算法是由Gale and Shapley两人提出的,同时也是他们最早开始研究Stable Marriage Problem。在过去的几十年里很多人开始对这一课题产生兴趣,同时越来越多的paper对这一问题进行深入的研究,更重要的是Stable Marriage Problem已经被证明是一个NP问题。但是目前国内对于该问题的讨论和应用还不是很多(google,baidu,yahoo上面也鲜有介绍),而本文也是在看了导师发给我的N篇英文paper后做的一个总结。
Gale-Shapley算法描述:
先对所有女士进行落选标记
当存在落选女士时,进行以下操作
1)每一位落选女士在所有尚未拒绝她的男士中选择一位被他排名最优先的男士
2)每一位男士在所有已经选择他,并且他尚未拒绝的女士中挑选被他排名最优先的女士,对她推迟决定,并于此时拒绝其余女士。
于 是,在算法执行期间,由女士向男士求婚,一些男女订婚。但是,如果收到更好的求婚,男士可以悔婚。即,算法中,一旦男士订婚,他就一直处在订婚状态,但是 她的未婚妻可以改变;而且,对他来说每次改变都是一种改进。然而,女士则在算法执行期间订婚若干次;每一次对她来说都将导致更不理想的伴侣。只要不存在被 拒绝女士,那么每一位男士恰与一位女士订婚,由于人数相等,每一位女士也恰与男士订婚。
现在将每一位男士和他订婚的女士配对就可以得到一种完备婚姻,而且可以证明,这种完备婚姻是稳定的。(证明过程中涉及到《离散数学》中相关图论知识,这里不作证明)
算法的另类表述:任选一位没有配偶的男士X,考察当前没有拒绝他的女士中他最喜欢的那个女士Y,若Y没有配偶或者Y喜欢X更甚于她当前的配偶,则X和Y(暂时)结成配偶,否则Y会拒绝X,重复以上过程,直至所有男士都有配偶。
从上面很容易看出,该算法的最坏时间复杂度为O(N^2),当然前面已经提到这是一个NP问题,没有最好的结果,同时我上面关于算法的表述也只是原始paper中Gale&Shapley所要表达意思,如果你有兴趣的话我会推荐你一下几篇paper,他们都对算法进行了改进:
1:Gale-Shapley Stable Marriage Problem Revisited: Strategic Issues and Applications
(Extended Abstract)
2:Improved Approximation Results for the Stable Marriage Problem
3:Thread Clustering: Sharing-Aware Scheduling on SMP-CMP-SMT Multiprocessors
其中第三篇该算法是在目前SMP-CNP-SMP处理器的实现了应用
好了,最后让我来举个例子对上述算法进行演示:
首先我们做如下规定:(注意前后箭头的方向)
用表达式:“女X——>男Y” 表示女士X选择男士Y,
用表达式:“女X<——男Y” 表示男士Y选择女士X;
比如:1——>4表示女士1选择了男士4,2<——5表示男士5选择了女士2,
而5<——>3表示:女士5选择了男士3,同时男士3也选择了女士5。
1):首先女士1通过观察找到一个最喜欢她(被他排名最优先)的男士5。(注意:并不是要女士选出她最喜欢的男士,而是找出最喜欢该女士的男士)并表示为:1——>5
2):然后女士2找到被他排名最优的男士1(即最喜欢该女士的男士1):2——>1
3):对剩余的女士作同样的选择,得到:女士3选择了男士2(或4);女士4选择了男士2 ;女士5选择了男士3,表达式分别为:3——>2/4;4——>2;5——>3.这里我们假设女士3选择了男士3——>2(如果你选择3——>4也可以,最后的结果是一样的)。
即有如下的选择关系:
女士——>男士
1——>5
2——>1
3——>2
4——>2
5——>3
3):现在开始男士对女士作出选择,男士1选出他最喜欢的女士2:即 2<——1;
男士2选出他最喜欢的女士3即 :3<——2;其余的选择依次类推得出 :5<——3;
3<——4;1<——5;
即有如下的选择关系:
女士<——男士
2<——1
3<——2
5<——3
3<——4
1<——5
综合上述两组关系得到:
女士——男士
1<——>5
2<——>1
3<——>2
5<——>3
4——>2
3<——4
由此可以看出其中男士4和女士4未成功匹配(结婚),最后可以将他们两个暂时匹配即
4<——>4.
所以又得到如下匹配(婚姻)关系:
1<——>5
2<——>1
3<——>2
5<——>3
4<——>4
4):让我门对上述婚姻匹配关系进行稳定性分析,即判断任意两对之间是否满足下面的4个关系:
A和a已结婚(配对)
B和b已结婚(配对)
A更偏爱b而非a(名次优先)
b更偏爱A而非B
我们以1<——>5和3<——>2为例进行说明,通过看女士1的True Preferences表,与当前的伴侣男士5相比起来她更喜欢男士3,而通过看男士2的True Preferences表,与女士1相比他更喜欢当前的已经结婚的女士3,所以上面的4个不稳定关系不能同时满足,所以目前的两对婚姻暂时是稳定的(即不存在私奔的情况~~哈哈!),同样方法分析剩余的所有已婚对的稳定性,如果有不稳定的可能(潜在的私奔可能),就需要从新匹配,直到所有的婚姻是完备稳定的。
/*
利用吉大模板 在匹配算法中有诡异的+k
*/
#include <iostream>
#include <cstring>
using namespace std;
const int N=50;
int case_t,n;
struct node
{
bool state;
int opp,tag;
int list[N];
int priority[N];
void ini()
{
state=tag=0;
}
}man[N],woman[N];
struct R
{
int opp;
int own;
}requst[N];
void stable_match()
{
int k;
for(k=0;k<n;+k)
{
int i,id=0;
for(i=0;i<n;i++)
if(man[i].state==0)
{
requst[id].opp=man[i].list[man[i].tag];
requst[id].own=i;
man[i].tag+=1;
++id;
}
if(id==0)
break;
for(i=0;i<id;i++)
{
if(woman[requst[i].opp].state==0)
{
woman[requst[i].opp].opp=requst[i].own;
woman[requst[i].opp].state=1;
man[requst[i].own].state=1;
man[requst[i].own].opp=requst[i].opp;
}
else
{
if(woman[requst[i].opp].priority[woman[requst[i].opp].opp]>woman[requst[i].opp].priority[requst[i].own])
{
man[woman[requst[i].opp].opp].state=0;
woman[requst[i].opp].opp=requst[i].own;
man[requst[i].own].state=1;
man[requst[i].own].opp=requst[i].opp;
}
}
}
}
}
int main()
{
int i,j;
char ch,chr[N];
bool fg=0;
scanf("%d",&case_t);
while(case_t--)
{
if(fg)
printf("/n");
fg=1;
scanf("%d",&n);
getchar();
for(i=0;i<2*n;i++)
{
scanf("%c",&chr[i]);
getchar();
}
for(i=0;i<n;i++)
{
man[i].ini();
getchar();
getchar();
for(j=0;j<n;j++)
{
scanf("%c",&ch);
man[i].list[j]=ch-'A';
}
getchar();
}
for(i=0;i<n;i++)
{
woman[i].ini();
getchar();
getchar();
for(j=0;j<n;j++)
{
scanf("%c",&ch);
woman[i].priority[ch-'a']=j;
}
getchar();
}
stable_match();
for(i=0;i<n;i++)
printf("%c %c/n",chr[i],man[i].opp+'A');
}
// system("pause");
return 0;
}