前言

最近看了一档综艺《心动的信号》(唉, 单身久了, 开始喜欢看别人谈恋爱了)

节目中共有n男n女, 他们会在节目的最后进行表白, 如果我喜欢你, 恰好你也喜欢我, 那么便就会在一起, 自此传为一段佳话.

于是, 我就在想, 如何用算法来实现这个匹配的过程呢?

单一匹配

将信息抽象化, 现有两个集合 M N, 每个集合中存在a个对象.

结果集 R 中元素为 (m, n), 其中 m ∈ M, n ∈ N, m 喜欢 n, n 也喜欢 m.

OK, 设计数据结果进行实现. (以下以Go进行简单演示)

package mainimport "fmt"type people struct {Name       string // 名字LikePeople string // 喜欢的人名
}type lovers struct {Boy  peopleGirl people
}func main() {// 分别构造男女队列boyArr := []people{}girlArr := []people{}// 分别对他们进行匹配var result []loversfor _, boy := range boyArr {// 查看他喜欢的女孩是否喜欢他for _, girl := range girlArr {if boy.LikePeople == girl.Name {// 两情相悦了if girl.LikePeople == boy.Name {result = append(result, lovers{Boy:  boy,Girl: girl,})}break}}}fmt.Println(result)
}

当然, 这就是简单的循环查找, 但是如果将喜欢的人, 从单个换成一个列表呢? 别说, 我后面找了一下, 还真有这么一个算法.

Gale-Shapley 算法

再来.现有两个集合 M N, 每个集合中分别存在a个对象. 匹配集 R 中的元素为: (m, n), 其中 m ∈ M, n ∈ N.

到这, 基本上和我们上面的匹配一致. Gale-Shapley在此基础上, 提出了完美匹配稳定匹配的概念.

完美匹配

完美匹配是指, MN的每个成员, 都恰好出现在R的一个匹配队列中. 恰好的意思是, 不多不少就一次.

说人话就是: MN中的所有人都配对成功, 不存在落单的男孩或女孩.

稳定匹配

一个完美匹配中如果不存在不稳定因素, 就称为稳定匹配.

我们上面每个人只能喜欢一个人, 现在不是了, 每个人都有一个喜欢程度从高到低的列表.

如果说 m1n1 n2中更喜欢 n2, 那么m1的喜欢列表就是[n2, n1].

简单解释一下不稳定因素. 如果在结果集中, 存在这样的两个元素: (m1, n1), (m2, n2). 而m1相较于n1更喜欢n2, 同时n2相较于m2也更喜欢m1, 那么他俩就有私奔的可能. 这种可能就称为不稳定因素.

Gale-Shapley算法, 就是从中得出一个稳定匹配的算法. 算法的思想通俗易懂, 一句话概括: 所有男生依次尝试想所有女生表白

算法的实现步骤如下:

  • 找到一个还没有对象, 且未向所有女生表白的男生m
  • 找到一个m还没有表白过的女生n
  • 如果n还没有对象, 则进行匹配
  • 如果n有对象. 则判断n的喜欢列表. 若更喜欢当前对象, 则保持不变, 若更喜欢m则抛弃当前对象
  • 直到m找到对象或向所有女生都表白过. 则回到第一步, 直到找不到这样的男生.

通过流程上来看, 这是一个时间复杂度O(n^2)的算法. 借用Go来简单实现一下:

package maintype people struct {Name        string   // 名字LikePeople  []string // 喜好列表CurrentLike int      // 后面算法记录当前表白对象时使用Friend      string   // 当前匹配对象
}func main() {// 分别构造男女队列boyArr := []*people{}girlArr := []*people{}for true {// 找到一个没有对象, 且未全部表白的男生var searchBoy *peoplefor _, boy := range boyArr {if boy.Friend != "" { // 当前男孩已经有对象了continue}// 男孩向所有女生表白过了if boy.CurrentLike >= len(boy.LikePeople) {continue}searchBoy = boybreak}if searchBoy == nil { // 已经全部有对象了, 结束break}// 男生向女生依次表白var i intfor i := searchBoy.CurrentLike; i < len(searchBoy.LikePeople); i++ {girlName := searchBoy.LikePeople[i]// 找到这个女孩girl := searchPeople(girlArr, girlName)if girl == nil { // 习惯了, 判下空continue}if girl.Friend == "" { // 若女孩没有对象, 则直接配对girl.Friend = searchBoy.NamesearchBoy.Friend = girl.Namebreak} else { // 若女孩有对象, 看下 girl 更喜欢谁searchBoyIdx := searchNameIndex(girl.LikePeople, searchBoy.Name)girlFriendIdx := searchNameIndex(girl.LikePeople, girl.Friend)if girlFriendIdx < searchBoyIdx { // 保持当前continue} else { // 重新组队girlFriend := searchPeople(boyArr, girl.Friend)if girlFriend != nil { // 分手了girlFriend.Friend = ""}girl.Friend = searchBoy.NamesearchBoy.Friend = girl.Name}}}searchBoy.CurrentLike = i}
}func searchPeople(peopleArr []*people, name string) *people {for _, people := range peopleArr {if people.Name == name {return people}}return nil
}func searchNameIndex(nameArr []string, name string) int {for i, tmpName := range nameArr {if tmpName == name {return i}}return -1
}

