Gale 您所在的位置:网站首页 shapley算法 Gale

Gale

#Gale| 来源: 网络整理| 查看: 265

前言

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

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

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

单一匹配

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

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

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

package main import "fmt" type people struct { Name string // 名字 LikePeople string // 喜欢的人名 } type lovers struct { Boy people Girl people } func main() { // 分别构造男女队列 boyArr := []people{} girlArr := []people{} // 分别对他们进行匹配 var result []lovers for _, 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在此基础上, 提出了完美匹配和稳定匹配的概念.

完美匹配

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

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

稳定匹配

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

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

如果说 m1 在 n1 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 main type people struct { Name string // 名字 LikePeople []string // 喜好列表 CurrentLike int // 后面算法记录当前表白对象时使用 Friend string // 当前匹配对象 } func main() { // 分别构造男女队列 boyArr := []*people{} girlArr := []*people{} for true { // 找到一个没有对象, 且未全部表白的男生 var searchBoy *people for _, boy := range boyArr { if boy.Friend != "" { // 当前男孩已经有对象了 continue } // 男孩向所有女生表白过了 if boy.CurrentLike >= len(boy.LikePeople) { continue } searchBoy = boy break } if searchBoy == nil { // 已经全部有对象了, 结束 break } // 男生向女生依次表白 var i int for 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.Name searchBoy.Friend = girl.Name break } 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.Name searchBoy.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). 能不能再降低呢? 我还没有想到.

扩展

人数不匹配

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

喜欢列表不为全部

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

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

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

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

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

应用

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

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

原文连接: https://hujingnb.com/archives/640



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有