本文是對 Swift Algorithm Club 翻譯的一篇文章。git
Swift Algorithm Club是 raywenderlich.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 = 0
和high = 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。
這是一個如何修改基本二分搜索以解決其它算法問題的示例。 固然,它須要先對數組進行排序。