Gale-Shapley算法
前言
最近看了一檔綜藝《心動(dòng)的信號(hào)》(唉, 單身久了, 開(kāi)始喜歡看別人談戀愛(ài)了)
節(jié)目中共有n男n女, 他們會(huì)在節(jié)目的最后進(jìn)行表白, 如果我喜歡你, 恰好你也喜歡我, 那么便就會(huì)在一起, 自此傳為一段佳話.
于是, 我就在想, 如何用算法來(lái)實(shí)現(xiàn)這個(gè)匹配的過(guò)程呢?
單一匹配
將信息抽象化, 現(xiàn)有兩個(gè)集合 M N, 每個(gè)集合中存在a個(gè)對(duì)象.
結(jié)果集 R 中元素為 (m, n), 其中 m ∈ M, n ∈ N, m 喜歡 n, n 也喜歡 m.
OK, 設(shè)計(jì)數(shù)據(jù)結(jié)果進(jìn)行實(shí)現(xiàn). (以下以Go進(jìn)行簡(jiǎn)單演示)
package mainimport "fmt"type people struct {Name string // 名字LikePeople string // 喜歡的人名 }type lovers struct {Boy peopleGirl people }func main() {// 分別構(gòu)造男女隊(duì)列boyArr := []people{}girlArr := []people{}// 分別對(duì)他們進(jìn)行匹配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) }當(dāng)然, 這就是簡(jiǎn)單的循環(huán)查找, 但是如果將喜歡的人, 從單個(gè)換成一個(gè)列表呢? 別說(shuō), 我后面找了一下, 還真有這么一個(gè)算法.
Gale-Shapley 算法
再來(lái).現(xiàn)有兩個(gè)集合 M N, 每個(gè)集合中分別存在a個(gè)對(duì)象. 匹配集 R 中的元素為: (m, n), 其中 m ∈ M, n ∈ N.
到這, 基本上和我們上面的匹配一致. Gale-Shapley在此基礎(chǔ)上, 提出了完美匹配和穩(wěn)定匹配的概念.
完美匹配
完美匹配是指, M 和N的每個(gè)成員, 都恰好出現(xiàn)在R的一個(gè)匹配隊(duì)列中. 恰好的意思是, 不多不少就一次.
說(shuō)人話就是: M和N中的所有人都配對(duì)成功, 不存在落單的男孩或女孩.
穩(wěn)定匹配
一個(gè)完美匹配中如果不存在不穩(wěn)定因素, 就稱為穩(wěn)定匹配.
我們上面每個(gè)人只能喜歡一個(gè)人, 現(xiàn)在不是了, 每個(gè)人都有一個(gè)喜歡程度從高到低的列表.
如果說(shuō) m1 在 n1 n2中更喜歡 n2, 那么m1的喜歡列表就是[n2, n1].
簡(jiǎn)單解釋一下不穩(wěn)定因素. 如果在結(jié)果集中, 存在這樣的兩個(gè)元素: (m1, n1), (m2, n2). 而m1相較于n1更喜歡n2, 同時(shí)n2相較于m2也更喜歡m1, 那么他倆就有私奔的可能. 這種可能就稱為不穩(wěn)定因素.
Gale-Shapley算法, 就是從中得出一個(gè)穩(wěn)定匹配的算法. 算法的思想通俗易懂, 一句話概括: 所有男生依次嘗試想所有女生表白
算法的實(shí)現(xiàn)步驟如下:
- 找到一個(gè)還沒(méi)有對(duì)象, 且未向所有女生表白的男生m
- 找到一個(gè)m還沒(méi)有表白過(guò)的女生n
- 如果n還沒(méi)有對(duì)象, 則進(jìn)行匹配
- 如果n有對(duì)象. 則判斷n的喜歡列表. 若更喜歡當(dāng)前對(duì)象, 則保持不變, 若更喜歡m則拋棄當(dāng)前對(duì)象
- 直到m找到對(duì)象或向所有女生都表白過(guò). 則回到第一步, 直到找不到這樣的男生.
通過(guò)流程上來(lái)看, 這是一個(gè)時(shí)間復(fù)雜度O(n^2)的算法. 借用Go來(lái)簡(jiǎn)單實(shí)現(xiàn)一下:
package maintype people struct {Name string // 名字LikePeople []string // 喜好列表CurrentLike int // 后面算法記錄當(dāng)前表白對(duì)象時(shí)使用Friend string // 當(dāng)前匹配對(duì)象 }func main() {// 分別構(gòu)造男女隊(duì)列boyArr := []*people{}girlArr := []*people{}for true {// 找到一個(gè)沒(méi)有對(duì)象, 且未全部表白的男生var searchBoy *peoplefor _, boy := range boyArr {if boy.Friend != "" { // 當(dāng)前男孩已經(jīng)有對(duì)象了continue}// 男孩向所有女生表白過(guò)了if boy.CurrentLike >= len(boy.LikePeople) {continue}searchBoy = boybreak}if searchBoy == nil { // 已經(jīng)全部有對(duì)象了, 結(jié)束break}// 男生向女生依次表白var i intfor i := searchBoy.CurrentLike; i < len(searchBoy.LikePeople); i++ {girlName := searchBoy.LikePeople[i]// 找到這個(gè)女孩girl := searchPeople(girlArr, girlName)if girl == nil { // 習(xí)慣了, 判下空continue}if girl.Friend == "" { // 若女孩沒(méi)有對(duì)象, 則直接配對(duì)girl.Friend = searchBoy.NamesearchBoy.Friend = girl.Namebreak} else { // 若女孩有對(duì)象, 看下 girl 更喜歡誰(shuí)searchBoyIdx := searchNameIndex(girl.LikePeople, searchBoy.Name)girlFriendIdx := searchNameIndex(girl.LikePeople, girl.Friend)if girlFriendIdx < searchBoyIdx { // 保持當(dāng)前continue} else { // 重新組隊(duì)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 }攏共也沒(méi)有幾行, 但是, 它可以保證完美匹配和穩(wěn)定匹配么? 這簡(jiǎn)單的邏輯讓我都有點(diǎn)不相信自己了, 不行, 得證明一下.
首先是完美匹配, 因?yàn)槭沁M(jìn)行的一對(duì)一匹配, 如果最終存在落單的女生, 那么就一定存在相同數(shù)量落單的男生. 同時(shí), 因?yàn)樵谂鷽](méi)有對(duì)象的時(shí)候, 會(huì)接收任意男生的表白, 可以得出落單的女生沒(méi)有收到過(guò)任何男生的表白. 而在匹配的過(guò)程中, 男生會(huì)向喜歡列表中的所有女生依次進(jìn)行表白, 得出落單的男生一定向所有女生進(jìn)行過(guò)表白. 前后矛盾. 故不存在這樣的情況. 因此結(jié)果為完美匹配.
那么結(jié)果是否是穩(wěn)定匹配呢? 如果結(jié)果中存在不穩(wěn)定因素, 既(m1, n1), (m2, n2), 其中m1更喜歡n2, 同時(shí)n2也更喜歡m1. 根據(jù)算法的規(guī)則, m1會(huì)先向n2表白, 此時(shí), 如果n2單身, 則會(huì)接受表白, 如果n2已經(jīng)接受了m2的表白, 則會(huì)毅然決然的選擇分手, 和m1在一起. 即使后面n2再次收到m2的表白, 也不會(huì)和m1進(jìn)行分手. 故不存在這樣的不穩(wěn)定因素. 因此結(jié)果為穩(wěn)定匹配.
算法優(yōu)化
我們?cè)谧铋_(kāi)始分析的時(shí)候, 時(shí)間復(fù)雜度是O(n^2), 現(xiàn)在再來(lái)看一下我們實(shí)現(xiàn)的時(shí)間復(fù)雜度. 上方算法共有一下幾層循環(huán):
- 找到?jīng)]有對(duì)象且未全部表白的男生, n
- 向女生依次表白, n
- 找到表白的女生, n
- 若女生有對(duì)象, 則從女生的喜歡列表中, 找到這兩個(gè)男孩. 2n
- 若重新組隊(duì), 則找到女生的當(dāng)前對(duì)象, n
- 向女生依次表白, n
. 很顯然, 大 O 時(shí)間復(fù)雜度為O(n^3). 哎? 怎么和之前分析的不一樣呢? 很明顯, 就差在了第三層. 只要想辦法消掉就好啦.
- 找到要表白的女孩. 直接存儲(chǔ)女孩的索引即可直接找到.
- 找到女生當(dāng)前對(duì)象同理, 直接存儲(chǔ)索引.
- 從女生的喜歡列表中, 找到更喜歡誰(shuí)? 將數(shù)組換成 map, 即可實(shí)現(xiàn)O(1)的查找. 空間換時(shí)間唄.
至此, 第三層的循環(huán)全部去掉. 時(shí)間復(fù)雜度為O(n^2). 能不能再降低呢? 我還沒(méi)有想到.
擴(kuò)展
人數(shù)不匹配
如果男女生人數(shù)不一致呢? 那自然是無(wú)法得出完美匹配與穩(wěn)定匹配了, 但是通過(guò)上面的步驟, 無(wú)論是讓人數(shù)較少的一方主動(dòng)選擇, 還是人數(shù)較多的一方主動(dòng)選擇, 得出的還是一個(gè)比較滿意的匹配結(jié)果. 當(dāng)然, 因?yàn)槿藬?shù)不匹配, 最終是一定有落單的人.
喜歡列表不為全部
如果女生的喜歡列表, 只是部分男生呢? 那么對(duì)于未出現(xiàn)在喜歡列表的人有兩種情況:
對(duì)于這兩種態(tài)度, 處理方式也自然不同.
如果不能進(jìn)行匹配, 則匹配結(jié)果可能不是完美匹配, 因?yàn)槟阆矚g的已經(jīng)和別人跑了, 而喜歡你的你又拒絕了.
如果是無(wú)所謂的態(tài)度, 處理流程基本上和上面一致. 比較是不存在的給出一個(gè)較大的默認(rèn)值即可.
應(yīng)用
那么這個(gè)算法可以應(yīng)用到哪些場(chǎng)景呢? 想了一下, 關(guān)于這種意向匹配的場(chǎng)景, 基本上都可以參考此算法, 比如: 相親、工作分配、大學(xué)招生、拍賣等等
別人看綜藝, 想從中學(xué)到交友之法, 而我看到的卻是算法? 或許, 破案了? 唉, 不說(shuō)了, 刷劇去了.
總結(jié)
以上是生活随笔為你收集整理的Gale-Shapley算法的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: go tcp连接_TCP漫谈之keepa
- 下一篇: spark计算操作整理