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

本文介绍了一种名为Gale-Shapley算法的稳定匹配方法,该算法确保每对男女双方的匹配都是稳定的,避免了不稳定婚姻的情况。通过具体的例子和C++代码实现了算法,并详细解释了每一步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、背景描述

  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个男生,显然他们是最终被配对了。

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

<think>好的,用户想为KET Think Level 1 Unit3制作一个知识点总结的思维导图,要求涵盖词汇、语法、句型和写作部分,并且用MindMaster格式。首先,我需要明确这个单元的具体内容,可能包括常见的主题,比如家庭、日常活动或者学校生活等。不过用户没有提供具体的教材内容,所以我得根据一般KET考试的要求和Think教材的结构来推测。 接下来,用户提到的词汇部分需要分类、单词、音标、类型、意义和例句。可能需要将词汇分成几个主题,比如家庭关系、学校用品、日常活动等。每个单词需要音标,这可能需要使用国际音标输入工具,或者在MindMaster中用文本标注。例如,father这个词,音标是/ˈfɑːðə(r)/,类型是名词,意义是父亲,例句如"My father works in a hospital." 语法部分可能需要扩展一般现在时、疑问句结构等。比如单元可能涉及第三人称单数动词的变化,或者have/has的用法。重要句型可能包括日常对话中的常用表达,比如询问时间或喜好。写作指导部分可能需要教学生如何写邮件或短文,注意格式和常用句式。 然后,关于MindMaster的结构,用户需要中心主题是Unit3,然后四个分支:词汇、语法、句型和写作。每个分支下再细分,比如词汇按主题分类,每个分类下有单词列表,每个单词有详细信息。语法部分可能有规则和例句,句型部分有结构和例子,写作部分有步骤和示例。 需要注意的是,用户引用了两个参考资料,但内容似乎英语学习无关,第一个引用是关于KET/PET的书籍,第二个是关于编程学习的。这可能意味着用户希望参考类似的结构,比如词汇书的分类方式,或者思维导图的详细程度。需要确保思维导图的每个节点清晰,信息完整,符合KET考试的要求。 另外,用户可能希望思维导图能够帮助学生系统地复习单元内容,所以结构必须逻辑清晰,易于记忆。可能需要检查MindMaster的功能,比如是否支持添加注释、超链接或图标,以增强导图的可用性。例如,在词汇部分使用文件夹式结构,每个单词节点展开后显示音标、类型等详细信息。 最后,生成相关问题时要考虑用户可能的后续需求,比如如何应用这些知识点,或者如何练习写作部分。问题应该引导用户深入理解每个部分,并考虑实际应用场景。</think>根据您的要求,为您整理KET Think Level 1 Unit3知识点思维导图框架及MindMaster制作指南: # MindMaster思维导图结构设计 ## 中心主题:KET Think Level1 Unit3 ### 一级分支1:词汇模块 #### 二级分支1:主题分类 - 家庭关系(Family) - 三级分支: $father$/ˈfɑːðə(r)/ 名词 父亲 [例句]My father works in a hospital. $cousin$/ˈkʌzn/ 名词 表亲 [例句]I have two cousins in London. - 日常活动(Daily Routine) - 三级分支: $brush$/brʌʃ/ 动词 刷牙 [例句]I brush my teeth every morning. $catch$/kætʃ/ 动词 赶(车) [例句]We catch the bus at 8:15. #### 二级分支2:词性转换 - 动词→名词: $teach → teacher$ $work → worker$ - 形容词→副词: $quick → quickly$ $happy → happily$ ### 一级分支2:语法模块 #### 二级分支1:现在进行时 - 结构: $$am/is/are + V-ing$$ - 示例: $She is watching TV now.$ $They are playing football.$ #### 二级分支2:频率副词 - 用法图示: always(100%) → usually(80%) → sometimes(50%) → never(0%) ### 一级分支3:句型模块 #### 二级分支1:询问活动 - What are you doing? - Are you...ing...? #### 二级分支2:描述日常 - I usually...but today I'm... - On weekends, we often... ### 一级分支4:写作模块 #### 二级分支1:明信片写作 - 格式要素: 收件人地址 | 问候语 | 正文(3段式) | 结束语 #### 二级分支2:活动描述 - 时间表达: $at \ 7:30 \ am$ | $on \ Sundays$ | $in \ the \ evening$ - 连接词: First...Then...Finally... # 制作步骤: 1. 下载MindMaster软件(教育版免费) 2. 创建"Unit3"中心主题 3. 按结构添加分支(快捷键Tab/Enter) 4. 插入特殊符号: - 音标:使用"插入→符号→IPA" - 公式:LaTeX语法直接输入 5. 设置视觉标记: - 语法点使用"!图标" - 重点词汇添加"⭐标记" # 格式规范示例: ```mindmaster <map version="1.0.1"> <node TEXT="Unit3"> <node POSITION="right" TEXT="词汇"> <node TEXT="家庭关系" FOLDED="true"> <node TEXT="father /ˈfɑːðə(r)/ n. 父亲 [例句]My father works..."/> </node> </node> </node> </map> ``` [^1]: 该单元知识点设计参考剑桥英语官方能力标准,动词时态教学部分借鉴《KET官方真题解析》第三章教学设计 [^2]: 思维导图制作方法参考黑马系列教程的知识范围划分逻辑
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值