二分查找(上)

也叫折半查找算法

思考

假设我们有1000万个整数数据,每个数据占8个字节,如何设计数据结构和算法,快速判断某个整数是否出现在这1000万个数据中?占用内存最多不要超过100MB。

二分思想

如果从有1000个元素的有序数组找某一个元素,每次取中间(如果是偶数个就-1)的数,判断是大于、小于或等于就直接找到,这样就确定了目标元素的范围,在缩小后的范围中按这样的方式一直找下去,
1000/2=500/2=250/2=125-1 /2=62/2=31-1 /2=15-1 /2=7-1 /2=3-1 /2=1,
这样最多不超过10次就可以找到目标元素。
这就是二分思想。

O(logn)的时间复杂度

假设数据大小为n,每次查找后数据都会缩小为原来的一半,最坏情况下,知道查找区间被缩小为空才停止。

n, n/2, n/4, n/8, ... , n/2^k, ...

当 n/2^k=1 时,k的值就是总共缩小的次数。每一次缩小操作只涉及两个数据的大小比较,所以,经过了k次区间缩小操作,时间复杂度就是O(k)。
通过 n/2^k=1,可以得到 k = ㏒(2)n,所以时间复杂度为O(logn)。

这样即使数据量很大,实际 logn 也会非常小。某些时候甚至比 O(1) 还要快。我们在用 O(1) 表示常数时间复杂度的时候,实际上是省略了常数、系数和低阶,O(1) 也有可能是一个非常大的常亮值,比如 O(1000), O(10000),这样有时候就还没有 O(logn) 时间复杂度高。

递归与非递归简单实现

非递归
def binary_search(array, value):
    low = 0
    high = len(array) - 1
    while low <= high:
        mid = low + (high-low)>>1
        if array[mid] == value:
            return mid
        elif array[mid] < value:
            low = mid + 1
        else:
            high = mid - 1

    return -1
递归
def binary_search(array, low, high, value):
    if low > high: 
        return -1
    mid = low + ((high-low)>>1)
    if array[mid] == value:
        return mid
    elif array[mid] < value:
        return bsearch(array, mid+1, high, value)
    else:
        return bsearch(array, low, mid-1, value)

二分查找的局限性

  1. 数据必须是顺序表结构,像数组这样支持随机存储,可以通过下标直接访问元素,如果是链表这样的,每次通过下标获取元素的时间复杂度都是O(n)效率就很低了。
  2. 其次必须是有序数据,如果没有序,先进行排序最低时间复杂度是O(nlogn),如果数据频繁的插入、删除,每次都要先排序的话,成本也不低,所以最好数据没有频繁的插入、删除,是一组景静态数据。
  3. 数据量太小也没有意义,直接顺序遍历就够了。
  4. 数据量太大也不行,应为要读取到数组中,而数组这样的数据结构为了支持随机访问,需要内存空间连续。如果我们有1GB的数据的话,那么读取到内存中需要1GB的内存空间,而且必须是连续的,这就非常苛刻了。

总结

二分查找通过每次取中间元素对比,缩半区间,使得性能非常优秀。但是应用场景也特别有限。

来自 https://leejnull.github.io/2020/03/03/2020-03-03-01/

你可能感兴趣的:(二分查找(上))