拢共也没有几行, 但是, 它可以保证完美匹配稳定匹配么? 这简单的逻辑让我都有点不相信自己了, 不行, 得证明一下.

首先是完美匹配, 因为是进行的一对一匹配, 如果最终存在落单的女生, 那么就一定存在相同数量落单的男生. 同时, 因为在女生没有对象的时候, 会接收任意男生的表白, 可以得出落单的女生没有收到过任何男生的表白. 而在匹配的过程中, 男生会向喜欢列表中的所有女生依次进行表白, 得出落单的男生一定向所有女生进行过表白. 前后矛盾. 故不存在这样的情况. 因此结果为完美匹配.

那么结果是否是稳定匹配呢? 如果结果中存在不稳定因素, 既(m1, n1), (m2, n2), 其中m1更喜欢n2, 同时n2也更喜欢m1. 根据算法的规则, m1会先向n2表白, 此时, 如果n2单身, 则会接受表白, 如果n2已经接受了m2的表白, 则会毅然决然的选择分手, 和m1在一起. 即使后面n2再次收到m2的表白, 也不会和m1进行分手. 故不存在这样的不稳定因素. 因此结果为稳定匹配.

算法优化

我们在最开始分析的时候, 时间复杂度是O(n^2), 现在再来看一下我们实现的时间复杂度. 上方算法共有一下几层循环:

  • 找到没有对象且未全部表白的男生, n

    • 向女生依次表白, n

      • 找到表白的女生, n
      • 若女生有对象, 则从女生的喜欢列表中, 找到这两个男孩. 2n
      • 若重新组队, 则找到女生的当前对象, n

. 很显然, 大 O 时间复杂度为O(n^3). 哎? 怎么和之前分析的不一样呢? 很明显, 就差在了第三层. 只要想办法消掉就好啦.

  • 找到要表白的女孩. 直接存储女孩的索引即可直接找到.
  • 找到女生当前对象同理, 直接存储索引.
  • 从女生的喜欢列表中, 找到更喜欢谁? 将数组换成 map, 即可实现O(1)的查找. 空间换时间呗.

至此, 第三层的循环全部去掉. 时间复杂度为O(n^2). 能不能再降低呢? 我还没有想到.

扩展

人数不匹配

如果男女生人数不一致呢? 那自然是无法得出完美匹配稳定匹配了, 但是通过上面的步骤, 无论是让人数较少的一方主动选择, 还是人数较多的一方主动选择, 得出的还是一个比较满意的匹配结果. 当然, 因为人数不匹配, 最终是一定有落单的人.

喜欢列表不为全部

如果女生的喜欢列表, 只是部分男生呢? 那么对于未出现在喜欢列表的人有两种情况:

  1. 十分讨厌, 不能进行匹配
  2. 无所谓, 如果不能和我喜欢的人在一起, 那和谁都一样

对于这两种态度, 处理方式也自然不同.

如果不能进行匹配, 则匹配结果可能不是完美匹配, 因为你喜欢的已经和别人跑了, 而喜欢你的你又拒绝了.

如果是无所谓的态度, 处理流程基本上和上面一致. 比较是不存在的给出一个较大的默认值即可.

应用

那么这个算法可以应用到哪些场景呢? 想了一下, 关于这种意向匹配的场景, 基本上都可以参考此算法, 比如: 相亲、工作分配、大学招生、拍卖等等


别人看综艺, 想从中学到交友之法, 而我看到的却是算法? 或许, 破案了? 唉, 不说了, 刷剧去了.

