[算法]Gale-Shapley Algorithm-稳定匹配算法的设计、实现与探讨(上)

一、背景描述

  Gale-Shapley Algorithm,简称为GS算法。也被成为Deferred-Acceptance Algorithm.
  是盖尔和沙普利为了寻找一个稳定匹配而设计出的市场机制。市场一方中的对象(医疗机构)向另一方中的对象(医学院学生)提出要约,每个学生会对自己接到的要约进行考虑,然后抓住自己青睐的(认为它是可接受的),拒绝其它的。该算法一个关键之处在于,合意的要约不会立即被接受,而只是被“抓住”(hold on to),也就是“延迟接受”。要约被拒绝后,医疗机构才可以向另一名医学院学生发出新的要约。整个程序一直持续到没有机构再希望发出新的要约为止,到那个时候,学生们才最终接受各自“抓住”的要约。
  1962年,沙普利与同事戴维·盖尔在《高校招生与婚姻稳定性》一文中写到:以10名男子和10名女子“婚配”为范例,设想如果由每一名女子先作选择并向最中意男子“求婚”,然后再由每一名男子审视所获所有“求婚”并回绝除了最中意女子以外的其他所有“求婚”,就可以实现稳定分配。设想的核心,是男子保留并延迟接受最中意女子的“求婚”。这一方法获称“盖尔-沙普利运算法则”,又称“延迟接受运算法则”。

  以上背景描述来源于百度百科。

二、场景描述

  假设有10个男生和10个女生,已知每个男生对每个女生的喜欢程度排序,以及每个女生对每个男生的喜欢程度排序。我们分别用一个二维数组来表示男生和女生,如下:
  男生:
     1 4 3 6 2 5 8 7 9 0
     2 1 0 3 4 8 5 9 7 6
     4 3 8 9 0 2 1 7 6 5
     2 7 6 1 4 3 8 0 9 5
     5 3 8 4 2 0 7 6 1 9
     5 0 1 7 2 8 9 4 6 3
     6 2 9 8 0 7 5 1 4 3
     9 7 1 8 0 2 5 6 3 4
     8 0 5 9 6 7 1 2 4 3
     7 9 1 6 2 0 5 8 4 3
女生:
     3 5 0 6 9 4 8 7 2 1
     0 1 3 2 7 8 5 9 4 6
     1 0 7 9 3 2 5 8 6 4
     2 0 6 3 4 1 5 7 9 8
     5 6 8 3 2 0 9 4 1 7
     3 0 1 7 9 8 2 4 6 5
     6 2 7 8 0 9 4 1 5 3
     9 3 1 2 0 7 5 6 8 4
     4 1 5 9 6 7 0 2 8 3
     6 0 1 7 2 9 5 8 4 3

  其中每行表示这个男/女生对于全部女/男生的一个喜好程度排序排列,比如第一行的这个男生是1 4 3 6 2 5 8 7 9 0 ,则表示他是喜欢1号女生、其次是4号,再是3号……最后才是0号女生。

  求出最为妥当的男女配对方案。比如从上面的数据中,第二个男生(索引为1,表示第1号)的喜好程度排序列表是2 1 0 3 4 8 5 9 7 6,表示最喜欢第2号女生,而第2号女生的喜好列表是1 0 7 9 3 2 5 8 6 4,刚好她最喜欢的是第1号男生。那么既然他们彼此是最喜欢的,应该要为他们搭桥牵线,成全他们。而对于其他种情况,则尽可能的让男生匹配到他更喜欢的女生,同时匹配到的这个女生也是相对来说比较喜欢这个男生的,之所以用“相对”这个词,是因为由于大家有各自的喜好程度排序,不可能匹配到的双方都一定是相互很喜欢的,只能说从一个最大的彼此喜欢程度排序上来做出匹配。由于最后每个人都会匹配到一个伴,不会存在有人匹配成功,有人找不到伴的情况。所以这个是一种稳定的匹配策略,因此也称之为“稳定婚姻匹配算法”。

三、算法设计

1、首先获取男女生的喜好程度排序列表到二维数组里P_m和P_w,并且创建两个数组M_m和M_w分别储存男生和女生的婚姻配对情况。把这两个数组初始化为-1,-1表示没有配对;

2、从M_m里获取第一个还没配对的男生M,记录索引为i。如果取不出男生,即i=-1的话,说明全部已经配对好了,那么跳到第8步;

3、从P_m[i]里取出男生的喜好列表里的第W(j)个女生(j初始化为0);

4、如果取出的这个女生W(j)还没配对过,那么他们就可以完成配对;

