幾種計算全排列的方法

基于golang實現(xiàn),有非并發(fā)實現(xiàn)和并發(fā)實現(xiàn)

遞歸

全排列問題创夜,比如打印1-9的共9個字母的全排列。先輸出1仙逻,然后是2-9的全排列驰吓,輸出2涧尿,然后1-9中去除2的全排列。于是很自然想到遞歸的方法檬贰。寫出偽代碼:


permutaion(prefix, set) {
    if set 為空
        print prefix

    for s in set:
        permuetaion(prefix+s, set - s)
}

go遞歸實現(xiàn)

func permutaionImpl(prefix []byte, s []byte, cur int) {
    if cur == len(s) {
        fmt.Println(prefix)
        return
    }

    for _, b := range s {
        exist := false
        for i := 0; i < cur; i++ {
            if prefix[i] == b {
                exist = true
                break
            }
        }

        if !exist {
            prefix[cur] = b
            permutaionImpl(prefix, s, cur+1)
        }

    }
}

func Permutation(s []byte) {
    //前綴slice
    p := make([]byte, len(s), len(s))
    permutaionImpl(p, s, 0)
}

并發(fā)1

剛才實現(xiàn)的是go單線程執(zhí)行的姑廉,改成并發(fā)版本的, 參考rob pike講Go Concurrency Patterns,多個goroutine通過channel連接起來翁涤。goroutine1發(fā)送1給goroutine2, goroutine2發(fā)送12給goroutine3桥言,goroutine3發(fā)送123給goroutine4, 以此類推葵礼。

func prefixIncrement(in []byte, s []byte, next chan []byte) {
    for _, c := range s {
        exist := false
        for _, e := range in {
            if e == c {
                exist = true
                break
            }
        }
        if exist {
            continue
        }

        temp := make([]byte, 0)
        temp = append(temp, in...)
        temp = append(temp, c)
        next <- temp
    }
}

func permutaionConImpl(req chan []byte, out chan []byte, s []byte) {
    go func() {
        //遞歸退出條件: len(v) == len(s)-1
        v, ok := <-req
        if !ok {
            return
        }

        next := out
        if len(v) != len(s)-1 {
            next = make(chan []byte)
            permutaionConImpl(next, out, s)
        }

        prefixIncrement(v, s, next)
        for in := range req {
            prefixIncrement(in, s, next)
        }
        close(next)
    }()
}

// PermutationConcurrency  并發(fā)計算全排列
func PermutationConcurrency(s []byte) {
    req, out := make(chan []byte), make(chan []byte)

    //開啟goroutine計算
    permutaionConImpl(req, out, s)

    over := make(chan struct{})
    
    //要開goroutine讀取out号阿,如果放在主函數(shù)中,會導(dǎo)致死鎖鸳粉。
    go func() {
        for res := range out {
            fmt.Println(res)
        }
        close(over)
    }()

    for _, c := range s {
        sl := []byte{c}
        req <- sl
    }
    close(req)

    <-over

}

并發(fā)2

并發(fā)1中把每個排列的階段拆分到不同的goroutine扔涧, 從goroutine1開始每個goroutine越來越繁忙,最后一個goroutine要輸出n!個slice届谈,任務(wù)輕重很不均衡扰柠。于是也可以考慮讓每個goroutine做同樣的事情,比如下面的實現(xiàn)

func PermutationConcurrencyVertical(s []byte) {
    var sg sync.WaitGroup
    for _, c := range s {
        sg.Add(1)

        //要傳參數(shù)進(jìn)去疼约,避免只取到最后一個值
        go func(k byte) {
            p := make([]byte, len(s), len(s))
            p[0] = k
            permutaionImpl(p, s, 1)
            defer sg.Done()
        }(c)
    }

    sg.Wait()
}

并發(fā)3

如果能提前知道要開幾個goroutine卤档,那就可以不用遞歸的方式創(chuàng)建goroutine了,代碼邏輯會更清晰易懂程剥。

func permutaionCal(left chan []byte, right chan []byte, s []byte) {
    for v := range left {
        prefixIncrement(v, s, right)
    }
    close(right)
}

