快速从一个数组中查找一个元素。

Linear Search 线性查找

func linearSearch<T: Equatable>(_ a: [T], _ key: T) -> Int? {
    for i in 0 ..< a.count {
        if a[i] == key {
            return i
        }
    }
    return nil
}

线性查找在最坏情况:遍历了整个数组,但没有找到合适的元素。平均要遍历一半的元素性能为 O(n),而二分查找的效率为 O(log n),也就是说一个有 1,000,000 元素的数组只需要 20 步就可以找到想要的元素 log_2(1,000,000) = 19.9

但是二分查找要求数组必须是排好序。

二分查找步骤:

  1. 将数组分为两半。
  2. 判断想要找的元素是在左边数组还是右边,这也是数组需要排好顺序的原因。
  3. 如果要的元素在左边,就将左边的数组分成更小的两部分,并判断要的元素在哪部分。
  4. 重复步骤直到找到想要的元素。如果数组不能进一步查分,就说明要找的元素不在数组中。

divide-and-conquer

The code

func binarySearch<T: Comparable>(_ a: [T], key: T, range: Range<Int>) -> Int? {
    if range.lowerBound >= range.upperBound {
        // If we get here, then the search key is not present in the array.
        return nil

    } else {
        // Calculate where to split the array.
        let midIndex = range.lowerBound + (range.upperBound - range.lowerBound) / 2

        // Is the search key in the left half?
        if a[midIndex] > key {
            return binarySearch(a, key: key, range: range.lowerBound ..< midIndex)

        // Is the search key in the right half?
        // 这里 + 1 的原因是排除 midIndex 中间值
        } else if a[midIndex] < key {
            return binarySearch(a, key: key, range: midIndex + 1 ..< range.upperBound)

        // If we get here, then we've found the search key!
        } else {
            return midIndex
        }
    }
}
// 19 numbers
let numbers = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67]

// 0 ..< numbers.count 覆盖所有范围
binarySearch(numbers, key: 43, range: 0 ..< numbers.count)  // gives 13

二分查找是将数组分为两个,但是我们不需要正真的创建两个新数组。取而代之,我们使用 Swift Range 对象跟踪这些拆分。左闭右开。upperBound 总是比最后一个元素的索引多一。

midIndex = (lowerBound + upperBound) / 2

如果这样写将存在一个 bug,就是当这两值非常大时,将存在一个越界的问题。

Iterative vs recursive 迭代 vs 递归

二分查找本质是递归。

使用迭代的方式实现:

func binarySearch(_ a: [Int], key: Int) -> Int? {
    var lowerBound = 0
    var upperBound = a.count
    while lowerBound < upperBound {
        // 这行僵硬了 没有必要的
        var range = lowerBound ..< upperBound
        let midIndex = range.lowerBound + (range.upperBound - range.lowerBound) / 2
        let midValue = a[midIndex]
        if key < midValue {
            upperBound = midIndex
        } else if key > midValue {
            lowerBound = midIndex + 1
        } else {
            return midIndex
        }
    }
    return nil
}

The end

查找前数组一定要先排序吗?这取决于排序花费的时间,有时候:数组排序加二分查找比线性搜索还要慢。二分查找的优势在于一次排序后多次查找。

文章代码:GitHub - imzyf/data-structure-and-algorithm/004-Binary Search/

References