Go語言性能優化-兩數之和算法性能研究

好多人都在刷leetcode,今天我也註冊了一個玩玩,發現裏面好多都是算法題,好吧,畢業十來年,學的那點可憐的數學知識,全都還給學校了。好了閒話少說,言歸正傳,讓咱們看看今天在裏面我嘗試的第一道題,有點意思, 不僅是單純的算法,還有數據和是否適合的問題。html

承題

點開題庫,看了第一題,咱們看看這道題:golang

給定一個整數數組和一個目標值,找出數組中和爲目標值的兩個數。
你能夠假設每一個輸入只對應一種答案,且一樣的元素不能被重複利用。
示例:
給定 nums = [2, 7, 11, 15], target = 9
由於 nums[0] + nums[1] = 2 + 7 = 9
因此返回 [0, 1]

用了這麼多文字描述,其實總結起來就是:數組裏那兩個數想加等於目標值,找出來這兩個數的索引。算法

題是不難,leetcode給出了兩種算法:數組

  1. 暴力法,循環迭代找出來,時間複雜度O(n^2),空間複雜度是O(1)
  2. 一遍哈希表,時間和空間複雜度都是O(n)

暴力法

我用Go語言(golang)實現了暴力法,下面看看代碼。app

func TwoSum1(nums []int, target int) []int {

    n:=len(nums)

    for i,v:=range nums {
        for j:=i+1;j<n;j++ {
            if v+nums[j] == target {
                return []int{i,j}
            }
        }
    }

    return nil
}

兩層循環嵌套,很黃很暴力。這個算法是若是運氣好了,循環兩遍就出來結果了,若是運氣很差,要找的元素正好在最後兩位,那麼真的是O(n^2)了。性能

哈希法

Go語言裏有map類型,這個默認的Hash實現,基於這個咱們用Golang實現哈希法。測試

func TwoSum2(nums []int, target int) []int {

    m:=make(map[int]int,len(nums))

    for i,v:=range nums {
        sub:=target-v
        if j,ok:=m[sub];ok{
            return []int{j,i}
        }else{
            m[v]=i
        }
    }

    return nil
}

這個算法中規中矩,時間和空間複雜度都是O(n),若是運氣好,數組內重複的元素多,空間佔用還會再少一些。網站

測試

寫好了算法,還要測試一下,要保證結果是正確的,不能搞烏龍。spa

package main

import (
    "flysnow.org/hello/lib"
    "fmt"
)

func main(){
    r1:=lib.TwoSum1([]int{2, 7, 11, 15},9)
    fmt.Println(r1)
    r2:=lib.TwoSum2([]int{2, 7, 11, 15},9)
    fmt.Println(r2)
}

運行輸出:code

[0 1]
[0 1]

和指望的結果同樣,說明咱們的算法沒有問題。

性能指望

這兩種算法,leetcode也給了空間和時間複雜度,從咱們本身的代碼實現分析看,也是第二種哈希法要比暴力法好的多,真實的狀況真的是這樣嗎?咱們用Go語言的基準測試(Benchmark),測試一下。

關於基準測試(Benchmark)能夠參考 Go語言實戰筆記(二十二)| Go 基準測試 ,這裏再也不詳述。

func BenchmarkTwoSum1(b *testing.B) {
    b.ResetTimer()
    for i:=0;i<b.N;i++{
        TwoSum1([]int{2, 7, 11, 15},9)
    }
}

func BenchmarkTwoSum2(b *testing.B) {
    b.ResetTimer()
    for i:=0;i<b.N;i++{
        TwoSum2([]int{2, 7, 11, 15},9)
    }
}

運行➜ lib go test -bench=. -benchmem -run=none命令查看Golang Benchmark 測試的結果。

pkg: flysnow.org/hello/lib
BenchmarkTwoSum1-8      50000000    26.9 ns/op  16 B/op   1 allocs/op
BenchmarkTwoSum2-8      20000000    73.9 ns/op  16 B/op   1 allocs/op