5、如果该女生W(j)是配对过的了,那么从M_w[j]中取出与这个女生配对的那个男生m,记录索引为i2,跟现在要匹配的男生M进行喜好程度排序的比较,如果说M在m前面,也就是表示对于男生m来说,女生更喜欢的是男生M,那么则把女生的配对结果M_w[j]改为与男生M配对,即让M_w[j]=i,同时男生M配对了女生W(j),即让P_m[i]=j。而男生m则暂时沦落为单身狗,P_m[i2]被设置为-1,表示没有匹配;

6、如果说女生更喜欢的是男生m,就拒绝了这个男生M。那么男生M就对这个女生没戏了,只能在他的喜好列表P_m[i]里找第j+i个女生(前面喜欢的女生被抢走了,只能考虑后面喜欢的女生咯),把j = j+1后,跳到前面的第3步;

7、经过前面3-6步的操作后,这个男生M就算配对完了,在他配对成功后,有可能其他已经配对过的男生就被踢出来了。不过我们不用关心是哪个被踢出来,只要再继续循环第2步查询第一个没有配对过的男生,对他进行配对就行了。所以,我们跳到第2步去;

8、会到达这一步说明全部男生是已经配对好的了,表示全部男女生已经配对完毕,我们把匹配结果进行输出。

经过上面的步骤,我们就完成了稳定匹配。可能有读者提出对于第8步的疑问,第8步说了全部男生已经配对好,表示全部男女生已经配对完毕,怎么能知道女生也全部有配对了呢。我们在核心的第4-6部=步中可以看出来,每个男生匹配到的女生都是与其他男生不同的,因为如果相同的话则会计算这两个男生在女生心中的喜好程度排序,从而得出一个男生,而踢掉另外一个,所以最终配对结果不可能存在有多个男生同时配对到同个女生、或者是多个女生同时配对到一个男生的情况。另外,为什么说是稳定的呢?不稳定婚姻是说比如M1与W1匹配了,但是M1却更喜欢W2,而M2与W2匹配了,但是M2却更喜欢W1,这种情况下称之为不稳定婚姻,因为男生没有找到其实自己喜欢的女生,而这女生也比较喜欢自己。在这个算法中,不存在这个问题,因为男生都会优先去选取他最喜欢的女生,而如果女生已经匹配过了的话,则会考虑是不是要换人。比如这里M2与W2虽然匹配了,但是因为M1更喜欢W2,而W2也是比较喜欢M1的,那么W2会果断接受M1而拒绝了M2。所以不存在婚姻的不稳定。

四、算法的实现

  经过上面的算法分析,其实也不难写出对应的代码了。在这里我采用的是C++代码,语言不重要,相信不了解C++的读者只有有语言基础的话 ,也可以根据算法分析而读懂代码流程。
  我先讲解一下代码流程,然后再贴出全部实现代码。

  首先,我们定义一个Marriage类,有以下成员变量:

    int no_of_couples;
    vector <vector <int> > Preference_of_men;
    vector <vector <int> > Preference_of_women;
    vector <int> match_for_men;
    vector <int> match_for_women;

  no_of_couples表示婚姻数量,Preference_of_men和Preference_of_women分别表示的是男/女生的喜好程度排序列表,对应上文的算法分析中的P_m和P_w。

  match_for_men和match_for_women则是存储了每个男/女生配对的女/男生,对应上文的算法分析中的M_m和M_w。

  我们先写出读取文本中的喜好程度排序数据:

  // fill the necessary code here.  The first entry in the input
    // file is the #couples, followed by the preferences of the men
    // and preferences of the women.  Keep in mind all indices start
    // from 0.
    void read_data()
    {
        int value_just_read_from_file;
        int value;
        int i=0,j=0;
        vector <int> *v; 
       ifstream input_file("input.txt");
        if (input_file.is_open()){

            input_file >> no_of_couples;
            for(i=0;i<no_of_couples;i++)
            {
                v = new vector<int>();
                for(j=0;j<no_of_couples;j++)
                {
                    input_file >> value;
                //  cout<<value<<endl;
                    v->push_back(value);
                }
                Preference_of_men.push_back(*v);
            }
            for(i=0;i<no_of_couples;i++)
            {
                v = new vector<int>();
                for(j=0;j<no_of_couples;j++)
                {
                    input_file >> value;
                    v->push_back(value);
                }
                Preference_of_women.push_back(*v);
            }
        }
        else{
            std::cout << "Input file  does not exist" << endl;
        }

    }

  读取的源码很简单,就是文本中的第一个数据表示的是婚姻数量,读取给了no_of_couples,然后再分别读取男生和女生的喜好程度排序列表。

  在算法实现之前,我们先来完成两个函数的封装:
  由于我们需要取出没有配对过的男生,所以封装一个函数,来遍历得得到第一个没配对的,返回一个索引:

  //取出第一个没有配对过的 
    int first_No_Marriage(vector <int> my_array)
    {
        // fill the necessary code for this function
        int i;
        for(i = 0;i<my_array.size();i++)
        {
            if(my_array[i]==-1)
            {
                return i;
            }
        }
        return -1;
    }

  其次,我们在步骤中谈到需要比较女生列表中两个男生的喜好排名,我们封装一个函数,来得到男生1对男生2是否排在前面:

  //计算索引排名,如果index1是排在index2前面则返回true,否则返回false 
    bool rank_check (vector <int> my_array, int index1, int index2)
    {

        int i;
        for(i = 0;i<my_array.size();i++)
        {
            if(my_array[i]==index1)
            {
                return true;
            }else if(my_array[i]==index2)
            {
                return false;
            }
        }
        return false;

    }

  核心的稳定匹配算法代码如下:

