穩定婚姻匹配問題 EOJ 162 The Stable Marriage Problem
首先說明:本文不是討論婚姻問題的,而是一篇以日常生活的婚姻問題為例子說明一個有趣的演算法: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 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;
}