穩定婚配問題

一、穩定婚配問題

通過問題的名字可以看出這是一個類似匹配的問題,有男女各n人,每個人對其他人都有好感度,問如何匹配可以使每個人都能找到自己心儀的對象?

很顯然,二分圖匈牙利算法即可,這裏不過多敘述。

現在加上穩定兩個字,

即當前假設1號男生的對象是1號女生,2號男生的對象是2號女生,但如果1號男生對2號女生的好感度大於對1號女生的好感度,並且二號女生對1號男生的好感度也大於對2號男生的好感度,那麼很不幸:

1號男生會和2號女生在一起了......(很現實的問題o(╥﹏╥)o

那麼如果讓你對這n個男生和n個女生進行婚配,你該如何進行匹配?

二、Gale-Shapley算法

  這裏介紹GS算法來解決這個穩定婚配問題

1、首先先對每一個男生按照當前男生對n個女生的好感度從大到小嚐試匹配一下,如果當前的要匹配的女生還沒有對象,那麼就匹配成功,否則就比較一下這個女生現有的對象和當前這個男生,這個女生對誰的好感度高,來選擇和哪一個男生進行匹配,然後匹配失敗的男生(失去對象的男生o(╥﹏╥)o)壓入一個棧中。

2、遍歷完一遍男生後,這時棧裏有可能有元素,也有可能沒有(沒有即匹配結束),如果有,就按照第一步再來給這個男生匹配,當然嘗試匹配的女生就是之前按好感度從大到小排的匹配失敗的女生的下一個。

3、當棧中爲空時,算法結束,匹配成功。

這個算法其實保證了男生匹配的女生一定是最佳的,因爲更好的肯定是已經匹配失敗了,而對於女生來說,只有可能找到最差的對象。

附上我的GS算法的模板

#include<bits/stdc++.h>
#define exp 1e-8
#define mian main
#define pii pair<int,int>
#define pll pair<ll,ll>
#define ll long long
#define pb push_back
#define PI  acos(-1.0)
#define inf 0x3f3f3f3f
#define w(x) while(x--)
#define int_max 2147483647
#define lowbit(x) (x)&(-x)
#define gcd(a,b) __gcd(a,b)
#define pq(x)  priority_queue<x>
#define ull unsigned long long
#define sc(x) scanf("%d",&x)
#define scl(x) scanf("%lld",&x)
#define pl(a,n) next_permutation(a,a+n)
#define ios ios::sync_with_stdio(false)
#define met(a,x) memset((a),(x),sizeof((a)))
using namespace std;
const int N=1e4+10;
int man[N],gir[N];  //編號爲i的男生/女生匹配的是哪一個女生/男生
int manpos[N];   //編號爲i的男生當前匹配到的是他心中的第幾位的女生
int manarr[N][N],girarr[N][N];  //編號爲i的男生心中排在第j的女生的編號是什麼
int n;
int manp(int manI,int girI)   //返回編號爲girI的女生對編號爲manI的男生的好感度排在第幾
{
    for(int i=1;i<=n;i++)
       if(girarr[girI][i]==manI)
            return i;
       return -1;
}
stack<int>s;
void init()  //初始化
{
    met(man,0);
    met(gir,0);
    while(!s.empty())
        s.pop();
}
void Find(int mani)
{
    int nowgir=manarr[mani][manpos[mani]];
    if(gir[nowgir]==0){     //如果當前女生還沒有對象,直接匹配
        man[mani]=nowgir;
        gir[nowgir]=mani;
    }
    else {
        int nowman=manp(mani,nowgir);   //現在要匹配的男生的好感度排名
        int oldman=manp(gir[nowgir],nowgir);//現有的對象的好感度的排名
        if(nowman<oldman){  //當前要匹配的男生更好
            manpos[gir[nowgir]]++;
            s.push(gir[nowgir]);
            man[mani]=nowgir;
            gir[nowgir]=mani;
        }
        else {  //現有的更好,不交換
            manpos[mani]++;
            s.push(mani);
        }
    }
}
int main()
{
    while(~scanf("%d",&n)){
        init();
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
            scanf("%d",&manarr[i][j]);
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
            scanf("%d",&girarr[i][j]);
            for(int i=1;i<=n;i++)
                manpos[i]=1;
        for(int i=1;i<=n;i++)
            Find(i);       //先爲每個男生匹配一遍
        while(!s.empty()){
            int p=s.top();
            s.pop();
            Find(p);
        }
        for(int i=1;i<=n;i++)
                cout<<man[i]<<endl;
        for(int i=1;i<=n;i++)
            printf("%d  %d ",i,man[i]);
        printf("\n");
    }
}

如果我之前講的不是很清楚地話,這裏引用一下程序員雜誌上的一篇文章再來說一遍

 

什麼是GS算法?每當有人問作者這樣的問題時,他總會引用這個例子:假如你是一個媒人,有若干個單身男子登門求助,還有同樣多的單身女子也前來徵婚。如果你已經知道這些女孩兒在每個男孩兒心目中的排名,以及男孩兒們在每個女孩兒心中的排名,你應該怎樣爲他們牽線配對呢?

最好的配對方案當然是,每個人的另一半正好都是自己的“第一選擇”。這雖然很完美,但絕大多數情況下都不可能實現。比方說,男1號最喜歡的是女1號,而女1號的最愛不是男1號,這兩個人的最佳選擇就不可能被同時滿足。如果好幾個男孩兒最喜歡的都是同一個女孩兒,這幾個男孩兒的首選也不會同時得到滿足。當這種最爲理想的配對方案無法實現時,怎樣的配對方案才能令人滿意呢?

其實,找的對象太完美不見得是好事兒,和諧纔是婚姻的關鍵。如果男1號和女1號各有各的對象,但男1號覺得,比起自己現在的,女1號更好一些;女1號也發現,在自己心目中,男1號的排名比現男友更靠前。這樣一來,這兩人就可能拋棄各自現在的對象——如果出現了這種情況,我們就說婚姻搭配是不穩定的。作爲一個紅娘,你深知,對象介紹得不好沒關係,就怕婚姻關係不穩定。給客戶牽線配對時,雖然不能讓每個人都得到最滿意的,但搭配必須得穩定。換句話說,對於每一個人,在他心目中比他當前伴侶更好的異性,都不會認爲他也是一個更好的選擇。現在,我們的問題是:穩定的婚姻搭配總是存在嗎?應該怎樣尋找?

爲了便於分析,我們下面做一些約定。我們用字母A、B、C對男性進行編號,用數字1、2、3對女性進行編號。我們把所有男性從上到下列在左側,括號裏的數字表示每個人心目中對所有女性的排名;再把所有女性列在右側,用括號裏的字母表示她們對男性的偏好。圖1所示的就是2男2女的一種情形,每個男的都更喜歡女1號,但女1號更喜歡男B,女2號更喜歡男A。若按A-1、B-2進行搭配,則男B和女1都更喜歡對方一些,這樣的婚姻搭配就是不穩定的。但若換一種搭配方案(如圖2),這樣的搭配就是穩定的了


圖1 一個不穩定的婚姻搭配圖

è¿éåå¾çæè¿°

可能很多人會立即想到一種尋找穩定婚姻搭配的策略:不斷修補當前搭配方案。如果兩個人互相都覺得對方比自己當前的伴侶更好,就讓這兩個人成爲一對,剩下被甩的那兩個人組成一對。


圖2 一個穩定的婚姻搭配

è¿éåå¾çæè¿°

如果還有想要私奔的男女對,就繼續按照他們的願望對換情侶,直到最終消除所有的不穩定組合。容易看出,應用這種“修補策略”所得到的最終結果一定滿足穩定性,但這種策略的問題在於,它不一定存在“最終結果”。事實上,按照上述方法反覆調整搭配方案,最終可能會陷入一個死循環,因此該策略甚至不能保證得出一個確定的方案來,如圖3所示。


圖3 應用“修補策略”可能會產生死循環

è¿éåå¾çæè¿°

Gale-Shapley算法

1962年,美國數學家David Gale和Lloyd Shapley發明了一種尋找穩定婚姻的策略。不管男女各有多少人,也不管他們的偏好如何,應用這種策略後總能得到一個穩定的搭配。換句話說,他們證明了穩定的婚姻搭配總是存在的。有趣的是,這種策略反映了現實生活中的很多真實情況。

在這種策略中,男孩兒將一輪一輪地去追求他中意的女子,女子可以選擇接受或者拒絕他的追求者。第一輪,每個男孩兒都選擇自己名單上排在首位的女孩兒,並向她表白。此時,一個女孩兒可能面對的情況有三種:沒有人跟她表白,只有一個人跟她表白,有不止一個人跟她表白。在第一種情況下,這個女孩兒什麼都不用做,只需要繼續等待;在第二種情況下,接受那個人的表白,答應暫時和他做情侶;在第三種情況下,從所有追求者中選擇自己最中意的那一位,答應和他暫時做情侶,並拒絕所有其他追求者。

第一輪結束後,有些男孩兒已經有女朋友了,有些男孩兒仍然是單身。在第二輪追女行動中,每個單身男孩兒都從所有還沒拒絕過他的女孩兒中選出自己最中意的那一個,並向她表白,不管她現在是否是單身。和第一輪一樣,女孩兒們需要從表白者中選擇最中意的一位,拒絕其他追求者。注意,如果這個女孩兒已經有男朋友了,當她遇到了更好的追求者時,她必須拒絕掉現在的男友,投向新的追求者的懷抱。這樣,一些單身男孩兒將會得到女友,那些已經有了女友的人也可能重新變成光棍。在以後的每一輪中,單身男孩兒繼續追求列表中的下一個女孩兒,女孩兒則從包括現男友在內的所有追求者中選擇最好的一個,並對其他人說不。這樣一輪一輪地進行下去,直到某個時候所有人都不再單身,下一輪將不會有任何新的表白發生,整個過程自動結束。此時的婚姻搭配就一定是穩定的了。

這個策略會不會像之前的修補法一樣,出現永遠也無法終止的情況呢?不會。下面我們將說明,隨着輪數的增加,總有一個時候所有人都能配對。由於在每一輪中,至少會有一個男孩兒向某個女孩兒告白,因此總的告白次數將隨着輪數的增加而增加。倘若整個流程一直沒有因所有人都配上對了而結束,最終必然會出現某個男孩兒追遍了所有女孩兒的情況。而一個女孩兒只要被人追過一次,以後就不可能再單身了。既然所有女孩兒都被這個男孩兒追過,就說明所有女孩兒現在都不是單身,也就是說此時所有人都已配對。


圖4 應用上述策略,三輪之後將得出穩定的婚姻搭配

è¿éåå¾çæè¿°

接下來,我們還需要證明,這樣得出的配對方案確實是穩定的。首先注意到,隨着輪數的增加,一個男孩兒追求的對象總是越來越糟,而一個女孩兒的男友只可能變得越來越好。假設男A和女1各自有各自的對象,但比起現在的對象,男A更喜歡女1。因此,男A之前肯定已經跟女1表白過。既然女1最後沒有跟男A在一起,說明女1拒絕了男A,也就是說她有了比男A更好的男孩兒。這就證明了,兩個人雖然不是一對,但都覺得對方比自己現在的伴侶好,這樣的情況絕不可能發生。

我們把用來解決某種問題的一個策略,或者說一個方案,或者說一個處理過程,或者說一系列操作規則,或者更貼切地說,一套計算方法,叫做“算法”。上面這個用來尋找穩定婚姻的策略就叫做“Gale-Shapley算法”,有些人也管它叫“延遲認可算法”。

Gale-Shapley算法的意義和應用

每個算法都有它的實際意義,能給我們帶來很多啓發。Gale-Shapley算法最大的意義就在於,作爲一個爲這些男女牽線的媒人,你並不需要親自計算穩定婚姻匹配,甚至根本不需要了解每個人的偏好,只需要按照這個算法組織一個男女配對活動就可以了。你需要做的僅僅是把算法流程當作遊戲規則告訴大家,遊戲結束後會自動得到一個大家都滿意的婚姻匹配。整個算法可以簡單地描述爲:每個人都去做自己想做的事情。對於男性來說,從最喜歡的女孩兒開始追起是順理成章的事;對於女性來說,不斷選擇最好的男孩兒也正好符合她的利益。因此,大家會自動遵守遊戲規則,不用擔心有人虛報自己的偏好。

歷史上,這樣的“配對遊戲”還真有過實際應用,並且更有意思的是,這個算法的應用居然比算法本身的提出還早10年。早在1952年,美國就開始用這種辦法給醫學院的學生安排工作,這被稱之爲“全國住院醫師配對項目”。配對的基本流程就是,各醫院從尚未拒絕這一職位的醫學院學生中選出最佳人選併發送聘用通知,當學生收到來自各醫院的聘用通知後,系統會根據他所填寫的意願表自動將其分配到意願最高的職位,並拒絕掉其他的職位。如此反覆,直到每個學生都分配到了工作。那時人們並不知道這樣的流程可以保證工作分配的穩定性,只是憑直覺認爲這是很合理的。直到10年之後,Gale和Shapley才系統地研究了這個流程,提出了穩定婚姻問題,並證明了這個算法的正確性。

這個算法還有很多有趣的性質。比如說,大家可能會想,這種男追女女拒男的方案對男性更有利還是對女性更有利呢?答案是,這種方案對男性更有利。事實上,穩定婚姻搭配往往不止一種,然而上述算法的結果可以保證,每一位男性得到的伴侶都是所有可能的穩定婚姻搭配方案中最理想的,同時每一位女性得到的伴侶都是所有可能的穩定婚姻搭配方案中最差的。受篇幅限制,我們略去證明的過程。

這個算法會有一些潛在的問題。剛纔我們已經說了,對於每位女性來說,得到的結果都是所有可能的穩定搭配中最差的一種。此時,倘若有某位女性知道所有其他人的偏好列表,經過精心計算,她有可能發現,故意拒絕掉本不該拒絕的人(暫時保留一個較差的人在身邊),或許有機會等來更好的結果。因而,在實際生活中應用這種算法,不得不考慮一些可能的欺詐與博弈。

這個算法還有一些侷限。例如,它無法處理2n個人不分男女的穩定搭配問題。一個簡單的應用場景便是宿舍分配問題:假設每個宿舍住兩個人,已知2n個學生中每一個學生對其餘2n-1個學生的偏好評價,如何尋找一個穩定的宿舍分配?此時,Gale-Shapley算法就不再有用武之地了。而事實上,宿舍分配問題中很可能根本就不存在穩定的搭配。例如,有A、B、C、D四個人,其中A把B排在第一,B把C排在第一,C把A排在第一,而且他們三人都把D排在最後。容易看出,此時一定不存在穩定的宿舍分配方案。倘若A、D同宿舍,B、C同宿舍,那麼C會認爲A是更好的室友(因爲C把A排在了第一),同時A會認爲C是更好的室友(因爲他把D排在了最後)。同理,B、D同宿舍或者C、D同宿舍也都是不行的,因而穩定的宿舍分配是不存在的。此時,重新定義宿舍分配的優劣性便是一個更爲基本的問題。

穩定婚姻問題還有很多其他的變種,有些問題甚至是NP完全問題,至今仍然沒有(也不大可能有)一種有效的算法。在圖論、算法和博弈論中,這都是非常有趣的話題。

作者顧森,網名Matrix67,北京大學中文系應用語言學專業學生,數學愛好者。2005年開辦數學博客http://www.matrix67.com,至今已積累上千篇文章,已有上萬人訂閱
 

最後再給一個例題poj3487

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<stack>
#include<map>
#define exp 1e-8
#define mian main
#define pii pair<int,int>
#define pll pair<ll,ll>
#define ll long long
#define pb push_back
#define PI  acos(-1.0)
#define inf 0x3f3f3f3f
#define w(x) while(x--)
#define int_max 2147483647
#define lowbit(x) (x)&(-x)
#define gcd(a,b) __gcd(a,b)
#define pq(x)  priority_queue<x>
#define ull unsigned long long
#define sc(x) scanf("%d",&x)
#define scl(x) scanf("%lld",&x)
#define pl(a,n) next_permutation(a,a+n)
#define ios ios::sync_with_stdio(false)
#define met(a,x) memset((a),(x),sizeof((a)))
using namespace std;
const int N=50;
int man[N],gir[N];
int manpos[N];
int manarr[N][N],girarr[N][N];
stack<int>s;
int n;
int mm[N],gg[N];
int manp(int mani,int giri)
{
    for(int i=1;i<=n;i++)
        if(girarr[giri][i]==mani)
        return i;
    return -1;
}
void Find(int mani)
{
    int nowgir=manarr[mani][manpos[mani]];
    if(gir[nowgir]==0){
        gir[nowgir]=mani;
        man[mani]=nowgir;
    }
    else{
        int oldman=manp(gir[nowgir],nowgir);
        int nowman=manp(mani,nowgir);
        if(oldman<nowman){
            manpos[mani]++;
            s.push(mani);
        }
        else{
            manpos[gir[nowgir]]++;
            s.push(gir[nowgir]);
            gir[nowgir]=mani;
            man[mani]=nowgir;
        }
    }
}
void init()
{
    met(man,0);
    met(gir,0);
    while(!s.empty())
        s.pop();
}
map<char,int>m,g;
char t[N];
int main()
{
    int T;
    sc(T);
    while(T--){
            scanf("%d",&n);
            init();
        char c;
        for(int i=1;i<=n;i++){
            cin>>c;
           m[c]=i;
           mm[i]=c-'a'+1;
        }
        for(int i=1;i<=n;i++){
            cin>>c;
            g[c]=i;
            gg[i]=c-'a'+1;
        }
        for(int i=1;i<=n;i++){
           cin>>t;
        for(int j=1;j<=n;j++)
            manarr[m[t[0]]][j]=t[j+1]-'A'+1;
        }
        for(int i=1;i<=n;i++){
            cin>>t;
            for(int j=1;j<=n;j++)
                girarr[g[t[0]]][j]=t[j+1]-'a'+1;
        }
        for(int i=1;i<=40;i++)
            manpos[i]=1;
        for(int i=1;i<=n;i++)
            Find(i);
        while(!s.empty()){
            int p=s.top();
            s.pop();
            Find(p);
        }
        for(int i=1;i<=n;i++)
            printf("%c %c\n",mm[i]+'a'-1,man[i]+'A'-1);
            printf("\n");
    }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章