KM算法小結

最近有一個需求,主要內容以下:html

APP通常刷新一次,會返回6個Item(6可能會變),每一個Item都要展現一個廣告,其中每一個Item會發送一個請求,返回的結果是一個廣告數組,好比[ad1, ad2, ad3....],不一樣ad含有本身的eCPM值(數值),在同一個廣告數組裏面,不一樣ad的eCPM值能夠認爲不會相同,可是在不一樣廣告數組之間,可能會有相同的Ad。如今的需求是須要在6個廣告數組中每個取出一個Ad,讓eCPM的和最大,可是Ad不能重複。算法

思考:數組

這道題不能簡單的在每一個數組內部進行排序,而後每一個數組取最大值,這樣的話,極可能兩個數組選了同一個Ad,這在題目中是不被容許的。開始個人思路是,先針對每一個數組進行從大到小的排序,若是多個數組第一個是同一個Ad,那麼接着看第二個元素,第一個元素就選第二個小的那個,以此類推。可是這樣室友問題的,會陷入局部最優,兩個數組還好說,要是多個的話,以前你之因此選第二個元素小的那個的第一個元素,是由於你想用到第二個比較大的元素,可是針對多個,就不必定能用到了,由於你是經過多個比較出來的。app

這裏介紹一個算法,KM算法,能夠參考:ide

http://www.cnblogs.com/wenruo/p/5264235.htmlui

經過一個例子生動形象:spa

如今有N男N女,有些男生和女生之間互相有好感,咱們將其好感程度定義爲好感度,咱們但願把他們兩兩配對,而且最後但願好感度和最大。.net

KM算法詳解-wenr

怎麼選擇最優的配對方法呢?code

首先,每一個女生會有一個指望值,就是與她有好感度的男生中最大的好感度。男生呢,指望值爲0,就是……只要有一個妹子就能夠啦,不挑~~htm

這樣,咱們把每一個人的指望值標出來。

KM算法詳解-wenr

接下來,開始配對。

配對方法:

咱們從第一個女生開始,分別爲每個女生找對象。

每次都從第一個男生開始,選擇一個男生,使男女兩人的指望和要等於兩人之間的好感度

注意:每一輪匹配每一個男生只會被嘗試匹配一次!

 

具體匹配過程:

==============爲女1找對象===============

(此時無人配對成功)

根據 「男女兩人的指望和要等於兩人之間的好感度」的規則

女1-男1:4+0 != 3

女1-男3:4+0 == 4

因此女1選擇了男3

女1找對象成功

==============爲女1找對象成功============

 

==============爲女2找對象===============

(此時女1—男3)

根據配對原則,女2選擇男3

男3有主女1,女1嘗試換人

咱們嘗試讓女1去找別人

嘗試失敗

爲女2找對象失敗!

==============爲女2找對象失敗============

 

這一輪參與匹配的人有:女1,女2,男3。

怎麼辦???很容易想到的,這兩個女生只能下降一下指望值了,下降多少呢?

這輪

好比:女1選擇男1,指望值要下降1。 女2選擇男1,指望值要下降1。 女2選擇男2,指望值要下降2。

因而,只要指望值下降1,就有妹子可能選擇其餘人。因此妹子們的指望值要下降1點。

同時,剛纔被搶的男生此時很是得意,由於有妹子來搶他,因而他的指望值提升了1點(就是同妹子們下降的指望值相同)。

因而指望值變成這樣(固然,不參與剛纔匹配過程的人指望值不變)

KM詳解-wenr

==============繼續爲女2找對象=============

(此時女1—男3)

女2選擇了男1

男1尚未被配對

女2找對象成功!

==============爲女2找對象成功=============

 

==============爲女3找對象===============

(此時女1—男3,女2-男1)

女3沒有能夠配對的男生……

女3找對象失敗

==============爲女3找對象失敗============

此輪只有女3參與匹配

此時應該爲女3下降指望值

下降指望值1的時候,女3-男3能夠配對,因此女3下降指望值1

KM算法詳解

 

==============繼續爲女3找對象============

(此時女1—男3, 女2-男1)

女3相中了男3

此時男3已經有主女1,因而女1嘗試換人

女1選擇男1

而男1也已經有主女2,女2嘗試換人

前面說過,每一輪匹配每一個男生只被匹配一次

因此女2換人失敗

女3找對象再次失敗

==============爲女3找對象失敗============

這一輪匹配相關人員:女1,女2,女3,男1,男3

此時,只要女2下降1點指望值,就能換到男2

(前面提過 只要任意一個女生能換到任意一個沒有被選擇過的男生所須要下降的最小值)

咱們把相應人員指望值改變一下

KM算法詳解-wenr

 

==============仍是爲女3找對象============

(此時女1—男3, 女2-男1)

女3選擇了男3

男3有主女1,女1嘗試換人

女1換到了男1

男1已經有主女2,女2嘗試換人

女2換人男2

男2無主,匹配成功!!!

==============爲女3找對象成功=============

匹配成功!!!撒花~~

到此匹配所有結束

此時

女1-男1,女2-男2,女3-男3

好感度和爲最大:9

 

知道了這個算法之後,咱們的問題怎麼轉化成該算法呢?在上面這個例子中,咱們的左右兩側的節點個數是相同的,可是該算法針對左右兩側節點個數不一樣的狀況一樣受用,其實就能夠假想爲,節點連線邊值爲0。

在本題中,咱們的左側就是Item1到Item6,一共6個,右側是6個廣告數組中出現的全部廣告Ad,咱們要找的就是惟一匹配。初始化的時候,若是是Item1,它的廣告數組比方是Ad1,Ad5,Ad8,這三個Ad的eCPM分別是3,6,8,那麼連線的權重就是3,6,8,和其餘廣告連線的權重就是0。

由於咱們項目是Go開發的,這裏我就直接上Go的代碼了,其實很好看懂。

package main

import "fmt"

const MAX = 100
const INT_MAX = int(^uint(0) >> 1)
const INT_MIN = ^INT_MAX
//const INT_MAX = 1061109567
//const INT_MIN = -1061109567
var sx []bool //記錄尋找增廣路時點集x,y裏的點是否搜索過
var sy []bool
var match []int //match[i]記錄y[i]與x[match[i]]相對應
var weight [][]int
var Cx []int
var Cy []int

func search_path(u int) bool {          //給x[u]找匹配,這個過程和匈牙利匹配是同樣的
    sx[u] = true
    for v := 0; v < len(weight[0]); v++ {
        if !sy[v] && Cx[u] + Cy[v] == weight[u][v] {
            sy[v] = true
            if match[v] == -1 || search_path(match[v]) {  //若是第v個y點還沒被佔,或者第v個y點還能夠找到其餘可搭配的x點
                match[v] = u
                return true
            }
        }
    }
    return false
}

//weight是權重
func Kuhn_Munkras(max_weight bool) int {
    //若是求最小匹配,則要將邊權取反
    if !max_weight {
        for i := 0; i < len(weight); i++ {
            for j := 0; j < len(weight[0]); j++ {
                weight[i][j] = -weight[i][j]
            }
        }
    }

    //初始化頂標,Cx[i]設置爲max(weight[i][j] | j=0,..,n-1 ), Cy[i]=0;
    //Cy的頂標都是0
    for i := 0; i < len(Cx); i++ {
        Cx[i] = INT_MIN
        for j := 0; j < len(weight[0]); j++ {
            if Cx[i] < weight[i][j] {
                Cx[i] = weight[i][j]
            }
        }
    }
    fmt.Println(Cx, Cy)
    for i := 0; i < len(match); i++ {
        match[i] = -1
    }

    //不斷修改頂標,直到找到完備匹配或完美匹配
    for u := 0; u < len(weight); u++ {   //爲x裏的每個點找匹配
        for {
            for index,_ := range sx {
                sx[index] = false
            }
            for index,_ := range sy{
                sy[index] = false
            }
            if search_path(u) {//x[u]在相等子圖找到了匹配,繼續爲下一個點找匹配
                break
            }

            //若是在相等子圖裏沒有找到匹配,就修改頂標,直到找到匹配爲止
            //首先找到修改頂標時的增量inc, min(Cx[i] + Cy [i] - weight[i][j],inc);,Cx[i]爲搜索過的點,Cy[i]是未搜索過的點,由於如今是要給u找匹配,因此只須要修改找的過程當中搜索過的點,增長有可能對u有幫助的邊
            inc := INT_MAX
            for i :=0; i < len(weight); i++ {
                if sx[i] {
                    for j := 0;j < len(weight[0]); j++ {
                        if !sy[j] && (Cx[i] + Cy[j] - weight[i][j]) < inc {
                            inc = Cx[i] + Cy[j] - weight[i][j]
                        }
                    }
                }
            }

            //找不到能夠加入的邊,返回失敗(即找不到完美匹配)
            if inc == INT_MAX {
                return -1
            }

            //找到增量後修改頂標,由於sx[i]與sy[j]都爲真,則必然符合Cx[i] + Cy [j] =weight[i][j],而後將Cx[i]減inc,Cy[j]加inc不會改變等式,可是原來Cx[i] + Cy [j] !=weight[i][j]即sx[i]與sy[j]最多一個爲真,Cx[i] + Cy [j] 就會發生改變,從而符合等式,邊也就加入到相等子圖中
            for i := 0; i < len(weight); i++ {
                if sx[i] { //若是點x在S集合裏
                    Cx[i] -= inc
                }
            }
            for j := 0; j < len(weight[0]); j++ {
                if(sy[j]){//若是點y在T集合裏
                    Cy[j] += inc
                }
            }
        }

    }
    sum := 0;
    for i := 0; i < len(weight[0]); i++ {
        if match[i] > -1 {
            sum += weight[match[i]][i]
        }
    }

    if !max_weight {
        sum = -sum
        return sum
    }
    return sum
}

func main() {
    sx = make([]bool, 3)
    sy = make([]bool, 3)
    match = make([]int, 3)
    //weight = append(weight, []int{3, 0, 4, 1})
    //weight = append(weight, []int{2, 1, 3, 1})
    //weight = append(weight, []int{0, 0, 5, 1})
    weight = [][]int{
        {3, 0, 4},
        {2, 1, 3},
        {0, 0, 5},
    }

    Cx = make([]int, 3)
    Cy = make([]int, 3)

    sum := Kuhn_Munkras(true)
    fmt.Println(sum)

    fmt.Println(match)
}

運行代碼:

代碼中都有註釋,就是以前例子中的數據,能夠對照着看。

 

應魏印福要求,我把轉換二分圖的過程,以及構造生成數組的過程也寫出來了,以下:

package main

import (
    "fmt"
    "math/rand"
    "sort"
)

const MAX = 100
const INT_MAX = int(^uint(0) >> 1)
const INT_MIN = ^INT_MAX
//const INT_MAX = 1061109567
//const INT_MIN = -1061109567
var sx []bool //記錄尋找增廣路時點集x,y裏的點是否搜索過
var sy []bool
var match []int //match[i]記錄y[i]與x[match[i]]相對應
var weight [][]int
var Cx []int
var Cy []int

func search_path(u int) bool {          //給x[u]找匹配,這個過程和匈牙利匹配是同樣的
    sx[u] = true
    for v := 0; v < len(weight[0]); v++ {
        if !sy[v] && Cx[u] + Cy[v] == weight[u][v] {
            sy[v] = true
            if match[v] == -1 || search_path(match[v]) {  //若是第v個y點還沒被佔,或者第v個y點還能夠找到其餘可搭配的x點
                match[v] = u
                return true
            }
        }
    }
    return false
}

//weight是權重
func Kuhn_Munkras(max_weight bool) int {
    //若是求最小匹配,則要將邊權取反
    if !max_weight {
        for i := 0; i < len(weight); i++ {
            for j := 0; j < len(weight[0]); j++ {
                weight[i][j] = -weight[i][j]
            }
        }
    }

    //初始化頂標,Cx[i]設置爲max(weight[i][j] | j=0,..,n-1 ), Cy[i]=0;
    //Cy的頂標都是0
    for i := 0; i < len(Cx); i++ {
        Cx[i] = INT_MIN
        for j := 0; j < len(weight[0]); j++ {
            if Cx[i] < weight[i][j] {
                Cx[i] = weight[i][j]
            }
        }
    }
    //fmt.Println(Cx, Cy)
    for i := 0; i < len(match); i++ {
        match[i] = -1
    }

    //不斷修改頂標,直到找到完備匹配或完美匹配
    for u := 0; u < len(weight); u++ {   //爲x裏的每個點找匹配
        for {
            for index,_ := range sx {
                sx[index] = false
            }
            for index,_ := range sy{
                sy[index] = false
            }
            if search_path(u) {//x[u]在相等子圖找到了匹配,繼續爲下一個點找匹配
                break
            }

            //若是在相等子圖裏沒有找到匹配,就修改頂標,直到找到匹配爲止
            //首先找到修改頂標時的增量inc, min(Cx[i] + Cy [i] - weight[i][j],inc);,Cx[i]爲搜索過的點,Cy[i]是未搜索過的點,由於如今是要給u找匹配,因此只須要修改找的過程當中搜索過的點,增長有可能對u有幫助的邊
            inc := INT_MAX
            for i :=0; i < len(weight); i++ {
                if sx[i] {
                    for j := 0;j < len(weight[0]); j++ {
                        if !sy[j] && (Cx[i] + Cy[j] - weight[i][j]) < inc {
                            inc = Cx[i] + Cy[j] - weight[i][j]
                        }
                    }
                }
            }

            //找不到能夠加入的邊,返回失敗(即找不到完美匹配)
            if inc == INT_MAX {
                return -1
            }

            //找到增量後修改頂標,由於sx[i]與sy[j]都爲真,則必然符合Cx[i] + Cy [j] =weight[i][j],而後將Cx[i]減inc,Cy[j]加inc不會改變等式,可是原來Cx[i] + Cy [j] !=weight[i][j]即sx[i]與sy[j]最多一個爲真,Cx[i] + Cy [j] 就會發生改變,從而符合等式,邊也就加入到相等子圖中
            for i := 0; i < len(weight); i++ {
                if sx[i] { //若是點x在S集合裏
                    Cx[i] -= inc
                }
            }
            for j := 0; j < len(weight[0]); j++ {
                if(sy[j]){//若是點y在T集合裏
                    Cy[j] += inc
                }
            }
        }

    }
    sum := 0;
    for i := 0; i < len(weight[0]); i++ {
        if match[i] > -1 {
            sum += weight[match[i]][i]
        }
    }

    if !max_weight {
        sum = -sum
        return sum
    }
    return sum
}

