二分搜索算法(转载自知乎大佬)


二分搜索算法及应用

这里是本算法的完全参考链接

什么是二分搜索算法

二分搜索(binary search)也称折半搜索(half-interval search)、对数搜索(logarithmic search),是一种在有序数组中查找某一特定元素的搜索算法。

搜索过程从数组的中间元素开始,如果中间元素正好是要查找的元素,则搜索过程结束;如果某一特定元素大于或者小于中间元素,则在数组大于或小于中间元素的那一半中查找,而且跟开始一样从中间元素开始比较。如果在某一步骤数组为空,则代表找不到。这种搜索算法每一次比较都使搜索范围缩小一半。

==学习算法的三个步骤==

  1. 问题的形式化描述(问题是什么?输入输出是什么?)
  2. 算法设计
  3. 算法分析(正确性及复杂度分析)

二分搜索算法的各种情况

我们接下来讲的是==广义二分搜索算法==,我们讲这个的目的是想要尽可能用一种写法来基本普适各种情况,以便我们以后能够正确地记住并根据具体情况修改及使用该算法。

  • 求下界,即找满足x >= valuex > value条件的**最小x**的位置
  • 上界,即找满足x <= valuex < value条件的**最大x**的位置

如果继续细分,还可以分为对非降序和非升序的数组进行求解。

十六种?

以寻找非降序数组内第一个不小于value值的最小x位置为例来说明算法:

二分搜索算法的实现思想

伪代码:

//返回[first,last)内第一个不小于value值的位置
//array是非降序数组
BINARYSEARCH(array,first,last,value){
    while (first < last):     //搜索区间[first, last)不为空
        mid = first + (last - first) /2  //防滥出
        if (array[mid] < value) 
            first = mid + 1
        else    //array[mid] >= value
            last = mid
    return first     //last也行,因为[first, last)为空的时候它们重合
}

二分搜索算法相关分析

正确性证明

1、为什么使用左闭右开的参数

一切始于图灵奖得主==Dijkstra==(据说他不用纸笔只花了二十分钟就发明了著名的==迪杰斯特拉算法==),其大致思路如下:

假设有一个长度为4的数组,用整数边界的区间表示它的下标0,1,2,3,有四种写法:

a)    0≤i<4
b)    -1<i≤3
c)    0≤i≤3
d)    -1<i<4

显然左边不闭的话-1 太丑了,所以只考虑a)和c),然后怎么取舍呢?
现在假设该数组长度慢慢减小到0,右边界减小,此时它的index范围是空集$\emptyset$,整数边界的区
间的四种写法变成了:

a)    0≤i<0
b)    -1<i≤-1
c)    0≤i≤-1
d)    -1<i<0

现在只有a)不会出现负数了。看来左闭右开的a)是唯一种不反人类的写法!它还有一些个
好处:
1.区间两端值的差,如[0, 4) 中的4-0=4,正好是区间或数组的长度
2.刚好相邻的区间,如[0, 2)和[2, 4),中间值(即2)相同,一眼就可以看出来

2、如何取中点

如果用mid = (first + last)/2计算中点,在C++、JAVA等语言里可能会出现溢出现象。

解决方案就是将其改写成mid = first + (last - first)/2的写法,其中lenth = last - first为区间长度。

此外,中点的选择并不唯一,

1、上位中位数:upperMid = first + length/2

2、下位中位数:lowerMid = first + (length - 1)/2

image-20200520082146564

由于下位中位数还需要减一,用起来怪麻烦,所以我们选择使用上位中位数。

3、while里的循环不变量 - loop invariants

代码进入while循环里面时一定成立的条件:

  1. 搜索范围[first, last)不为空,即first < last
  2. 搜索范围左侧所有元素都小于value(若存在),搜索范围右侧所有元素都大于等于value(若存在)。
4、 本算法中,当[first,last)为空的时候first与last重合

综合1、2、3,当[first,last)为空的时候first与last重合且该值即为第一个不小于value值的位置

为什么:因为算法的最终一定会收敛到区间first=last的结束,在该最终步的上一步的while循环里,根据循环不变量,我们再分析该区间长度为1的区间缩小策略,就可以得出结论。

image-20200520091157133

最好,我们需要说明,该算法其实没必要必须是非降序,只需要value值两侧的部分是数组的一个划分就可以满足循环不变量,说人话就是我们要找的元素的左侧的元素都小于value值,右侧元素都大于等于value值即可。

5、举例
6、 其他情况算法变化

==这个广义的二分算法,只要value是这个数组的一个划分就可以了。==

==对于非降序数组==

  1. 找满足x > value条件的**最小x**的位置

根据以上分析,将if条件改为小于等于即可

  1. 找满足x <= value条件的**最大x**的位置

情况1得到的位置再减一即可

  1. 找满足x < value条件的**最大x**的位置

我们讨论的寻找非降序数组内第一个不小于value值的位置的情况得到的位置再减一即可。

……

==对于非增序数组==

  1. 通过对while循环最后一步的分析修改红框里箭头处的三个条件

image-20200520210132672

  1. 将数组反序后求非降序数组的镜像位置之后再求出原问题的位置

分析一个例子:

求非增序数组x >= value的x的最大位置(给定value为3)

image-20200520202652115

分析while循环的最后一步,考虑如何设置if条件和返回值:

image-20200520210812057

由分析可得相应算法:

//返回[first,last)内第一个不小于value值的位置
//array是非降序数组
BINARYSEARCH(array,first,last,value){
    while (first < last):     //搜索区间[first, last)不为空
        mid = first + (last - first) /2  //防滥出
        if (array[mid] >= value) 
            first = mid + 1
        else    //array[mid] < value
            last = mid
    return first-1     //last-1也行,因为[first, last)为空的时候它们重合
}

……

时间复杂度分析

O(logn)

二分搜索算法的相关应用

一道力扣简单题,二分查找


文章作者: 青衫读书人
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 青衫读书人 !
评论
  目录