【譯】Swift算法俱樂部-統計出現次數

本文是對 Swift Algorithm Club 翻譯的一篇文章。git

Swift Algorithm Clubraywenderlich.com網站出品的用Swift實現算法和數據結構的開源項目,目前在GitHub上有18000+⭐️,我初略統計了一下,大概有一百左右個的算法和數據結構,基本上常見的都包含了,是iOSer學習算法和數據結構不錯的資源。github

🐙andyRon/swift-algorithm-club-cn是我對Swift Algorithm Club,邊學習邊翻譯的項目。因爲能力有限,如發現錯誤或翻譯不妥,請指正,歡迎pull request。也歡迎有興趣、有時間的小夥伴一塊兒參與翻譯和學習🤓。固然也歡迎加⭐️,🤩🤩🤩🤨🤪。算法

本文的翻譯原文和代碼能夠查看🐙swift-algorithm-club-cn/Count Occurrencesswift


目標:計算某個值在數組中出現的次數。數組

顯而易見的方法是從數組的開頭直到結束的線性搜索,計算您遇到該值的次數。 這是一個 O(n) 算法。數據結構

可是,若是數組已經排過序的,則能夠經過使用修改二分搜索來更快的完成這個任務,時間複雜度爲O(logn)函數

假設咱們有如下數組:學習

[ 0, 1, 1, 3, 3, 3, 3, 6, 8, 10, 11, 11 ]
複製代碼

若是咱們想知道值3出現的次數,咱們能夠進行常規二分搜索。 這能夠得到四個3索引中的一個:測試

[ 0, 1, 1, 3, 3, 3, 3, 6, 8, 10, 11, 11 ]
           *  *  *  *
複製代碼

可是,這仍然沒有告訴你有多少其它的3。 要找到那些其它的3,你仍然須要在左邊進行線性搜索,在右邊進行線性搜索。 在大多數狀況下,這將是足夠快的,但在最壞的狀況下 —— 當這個數組中除了以前的一個3以外就沒有其它3了 —— 這樣時間複雜度依然是O(n)網站

一個訣竅是使用兩個二分搜索,一個用於查找3開始(左邊界)的位置,另外一個用於查找3結束的位置(右邊界)。

代碼以下:

func countOccurrencesOfKey(_ key: Int, inArray a: [Int]) -> Int {
  func leftBoundary() -> Int {
    var low = 0
    var high = a.count
    while low < high {
      let midIndex = low + (high - low)/2
      if a[midIndex] < key {
        low = midIndex + 1
      } else {
        high = midIndex
      }
    }
    return low
  }

  func rightBoundary() -> Int {
    var low = 0
    var high = a.count
    while low < high {
      let midIndex = low + (high - low)/2
      if a[midIndex] > key {
        high = midIndex
      } else {
        low = midIndex + 1
      }
    }
    return low
  }

  return rightBoundary() - leftBoundary()
}
複製代碼

請注意,輔助函數leftBoundary()rightBoundary()二分搜索算法很是類似。最大的區別在於,當它們找到搜索鍵時,它們不會中止,而是繼續前進。

要測試此算法,將代碼複製到 playground,而後執行如下操做:

let a = [ 0, 1, 1, 3, 3, 3, 3, 6, 8, 10, 11, 11 ]

countOccurrencesOfKey(3, inArray: a)  // returns 4
複製代碼

請記住: 使用的數組,確保已經排序過!

來看看這個例子的過程。 該數組是:

[ 0, 1, 1, 3, 3, 3, 3, 6, 8, 10, 11, 11 ]
複製代碼

爲了找到左邊界,咱們從low = 0high = 12開始。 第一個中間索引是6

[ 0, 1, 1, 3, 3, 3, 3, 6, 8, 10, 11, 11 ]
                    *
複製代碼

經過常規二分搜索,你如今就能夠完成了,可是咱們不僅是查看是否出現了值3 —— 而是想要找到它第一次出現的位置。

因爲該算法遵循與二分搜索相同的原理,咱們如今忽略數組的右半部分並計算新的中間索引:

[ 0, 1, 1, 3, 3, 3 | x, x, x, x, x, x ]
           *
複製代碼

咱們再次找到了一個3,這是第一個。 但算法不知道,因此咱們再次拆分數組:

[ 0, 1, 1 | x, x, x | x, x, x, x, x, x ]
     *
複製代碼

還沒完, 再次拆分,但此次使用右半部分:

[ x, x | 1 | x, x, x | x, x, x, x, x, x ]
         *
複製代碼

數組不能再被拆分,這意味着左邊界在索引3處。

如今讓咱們從新開始,嘗試找到右邊界。 這很是類似,因此我將向您展現不一樣的步驟:

[ 0, 1, 1, 3, 3, 3, 3, 6, 8, 10, 11, 11 ]
                    *

[ x, x, x, x, x, x, x | 6, 8, 10, 11, 11 ]
                              *

[ x, x, x, x, x, x, x | 6, 8, | x, x, x ]
                           *

[ x, x, x, x, x, x, x | 6 | x | x, x, x ]
                        *
複製代碼

右邊界位於索引7處。兩個邊界之間的差別是7 - 3 = 4,所以數字3在此數組中出現四次。

每一個二分搜索須要4個步驟,因此總共這個算法須要8個步驟。 在僅有12個項的數組上得到的收益不是很大,可是數組越大,該算法的效率就越高。 對於具備1,000,000個項目的排序數組,只須要2 x 20 = 40個步驟來計算任何特定值的出現次數。

順便說一句,若是你要查找的值不在數組中,那麼rightBoundary()leftBoundary()返回相同的值,所以它們之間的差值爲0。

這是一個如何修改基本二分搜索以解決其它算法問題的示例。 固然,它須要先對數組進行排序。

做者:Matthijs Hollemans
翻譯:Andy Ron
校對:Andy Ron

相關文章
相關標籤/搜索