Gale-Shapley算法相关推荐

  1. 稳定婚姻问题:Gale–Shapley算法

    (一)问题的引出 在组合数学.经济学.计算机科学中,稳定婚姻问题(英语:stable marriage problem,简称SMP)又称为稳定配对问题(stable matching problem) ...

  2. 【Gale Shapley 婚姻稳定匹配算法实现】

    原理: 所有男性按照好感的高低向对应女性求婚 每个女性在所有的向她发出求婚的男性和其丈夫(如果暂无丈夫则不做比较)选择一个最喜欢的,如果这个最喜欢的是当前的丈夫,则婚姻关系不变,否则与当前丈夫离婚,并 ...

  3. 男女稳定匹配问题 stable matching

    离散数学课(CSCI 2110)上,讲到一个有趣的问题. 假设有五个男生,五个女生,每个人都在自己心中对五个异性有一定的preference排序,比如: 以上的排序表解读为:男生1最中意女生C,次中意 ...

  4. 算法(Python版)|156Kstars|神级项目-(1)The Algorithms - Python简介

    文章目录 算法(Python版) 项目地址 项目概况 说明 参与入门 社区频道 算法列表 Arithmetic Analysis 算术分析 Audio Filters 音频过滤器 Backtracki ...

  5. 软件工程实践2017 结队项目——第二次作业

    作业链接 031502333 GITHUB LINK 前言 近期比较倾向于使用Go语言写小工具,所以在看到作业不限制编程语言时就决定使用Go作为开发语言.而由于Go的诸多优秀特性,使得编写代码效率极高 ...

  6. 开源火种_火种艾完美的牵线搭桥

    开源火种 人工智能,意见,技术(Artificial Intelligence, Opinion, Technology) "Bored in the house, in the house ...

  7. AI帮你找对象:Tinder+ AI=最佳红娘?

    全文共4183字,预计学习时长11分钟 图源:unsplash 2012年,孵化器公司Hatch Labs首次推出了定位式约会软件--Tinder,由IAC和XtremeLabs共同经营.现在,Tin ...

  8. 算法证明:女生遇到心动的男人一定要追!

    我来讲恋爱中的博弈,不,我来讲恋爱中的算法,不,我来讲算法!! 有个著名的问题,叫做 stable matching.早年是一个可爱的俄罗斯老头在图论课上教我的,印象非常深刻,拿出来娱乐下大家.因为这 ...

  9. 【文末福利】图论算法:稳定婚姻问题,如何找到最适合自己的另一半

    什么是算法? >>>> 每当有人问我这样的问题,我总会引用下面这个例子. 假如你是一个媒人,有若干名单身男子登门求助,还有同样多的单身 女子也来征婚.如果你已经知道这些女孩儿在 ...

  10. 图论 —— 稳定婚姻问题与延迟认可算法

    [稳定婚姻问题] 1.集合 M 表示 n 个男性 2.集合 F 表示 n 个女性 3.对于每个人我们都按异性的中意程度给出一份名单(从最中意的到最不中意的) 如果没有 ,f 对 m 比对她的配偶中意的 ...

最新文章

  1. Linux串口原理与编程
  2. switch 使用使用小技巧
  3. For循环中不可以嵌套RDD操作
  4. Tuxedo 8.110gR3 开发环境的安装与配置
  5. oracle把列数据串成一个字符串,一组数据,如何根据一个字段值分组后,把另一字段的字符串累加连接起来?...
  6. php使用memcache处理缓存数据
  7. 中标麒麟服务器系统安装教程,安装国产Linux中标麒麟操作系统教程
  8. Ubuntu 设置固定ip地址
  9. 网银--U盾等--支付宝等总结
  10. C语言五子棋双人模式
  11. canvas学习绘制渐变色
  12. Linux Ubuntu NVIDIA双显卡切换intel显卡方法, 如果无法正常切换
  13. 基于envoy的分布式网关-contour
  14. 图像的高频信息和低频信息的含义
  15. kuberbetes 容器探测(liveness/readiness probes)
  16. 解决IE浏览器下载文件,文件名乱码问题(浏览器历史介绍)
  17. tkinter实现图片自适应
  18. 爆肝!!! orcale 期末复习资料整理
  19. 【汉字编码几个字节】
  20. java哨片红盒 绿盒的区别_海淘维骨力怎么区分红盒,绿盒,蓝盒版本之间的区别...

热门文章

  1. 模拟网页行为之工具篇二
  2. [设计模式] ------ 原型模式(浅拷贝和深拷贝)
  3. [前台]---关于input标签的value值
  4. 现代软件工程 其实还是人的问题
  5. IOC操作Bean管理注解方式(创建对象)
  6. mysql 控制id复原_清空mysql表后,自增id复原
  7. 如何开机进入grub界面_如何进入http://192.168.1.1的设置界面 ?
  8. python登录并关注公众号_python微信公众号之关注公众号自动回复
  9. python pandas库 画图_python绘图:matplotlib和pandas的应用
  10. 【LeetCode笔记】162. 寻找峰值(Java、二分、偏数学)