Gale-Shapley算法
前言
最近看了一檔綜藝《心動的信號》(唉, 單身久了, 開始喜歡看別人談戀愛了)
節目中共有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在此基礎上, 提出了完美匹配和穩定匹配的概念.
完美匹配
完美匹配是指, 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 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
- 向女生依次表白, n
. 很顯然, 大 O 時間復雜度為O(n^3). 哎? 怎么和之前分析的不一樣呢? 很明顯, 就差在了第三層. 只要想辦法消掉就好啦.
- 找到要表白的女孩. 直接存儲女孩的索引即可直接找到.
- 找到女生當前對象同理, 直接存儲索引.
- 從女生的喜歡列表中, 找到更喜歡誰? 將數組換成 map, 即可實現O(1)的查找. 空間換時間唄.
至此, 第三層的循環全部去掉. 時間復雜度為O(n^2). 能不能再降低呢? 我還沒有想到.
擴展
人數不匹配
如果男女生人數不一致呢? 那自然是無法得出完美匹配與穩定匹配了, 但是通過上面的步驟, 無論是讓人數較少的一方主動選擇, 還是人數較多的一方主動選擇, 得出的還是一個比較滿意的匹配結果. 當然, 因為人數不匹配, 最終是一定有落單的人.
喜歡列表不為全部
如果女生的喜歡列表, 只是部分男生呢? 那么對于未出現在喜歡列表的人有兩種情況:
對于這兩種態度, 處理方式也自然不同.
如果不能進行匹配, 則匹配結果可能不是完美匹配, 因為你喜歡的已經和別人跑了, 而喜歡你的你又拒絕了.
如果是無所謂的態度, 處理流程基本上和上面一致. 比較是不存在的給出一個較大的默認值即可.
應用
那么這個算法可以應用到哪些場景呢? 想了一下, 關于這種意向匹配的場景, 基本上都可以參考此算法, 比如: 相親、工作分配、大學招生、拍賣等等
別人看綜藝, 想從中學到交友之法, 而我看到的卻是算法? 或許, 破案了? 唉, 不說了, 刷劇去了.
總結
以上是生活随笔為你收集整理的Gale-Shapley算法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: go tcp连接_TCP漫谈之keepa
- 下一篇: spark计算操作整理