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

2023-11-02 20:59

本文主要是介绍[算法]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 functionint 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
{// Privateint 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 functionint 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个男生,显然他们是最终被配对了。

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

这篇关于[算法]Gale-Shapley Algorithm-稳定匹配算法的设计、实现与探讨(上)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/333290

相关文章

不懂推荐算法也能设计推荐系统

本文以商业化应用推荐为例,告诉我们不懂推荐算法的产品,也能从产品侧出发, 设计出一款不错的推荐系统。 相信很多新手产品,看到算法二字,多是懵圈的。 什么排序算法、最短路径等都是相对传统的算法(注:传统是指科班出身的产品都会接触过)。但对于推荐算法,多数产品对着网上搜到的资源,都会无从下手。特别当某些推荐算法 和 “AI”扯上关系后,更是加大了理解的难度。 但,不了解推荐算法,就无法做推荐系

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

康拓展开(hash算法中会用到)

康拓展开是一个全排列到一个自然数的双射(也就是某个全排列与某个自然数一一对应) 公式: X=a[n]*(n-1)!+a[n-1]*(n-2)!+...+a[i]*(i-1)!+...+a[1]*0! 其中,a[i]为整数,并且0<=a[i]<i,1<=i<=n。(a[i]在不同应用中的含义不同); 典型应用: 计算当前排列在所有由小到大全排列中的顺序,也就是说求当前排列是第

csu 1446 Problem J Modified LCS (扩展欧几里得算法的简单应用)

这是一道扩展欧几里得算法的简单应用题,这题是在湖南多校训练赛中队友ac的一道题,在比赛之后请教了队友,然后自己把它a掉 这也是自己独自做扩展欧几里得算法的题目 题意:把题意转变下就变成了:求d1*x - d2*y = f2 - f1的解,很明显用exgcd来解 下面介绍一下exgcd的一些知识点:求ax + by = c的解 一、首先求ax + by = gcd(a,b)的解 这个

综合安防管理平台LntonAIServer视频监控汇聚抖动检测算法优势

LntonAIServer视频质量诊断功能中的抖动检测是一个专门针对视频稳定性进行分析的功能。抖动通常是指视频帧之间的不必要运动,这种运动可能是由于摄像机的移动、传输中的错误或编解码问题导致的。抖动检测对于确保视频内容的平滑性和观看体验至关重要。 优势 1. 提高图像质量 - 清晰度提升:减少抖动,提高图像的清晰度和细节表现力,使得监控画面更加真实可信。 - 细节增强:在低光条件下,抖

【数据结构】——原来排序算法搞懂这些就行,轻松拿捏

前言:快速排序的实现最重要的是找基准值,下面让我们来了解如何实现找基准值 基准值的注释:在快排的过程中,每一次我们要取一个元素作为枢纽值,以这个数字来将序列划分为两部分。 在此我们采用三数取中法,也就是取左端、中间、右端三个数,然后进行排序,将中间数作为枢纽值。 快速排序实现主框架: //快速排序 void QuickSort(int* arr, int left, int rig

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi

让树莓派智能语音助手实现定时提醒功能

最初的时候是想直接在rasa 的chatbot上实现,因为rasa本身是带有remindschedule模块的。不过经过一番折腾后,忽然发现,chatbot上实现的定时,语音助手不一定会有响应。因为,我目前语音助手的代码设置了长时间无应答会结束对话,这样一来,chatbot定时提醒的触发就不会被语音助手获悉。那怎么让语音助手也具有定时提醒功能呢? 我最后选择的方法是用threading.Time

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo