舞伴问题是这样的:有 n 个男孩与 n 个女孩参加舞会,每个男孩和女孩均交给主持一个名单,写上他(她)中意的舞伴名字。无论男孩还是女孩,提交给主持人的名单都是按照偏爱程度排序的,排在前面的都是他们最中意的舞伴。试问主持人在收到名单后,是否可以将他们分成 n 对,使每个人都能和他们中意的舞伴结对跳舞?为了避免舞会上出现不和谐的情况,要求这些舞伴的关系是稳定的。

假如有两对分好的舞伴:(男孩 A,女孩 B)和(男孩 B,女孩 A),但是男孩A更偏爱女孩 A,女孩 A 也更偏爱男孩 A,同样,女孩 B 更偏爱男孩 B,而男孩 B 也更偏爱女爱 B。在这种情况下,这两对舞伴就倾向于分开,然后重新组合,这就是不稳定因素。很显然,这个问题需要的是一个稳定匹配的结果,适合使用Gale-Shapley 算法。

算法实现

首先定义舞伴的数据结构,根据题意,一个舞伴至少要包含两个属性,就是每个人的偏爱舞伴列表和他(她)们当前选择的舞伴。根据 Gale-Shapley 算法的规则,还需要有一个属性表示下一次要向哪个偏爱舞伴提出跳舞要求。当然,这个属性并不是男生和女生同时需要的,当使用“男士优先”策略时,男生需要这个属性,当使用“女士优先”策略时,女生需要这个属性。为了使程序输出更有趣味,需要为每个角色提供一个名字。

综上所述,舞伴的数据结构定义如下:

typedef struct tagPartner {

char *name; //名宇

int next; //下一个邀请对象

int current; //当前舞伴,-1表示还没有舞伴

int pcount; //偏爱列表中舞伴个数

int perfect[UNIT_C0UNT]; //偏爱列表

}PARTNER;

UNIT_COUNT 是男孩或女孩的数量(稳定匹配问题总是假设男孩和女孩的数虽相等),pcount 是偏爱列表中的舞伴个数。根据标准的“稳定婚姻问题”的要求,pcount 的值应该是和 UNIT_COUNT —致的,但是某些情况下(比如一些算法比赛题目的特殊要求)也会要求伙伴们提供的偏爱列表可长可短,因此我们增加了这个属性。

这里给出的实现算法使用bool Gale_Shapley(PARTNER *boys, PARTNER *girls, int count)

{

int bid = FindFreePartner(boys, count);

while(bid >= 0)

{

int gid = boys[bid].perfect[boys[bid].next];

if(girls[gid].current == -1)

{

boys[bid].current = gid;

girls[gid].current = bid;

}

else

{

int bpid = girls[gid].current;

//女孩喜欢bid胜过其当前舞伴bpid

if(GetPerfectPosition(&girls[gid],bpid) > GetPerfectPosition(&girls[gid], bid)) {

boys [bpid].current = -1; //当前舞伴恢复自由身

boys[bid].current = gid; //结交新舞伴

girls[gid].current = bid;

}

}

boys[bid] .next++; //无论是否配对成功,对同一个女孩只邀请一次 bid = FindFreePartner(boys, count);

}

return IsAllPantnerMatch(boys> count);

}

FindFreePartner() 函数负责从男孩列表中找一个还没有舞伴、并且偏好列表中还有没有邀请过的女孩的男孩,返回男孩在列表(数组)中的索引。如果返回值等于 -1,表示没有符合条件的男孩了,于是主循环停止,算法就结束了。

GetPerfectPosition() 函数用于判断女孩喜欢一个舞伴的程度,通过返回舞伴在自己的偏爱列表中的位罝来判断,位罝越靠前,也就是 GetPerfectPosition() 函数的返回值越小,说明女孩越喜欢这个舞伴。

GetPerfectPosition() 函数的实说代码如下:

int GetPerfectPosition(PARTNER *partner, int id)

{

for(int i = 0; i < partner->pCount; i++)

{

if(partner->perfect[i] == id)

{

return i;

}

}

//返回一个非常大的值,意味着根本排不上对

return 0X7FFFFFFF;

}

按照“稳定婚姻问题”的要求,这个函数应该总是能够得到 ID 指定的异性舞伴在 partner 的偏爱列表中的位置,因为每个 partner 的偏爱列表包含所有异性舞伴。但是当题目有特殊需求时,partner 的偏爱列表可能只有部分异性舞伴。比如 partner 非常恨一个人,他们绝对不能成为舞伴,那么 partner 的偏爱列表肯定不会包含这个人。

考虑到算法的通用性,GetPerfectPosition() 函数默认返回一个非常大的数,返回这个数这意味着 ID 指定的异性舞伴在 partner 的偏爱列表中根本没有位罝(非常恨),根据算法的规则,partner 最不喜欢的异性舞伴的位置都比id指定的异性舞伴位置靠前。这也是算法一致性处理的一个技巧,GetPerfectPosition() 函数当然可以设计成返回 -1 表示 ID 指定的异性舞伴不在 partner 的偏爱列表中,但是大家想一想,算法中是不是要对这个返回值做特殊处理?

原来代码中判断位置关系的一行代码处理:

if(GetPerfectPosition(&girls[gid], bpid) > GetPerfectPosition(&girls[gid], bid))

就会变得非常繁琐,让我们看看会是什么情况:

if((GetPerfectPosition(&girls[gid], bpid) == -1)&& (GetPerfectPosition(&girls[gid], bid) == -1))

{

//当前舞伴bpid和bid都不在女孩的喜欢列表中,太糟糕了

...

}

else if(GetPerfectPosition(&girls[gid], bpid) == -1)

{

//当前舞伴bpid不在女孩的喜欢列表中,bid有机会

...

}

else if(GetPerfectPosition(&girls[gid], bid) == -1)

{

//bid不在女孩的喜欢列表中,当前舞伴bPid维持原状

...

}

else if(GetPerfectPosition(&girls[gid], bpid) >GetPerfectPosition(&girls[gid], bid))

{

//女孩喜欢bid胜过其当前舞伴bpid\

...

}

else

{

//女孩喜欢当前舞伴bpid胜过bid

...

}

这是我最不喜欢的代码逻辑,真的,太糟糕了。可见,这个小小的技巧为代码的逻辑处理带来了极大的好处。类似的技巧被广泛应用,在排序算法中经常使用“哨兵”位,避免每次都要判断是否比较完全部元素。面向对象技术中常用的“DuramyObject”技术,也是类似的思想。

Gale-Shapley 算法原来如此简单,你是不是为沙普利能获得诺贝尔奖愤愤不平?其实不然,算法原理的简单并不等于其解决的问题也简单,小算法解决大问题。

改进优化:空间换时间

Gale_Shapley() 函数给出的算法还有点问题,主要是 GetPerfectPosition() 函数的策略,这个函数每次都要遍历 partner 的偏爱舞伴列表才能确定 bid 的位罝,很可能导致理论上O(1) 时间复杂度的方式直接查表确定偏爱舞伴的关系。这样的表可以是for(int w = 0; w < unit_count; w++)

{

//初始化成最大值,原理同上

for(int j = 0; j < UNIT_COUNT; j++)

{

priority= 0X7FFFFFFF;

}

//给偏爱舞伴指定位置关系

int pos = 0;

for(int m = 0; m < girls[w].pCount; m++)

{

priority[w][girls[w].perfect[m]] = pos++;

}

}

最后,将对 GetPerfectPosition() 函数的调用替换成查表:

if(priority[gid][bpid] > priority[gid][bid])

对于一些在算法执行过程中不会发生变化的静态数据,如果算法执行过程中需要反复读取这些数据,并且读取操作存在一定时间开销的场合,比较适合使用这种“以空间换时间”的策略。用合理的方式组织这些数据,使得数据能够在 O(1) 时间复杂度内实现是这种策略的关键。

对本问题应用“以空间换时间”的策略,需要在算法开始的准备阶段初始化好 priority 二维表,这需要一些额外的开销,但是相对于 n2 次查询节省的时间来说,这点开销是能够容忍的。

舞伴问题数据结构java_Gale-Shapley算法解决舞伴问题过程详解(C++实现)相关推荐

  1. 【数据结构入门】算法的时间复杂度和空间复杂度详解

    文章目录 (1)算法效率 (2)时间复杂度的计算 1)什么是时间复杂度 2)大O渐进表示法(估算) 3)时间复杂度计算实例 4)总结 5)一些思考 (3)空间复杂度的计算 (4)常见复杂度对比 本篇前 ...

  2. 数据结构:弗洛伊德算法(最短路径)图文详解

    弗洛伊德算法选取某个节点k作为i到j需要经过的中间节点,通过比较d(i,k)+d(k,j)和现有d(i,j)的大小,将较小值更新为路径长度,对k节点的选取进行遍历,以得到在经过所有节点时i到j的最短路 ...

  3. Bellman-Ford算法图解及手算过程详解 —— C++代码实现

    0. <算法导论>讲解 1. 图24-4 手算过程 2. 代码实现(自己根据算法导论伪代码实现的代码,有错请指出,谢谢) #include<iostream> #include ...

  4. c语言将AOE网络的数据写入TXT文档中,数据结构与算法学习辅导及习题详解.张乃孝版-C/C++文档类资源...

    数据结构与算法学习辅导及习题详解.张乃孝版.04年10月 经过几年的努力,我深深体会到,编写这种辅导书要比编写一本湝通教材困难得多. 但愿我的上述理想,在本书中能够得以体现. 本书的组织 本书继承了& ...

  5. 【数据结构与算法】哈希算法的原理和应用详解!

    在程序员的实际开发中,哈希算法常常能用得到,本文以哈希算法的原理和应用为核心,和大家详细讲解一下哈希算法的概念.常见算法以及原理.在信息安全的应用等等. 一.概念 哈希表就是一种以 键-值(key-i ...

  6. python模拟手写笔迹_Python实现基于KNN算法的笔迹识别功能详解

    本文实例讲述了Python实现基于KNN算法的笔迹识别功能.分享给大家供大家参考,具体如下: 需要用到: Numpy库 Pandas库 手写识别数据 点击此处本站下载. 数据说明: 数据共有785列, ...

  7. python实验原理_Python实现蒙特卡洛算法小实验过程详解

    蒙特卡洛算法思想 蒙特卡洛(Monte Carlo)法是一类随机算法的统称,提出者是大名鼎鼎的数学家冯·诺伊曼,他在20世纪40年代中期用驰名世界的赌城-摩纳哥的蒙特卡洛来命名这种方法. 通俗的解释一 ...

  8. 分治算法小结(附例题详解)

    分治算法小结(附例题详解) 我的理解: 分治算法我的理解就是看人下菜碟,我们要解决的问题就好像一群人构成的集体,要我们解决这个问题,那我们就要满足这群人里面每个人不同的需求,也就是写出解决的代码,把每 ...

  9. 【论文深度研读报告】MuZero算法过程详解

    深度强化学习实验室 官网:http://www.neurondance.com/ 论坛:http://deeprl.neurondance.com/ 作者:饼干Japson(DeepRL-Lab研究者 ...

  10. 《算法导论》红黑树详解(一):概念

    在学习红黑树之前,读者应先掌握二叉查找树的相关知识.学习红黑树或者二叉查找树,推荐大家看<算法导论>.<算法导论>原书第3版 高清PDF 带详细书签目录下载 密码:acis & ...

最新文章

  1. 读书笔记:《图解HTTP》第三章 HTTP报文
  2. CSP浏览器安全策略备忘
  3. Entity Framework 出现 此 ObjectContext 实例已释放,不可再用于需要连接的操作 的错误...
  4. mysql binlog恢复sql_binlog2sql实现MySQL误操作的恢复
  5. 彻底解决_OBJC_CLASS_$_某文件名“, referenced from:问题
  6. SQL语句求解同一人物不同日期,某一属性的差值
  7. Linux 基础I/O :文件描述符,重定向,文件系统,软链接和硬链接,动态库和静态库
  8. Failed to parse PID from file /run/nginx.pid: Invalid argument
  9. com.android.phone lg g3,详细的lg g3 root教程与方法
  10. java ognl表达式_OGNL表达式介绍
  11. html设置label的字体大小,swift - label 的font 设置 文字字体和大小
  12. 互联网架构的演进方向
  13. 站点(e.g. Hexo Blog)提交百度搜索引擎收录实现SEO
  14. 从董明珠雷军世纪之赌中看到什么样的格力和小米?
  15. 深圳大学大学计算机考试科目,深圳大学计算机考研科目有哪些
  16. Docker-入门基础知识(1)
  17. 爬取电影网站链接并进入网盘通过验证码下载的python(未完成)
  18. 配电室环境远程监控物联网方案
  19. tl494组成的超声波发射电路_最简单无线发射电路图大全(超声波发射/射频收发电路/调频发射器) - 全文...
  20. Java中快速掌握正则表达式

热门文章

  1. 奇瑞s61鸿蒙,数码提前曝光,奇瑞新能源 S61 将搭载华为鸿蒙车机系统
  2. r语言 图形一览_R语言之图形概览
  3. oracle 查询空值异常,Oracle中的NULL
  4. 三圆相交阴影部分面积_这题要证明圆的切线并求阴影面积,分割图形求面积法是解题关键...
  5. C语言面向过程与C++面向对象
  6. 便携式不锈钢管道焊接机器人_304不锈钢管居然可以发黑!?
  7. 4.Product-based Neural Networks for User Response Prediction论文详细解读和代码实现
  8. 相机模型--A Unifying Theory for Central Panoramic Systems and Practical Implications
  9. 卫星图像分割--Effective Use of Dilated Convolutions for Segmenting Small Object Instances
  10. GNT格式转换为PNG格式