void Gale_Shapley()
    {
        int man_index, woman_index;
        int index = 0;
        // 初始化为-1,表示没有匹配 
        for (int i= 0; i < no_of_couples; i++)
        {

            match_for_men.push_back(-1);
            match_for_women.push_back(-1);
        }

        // Gale-Shapley Algorithm
        //循环取出没有配对的男生 
        while ( (man_index = first_No_Marriage(match_for_men)) >= 0)
        {

            index = 0;
            //循环读取该男生的喜好列表里的女生 
            while(index<no_of_couples){
                //得到男生喜好的第index个女生 
                woman_index = Preference_of_men[man_index][index];
                //如果该女生还没匹配 ,则双方配对 
                if(match_for_women[woman_index]==-1)
                {
                    match_for_men[man_index] = woman_index;
                    match_for_women[woman_index] = man_index;
                    break;
                //如果女生已经配对了,则比较一下两个男生在女生的喜好列表中的排名,如果该男生更受这名女生喜欢,则替换该女生的配对男生 
                }else if(rank_check(Preference_of_women[woman_index],man_index,match_for_women[woman_index]))
                {
                    //踢掉原先配对的男生 
                    match_for_men[match_for_women[woman_index] ] = -1;
                    //重新配对这名男生 
                    match_for_women[woman_index] = man_index;
                    //这名男生也配对这名女生 
                    match_for_men[man_index] = woman_index;
                    break;
                //如果已经配对但是没有更受女生喜好的话,则继续探索男生列表里下一个喜欢的女生 
                }else{
                    index++;
                }
         }

         }

    }

  以上从每一步的注释中也说得比较明白了,相信读者不会看不清楚吧。

  最后我们按照一定的格式输出男女生的喜好列表以及配对情况:

  void print_soln()
    {
        int j;
        std::cout << "Number of Couples:" << no_of_couples << endl;
        std::cout << "Preference for Men" <<endl;
        std::cout << "------------------"<<endl;
        for (int i=0;i<no_of_couples;i++){
            std::cout << "(" << i << "):" ;
            for(j=0;j<no_of_couples;j++)
            {
                std::cout <<Preference_of_men[i][j]<<" ";
            }
            std::cout <<endl;
        }

        std::cout << "Preference for WoMen" <<endl;
        std::cout << "------------------"<<endl;
        for (int i=0;i<no_of_couples;i++){
            std::cout << "(" << i << "):" ;
            for(j=0;j<no_of_couples;j++)
            {
                std::cout <<Preference_of_women[i][j]<<" ";
            }
            std::cout <<endl;
        }

        std::cout << "Matching  for Men" <<endl;
            for (int i=0;i<no_of_couples;i++){

                    std::cout << "Man:" << i << "-> Woman:"<<match_for_men[i]<<endl ;
        }
        std::cout <<endl;

        std::cout << "Matching  for Wowan" <<endl;
        for (int i=0;i<no_of_couples;i++){

            std::cout << "Wowan:" << i << "-> man:"<<match_for_women[i] <<endl;
        }
        std::cout <<endl;
    }

  文本的数据如下:

数据

  全部代码如下:

#include <iostream>
#include <vector>
#include <fstream>

using namespace std;

class Marriage
{
    // Private
    int no_of_couples;
    vector <vector <int> > Preference_of_men;
    vector <vector <int> > Preference_of_women;
    vector <int> match_for_men;
    vector <int> match_for_women;


    //取出第一个没有配对过的  
    int first_No_Marriage(vector <int> my_array)
    {
        // fill the necessary code for this function
        int i;
        for(i = 0;i<my_array.size();i++)
        {
            if(my_array[i]==-1)
            {
                return i;
            }
        }
        return -1;
    }