func PermutaionChain(s []byte) {
    leftmost := make(chan []byte)
    left := leftmost
    right := leftmost

    for i := 0; i < len(s)-1; i++ {
        right = make(chan []byte)
        go permutaionCal(left, right, s)
        left = right
    }

    go func(c chan []byte) {
        for _, v := range s {
            c <- []byte{v}
        }
        close(c)
    }(leftmost)

    for v := range right {
        fmt.Println(v)
    }
}

對比

benchmark 4個byte的全排列:
遞歸 5000 308919 ns/op 768 B/op 24 allocs/op
并發(fā)1 5000 382600 ns/op 1774 B/op 93 allocs/op
并發(fā)3 5000 310740 ns/op 1675 B/op 92 allocs/op

benchmark 7個byte的全排列:
遞歸 20 62281150 ns/op 161372 B/op 5040 allocs/op
并發(fā)1 20 71853908 ns/op 275384 B/op 18781 allocs/op
并發(fā)3 20 84848114 ns/op 275663 B/op 18780 allocs/op

遞歸版本最快劝枣,分配內(nèi)存最少,應(yīng)為中間結(jié)果都是寫入一個前綴slice的织鲸,通過cur下標(biāo)來標(biāo)記slice的結(jié)尾舔腾。并發(fā)版本要在不同的goroutine之間傳遞slice,分配的內(nèi)存相對多一些搂擦。在這個例子中稳诚,并發(fā)沒有表現(xiàn)出比單線程更快,原因可能是每個goroutine的計算過程較短瀑踢,不能充分發(fā)揮出多核的優(yōu)勢扳还,反而將時間浪費在了goroutine之間的消息傳遞上。

代碼地址

https://github.com/amither/go-slice/tree/master/permutation

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末橱夭,一起剝皮案震驚了整個濱河市氨距,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌棘劣,老刑警劉巖俏让,帶你破解...
    沈念sama閱讀 216,651評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡首昔,警方通過查閱死者的電腦和手機(jī)寡喝,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來勒奇,“玉大人拘荡,你說我怎么就攤上這事∏肆辏” “怎么了珊皿?”我有些...
    開封第一講書人閱讀 162,931評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長巨税。 經(jīng)常有香客問我蟋定,道長,這世上最難降的妖魔是什么草添? 我笑而不...
    開封第一講書人閱讀 58,218評論 1 292
  • 正文 為了忘掉前任驶兜,我火速辦了婚禮,結(jié)果婚禮上远寸,老公的妹妹穿的比我還像新娘抄淑。我一直安慰自己,他們只是感情好驰后,可當(dāng)我...
    茶點故事閱讀 67,234評論 6 388
  • 文/花漫 我一把揭開白布肆资。 她就那樣靜靜地躺著,像睡著了一般灶芝。 火紅的嫁衣襯著肌膚如雪郑原。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,198評論 1 299
  • 那天夜涕,我揣著相機(jī)與錄音犯犁,去河邊找鬼。 笑死女器,一個胖子當(dāng)著我的面吹牛酸役,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播驾胆,決...
    沈念sama閱讀 40,084評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼涣澡,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了俏拱?” 一聲冷哼從身側(cè)響起暑塑,我...
    開封第一講書人閱讀 38,926評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎锅必,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,341評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡搞隐,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,563評論 2 333
  • 正文 我和宋清朗相戀三年驹愚,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片劣纲。...
    茶點故事閱讀 39,731評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡逢捺,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出癞季,到底是詐尸還是另有隱情劫瞳,我是刑警寧澤,帶...
    沈念sama閱讀 35,430評論 5 343
  • 正文 年R本政府宣布绷柒,位于F島的核電站志于,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏废睦。R本人自食惡果不足惜伺绽,卻給世界環(huán)境...
    茶點故事閱讀 41,036評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望嗜湃。 院中可真熱鬧奈应,春花似錦、人聲如沸购披。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽刚陡。三九已至程梦,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間橘荠,已是汗流浹背屿附。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留哥童,地道東北人挺份。 一個月前我還...
    沈念sama閱讀 47,743評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像贮懈,于是被迫代替她去往敵國和親匀泊。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,629評論 2 354

推薦閱讀更多精彩內(nèi)容