我用的測試用例,直接用題中給的,咱們發如今這種測試用例的狀況下,咱們不看好的暴力法,反而性能比哈希法高出2.5倍,好像和咱們想的有點不同。

數組位置調整

咱們看測試的數組,答案就在數組的前兩位,這對於暴力法來講,的確有優點,咱們把這兩個答案二、7調整到數組的末尾,也就是測試數組爲{11, 15, 2, 7},看看測試結果。

BenchmarkTwoSum1-8      50000000    29.1 ns/op  16 B/op     1 allocs/op
BenchmarkTwoSum2-8      10000000    140 ns/op   16 B/op     1 allocs/op

好吧,這一調,暴力法仍是一如既往的堅挺,可是哈希法的性能降低了1倍,把哈希法給調死了。

擴大數組個數

咱們發現,數組個數少的時候,暴力法是佔有優點的,性能是最好的。下面咱們調整下數組的個數,再進行測試。

const N  = 10

func BenchmarkTwoSum1(b *testing.B) {
    nums:=[]int{}
    for i:=0;i<N;i++{
        nums=append(nums,rand.Int())
    }
    nums=append( nums,7,2)

    b.ResetTimer()
    for i:=0;i<b.N;i++{
        TwoSum1(nums,9)
    }
}

func BenchmarkTwoSum2(b *testing.B) {
    nums:=[]int{}
    for i:=0;i<N;i++{
        nums=append(nums,rand.Int())
    }
    nums=append( nums,7,2)

    b.ResetTimer()
    for i:=0;i<b.N;i++{
        TwoSum2(nums,9)
    }
}

仔細看上面的代碼,我採用自動隨機生成數組元素的方式,可是爲了保證答案,數組的最後兩位仍是7,2
先測試下數組大小爲10個的狀況。

BenchmarkTwoSum1-8      20000000    73.3 ns/op  16 B/op     1 allocs/op
BenchmarkTwoSum2-8       2000000    660 ns/op   318 B/op    2 allocs/op

10個元素是,暴力法比哈希法的性能快10倍。

繼續調整數組大小爲50,直接修改常量N就行了,測試50個元素的狀況。

BenchmarkTwoSum1-8       2000000    984 ns/op   16 B/op     1 allocs/op
BenchmarkTwoSum2-8        500000    3200 ns/op  1451 B/op   6 allocs/op

隨着數組大小的增長,哈希法的優點開始凸現,50個數組元素時,相差只有4倍。

從不斷的增長數組的大小開始,在個人電腦上,當數組的大小爲300時,二者打平,性能同樣。

當數組大小爲1000時,哈希法的性能已是暴力法的4倍,反過來了。

當數組大小爲10000時,哈希法的性能已是暴力法的20倍,測試數據以下:

BenchmarkTwoSum1-8      100     21685955 ns/op      16 B/op         1 allocs/op
BenchmarkTwoSum2-8      2000    641821 ns/op        322237 B/op     12 allocs/op

從基準測試的數據來看,數組越大,每次操做耗費的時間越長,可是暴力法的耗時增加太大,致使性能低下。

從數據中也能夠看出,哈希法是空間換時間的方式,內存佔用和分配都比較大。

小結

從這測試和性能分析來看,不存在最優的算法,只存在最合適的。

若是你的數組元素比較少,那麼暴力算法是更適合你的。
若是數組元素很是多,那麼採用哈希算法就是一個比較好的選擇了。

因此,根據咱們本身系統的實際狀況,來選擇合適的算法,好比動態判斷數組的大小,採用不一樣的算法,達到最大的性能。

本文爲原創文章,轉載註明出處,「總有爛人抓取文章的時候還去掉個人原創說明」歡迎掃碼關注公衆號 flysnow_org或者網站 http://www.flysnow.org/,第一時間看後續精彩文章。「防爛人備註 *……&¥」以爲好的話,順手分享到朋友圈吧,感謝支持。

掃碼關注

相關文章
相關標籤/搜索