    //计算索引排名,如果index1是排在index2前面则返回true,否则返回false 
    bool rank_check (vector <int> my_array, int index1, int index2)
    {

        int i;
        for(i = 0;i<my_array.size();i++)
        {
            if(my_array[i]==index1)
            {
                return true;
            }else if(my_array[i]==index2)
            {
                return false;
            }
        }
        return false;

    }

    void Gale_Shapley()
    {
        int man_index, woman_index;
        int index = 0;
        // 初始化为-1,表示没有匹配 
        for (int i= 0; i < no_of_couples; i++)
        {

            match_for_men.push_back(-1);
            match_for_women.push_back(-1);
        }

        // Gale-Shapley Algorithm
        //循环取出没有配对的男生 
        while ( (man_index = first_No_Marriage(match_for_men)) >= 0)
        {

            index = 0;
            //循环读取该男生的喜好列表里的女生 
            while(index<no_of_couples){
                //得到男生喜好的第index个女生 
                woman_index = Preference_of_men[man_index][index];
                //如果该女生还没匹配 ,则双方配对 
                if(match_for_women[woman_index]==-1)
                {
                    match_for_men[man_index] = woman_index;
                    match_for_women[woman_index] = man_index;
                    break;
                //如果女生已经配对了,则比较一下两个男生在女生的喜好列表中的排名,如果该男生更受这名女生喜欢,则替换该女生的配对男生 
                }else if(rank_check(Preference_of_women[woman_index],man_index,match_for_women[woman_index]))
                {
                    //踢掉原先配对的男生 
                    match_for_men[match_for_women[woman_index] ] = -1;
                    //重新配对这名男生 
                    match_for_women[woman_index] = man_index;
                    //这名男生也配对这名女生 
                    match_for_men[man_index] = woman_index;
                    break;
                //如果已经配对但是没有更受女生喜好的话,则继续探索男生列表里下一个喜欢的女生 
                }else{
                    index++;
                }
         }

         }

    }


    void read_data()
    {
        int value_just_read_from_file;
        int value;
        int i=0,j=0;
        vector <int> *v; 
       ifstream input_file("input.txt");
        if (input_file.is_open()){

            input_file >> value_just_read_from_file;
            no_of_couples= value_just_read_from_file;
            for(i=0;i<no_of_couples;i++)
            {
                v = new vector<int>();
                for(j=0;j<no_of_couples;j++)
                {
                    input_file >> value;
                //  cout<<value<<endl;
                    v->push_back(value);
                }
                Preference_of_men.push_back(*v);
            }
            for(i=0;i<no_of_couples;i++)
            {
                v = new vector<int>();
                for(j=0;j<no_of_couples;j++)
                {
                    input_file >> value;
                    v->push_back(value);
                }
                Preference_of_women.push_back(*v);
            }
        }
        else{
            std::cout << "Input file  does not exist" << endl;
        }

    }

    void print_soln()
    {
        int j;
        std::cout << "Number of Couples:" << no_of_couples << endl;
        std::cout << "Preference for Men" <<endl;
        std::cout << "------------------"<<endl;
        for (int i=0;i<no_of_couples;i++){
            std::cout << "(" << i << "):" ;
            for(j=0;j<no_of_couples;j++)
            {
                std::cout <<Preference_of_men[i][j]<<" ";
            }
            std::cout <<endl;
        }

        std::cout << "Preference for WoMen" <<endl;
        std::cout << "------------------"<<endl;
        for (int i=0;i<no_of_couples;i++){
            std::cout << "(" << i << "):" ;
            for(j=0;j<no_of_couples;j++)
            {
                std::cout <<Preference_of_women[i][j]<<" ";
            }
            std::cout <<endl;
        }

        std::cout << "Matching  for Men" <<endl;
            for (int i=0;i<no_of_couples;i++){

                    std::cout << "Man:" << i << "-> Woman:"<<match_for_men[i]<<endl ;
        }
        std::cout <<endl;

        std::cout << "Matching  for Wowan" <<endl;
        for (int i=0;i<no_of_couples;i++){

            std::cout << "Wowan:" << i << "-> man:"<<match_for_women[i] <<endl;
        }
        std::cout <<endl;
    }

public:
    void solve_it()
    {
        read_data();
       Gale_Shapley();

        print_soln();
    }
};

int main()
{
    Marriage x;
    x.solve_it();
    return 0;
}

五、运行结果

基于上面的代码,我们进行编译运行,结果截图如下:

p

M

  从结果中可以看出,根据这些男女双方的喜好排序,做出了一个匹配结果,比如文章一开始提到,第1个男生最喜欢第2个女生,第2个女生最喜欢第1个男生,显然他们是最终被配对了。

  以上,我们便完成了对婚姻匹配问题的设计与实现。由于篇幅的原因,我将把对这个问题的探讨放在了下一篇博文里。

  • 21
    点赞
  • 96
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值