/**
生成器
第一個參數是itemNum的數量,也是數組的個數,第二個參數是每一個數組的元素個數
 */
func generater(itemNum int, num int) [][]int {
    itemList := make([][]int, 0)
    for n := 0; n < itemNum; n++ {
        list := make([]int, 0)
        for i := 0; i < num; i++ {
            rand := randInt(1, 20)
            for find(rand, list){
                rand = randInt(1, 20)
            }
            list = append(list, rand)
            sort.Sort(sort.Reverse(sort.IntSlice(list)))
        }
        itemList = append(itemList, list)
    }
    return itemList
}
//查找切片中是否存在某個數
func find(target int, list []int) bool {
    for _, num := range list {
        if num == target {
            return true
        }
    }
    return false
}

func randInt(min int, max int) int {
    return min + rand.Intn(max - min)
}

/**
初始化方法
 */
func initMethod() {
    ints := generater(6, 8)

    Cx = make([]int, len(ints))
    CyList := []int{}
    for i := 0; i < len(ints); i++ {
        for j := 0; j < len(ints[0]); j++ {
            CyList = append(CyList, ints[i][j])
        }
    }
    CyList = RemoveRepByMap(CyList)
    Cy = make([]int, len(CyList))

    //fmt.Println(len(ints))
    //fmt.Println(CyList)
    sx = make([]bool, len(ints))
    sy = make([]bool, len(CyList))
    match = make([]int, len(CyList))
    //weight = append(weight, []int{3, 0, 4, 1})
    //weight = append(weight, []int{2, 1, 3, 1})
    //weight = append(weight, []int{0, 0, 5, 1})
    weight = make([][]int, 0)
    for i := 0; i < len(ints); i++ {
        weightlist := make([]int, len(CyList))
        for j := 0; j < len(CyList); j++ {
            if find(CyList[j], ints[i]) {
                weightlist[j] = CyList[j]
            } else {
                weightlist[j] = 0
            }
        }
        weight = append(weight, weightlist)
    }
    fmt.Println("weight:", weight)
}

// 經過map主鍵惟一的特性過濾重複元素
func RemoveRepByMap(slc []int) []int {
    result := []int{}
    tempMap := map[int]byte{}  // 存放不重複主鍵
    for _, e := range slc {
        l := len(tempMap)
        tempMap[e] = 0
        if len(tempMap) != l{  // 加入map後,map長度變化,則元素不重複
            result = append(result, e)
        }
    }
    return result
}

func main() {
    initMethod()
    
    sum := Kuhn_Munkras(true)
    fmt.Println("sum:", sum)

    fmt.Println("match:", match)
}
View Code

參考

https://blog.csdn.net/li13168690086/article/details/81557890

https://blog.csdn.net/x_y_q_/article/details/51927054

http://www.javashuo.com/article/p-ywqsdlra-gs.html

http://www.cnblogs.com/wenruo/p/5264235.html

相關文章
相關標籤/搜索