Binary Search(二分搜索)

转载请注明出处 leonchen1024.com/2018/08/14/…git

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

二分搜索有几个变体.特别是,分散层叠(fractional cascading)(将每一个数组里的值集合成一个数组,元素为11[0,3,2,0] 的形式,括号内的数字是该值在对应数组中应该返回的数字)提升了在多个数组中查找相同值的效率,高效的解决了一系列计算几何和其余领域的查找问题).指数查找(Exponential search)延伸了二分查找到一个没有边界的 list.binary search treeB-tree是基于 binary search 延伸的.算法

原理

搜索时从数组中间元素开始,若是中间元素正好是要查找的元素,则搜索过程结束;若是中间元素大于或者小于要查找的元素,则在数组中大于或者小于查找元素的一半中继续查找,重复这个过程直到找到这个元素,或者这一半的大小为空时则表明找不到.这样子每一次比较都使得搜索范围缩小一半.数据库

步骤

给定一个有序数组 A 是 A0,...,An-1并保证 A0<=...<=An-1,以及目标值 T.数组

  1. 令 L 为0,R 为 n-1.
  2. 若是 L>R 则搜索失败
  3. 令m(中间值元素索引)为最大的小于(L+R)/2的整数
  4. 若是 Am<T ,令 L=m+1并回到第2步;
  5. 若是 Am>T ,令 R=m-1并回到第2步;
  6. 当 Am=T,搜索结束;T 所在的索引位置为m.

变体微信

  1. 令 L 为0,R 为 n-1.
  2. 令 m(中间元素索引) 为上限,也就是最小的大于(L+R)/2的值.
  3. 若是 Am>T ,设置 R 为 m-1而且返回第2步
  4. 若是 Am<=T ,设置 L 为m 而且返回第2步.
  5. 直到 L=R ,搜索完成.这时候若是T=Am,返回 m,不然,搜索失败.

转载请注明出处 leonchen1024.com/2018/08/14/…数据结构

在 Am<=T 的时候,这个变体将 L 设置为 m 而不是 m+1.这个方式的比较是更快速的,由于它在每一个循环里省略了一次比较.可是平均就会多出来一次循环.在数组包含重复的元素的时候这个变体老是会返回最右侧的元素索引.好比 A 是[1,2,3,4,4,5,6,7]查找的对象是4,那么这个方法会返回 index 4,而不是 index 3.函数

大体匹配

因为有序数组的顺序性,能够将二分搜索扩展到大体匹配.能够用来计算赋值的排名(或称秩,比它更小的元素的数量),前趋(下一个最小元素),后继(下一个最大元素)以及最近邻.还可使用两个排名查询来执行范围查询.性能

  • 排名查询可使用调整后的二分搜索来进行.成功时返回m,失败时返回 L, 这样就等于返回了比目标值小的元素数目.
  • 前趋和后继可使用排名查询来进行.当知道目标值的排名,成功时前趋是排名位置的上一个元素,失败时则是排名位置的元素.它的后继是排名位置的后一个元素,或是前趋的下一个元素.目标值的最近领多是前趋或后继,取决于哪一个更接近目标值.
  • 范围查询,一旦知道范围两边的值的排名,那么大于边界最小值且小于边界最大值的元素排名就是他们的范围,是否包含边界值根据须要处理.

性能分析

时间复杂度 二分查找每次把搜索区域减小一半,时间复杂度为编码

O(log_2 n)

(n 是集合中元素的个数) 最差的状况是 遍历到最后一层,或者是没有找到该元素的时候,复杂度为 O(\lfloor log_2 n + 1 \rfloor) .

综合复杂度为 O(log_2 n)

分散层叠(fractional cascading) 能够提升在多数组中查询相同值的效率. k 是数组的数量,在每一个数组中查询目标值消耗 O(k log n) 的时间.分散层叠能够将它下降到 O(k+log n).

变体效率分析 相对于正常的二分搜索,它减小了每次循环的比对次数,可是它必须作完完整的循环,而不会在中间就获得答案.可是在 n 很大的状况下减小了对比次数的提高不可以抵消多余的循环的消耗.

转载请注明出处 leonchen1024.com/2018/08/14/…

空间复杂度 O(1).尾递归,能够改写为循环.

应用

查找数组中的元素,或用于插入排序.

二分搜索和其余的方案对比

使用二分搜索的有序数组在插入和删除操做效率很低,每一个操做消耗 O(n) 的时间.其余的数据结构提供了更高效的插入和删除,而且提供了一样高效的彻底匹配.然而,二分搜索适用于不少的搜索问题,只消耗 O(log n) 的时间.

Hashing

对于关联数组 (associative arrays),哈希表 (hash tables),他们是经过hash 函数将键映射到记录上的数据结构,一般状况下比在有序数组的状况下使用二分查找要更快.大部分的实现平均开销都是常量级的.然而, hashing 并不适用于模糊匹配,好比计算前趋,后继,以及最近的键,它在失败的查询状况下能给咱们的惟一信息就是目标在记录中不存在.二分查找是这种匹配的理想模式,消耗对数级别的时间.

Trees

二叉搜索树(binary search tree) 是一个基于二叉搜索原理的二叉树(binary tree)数据结构.树的记录按照顺序排列,而且每一个树里的每一个记录均可以使用相似二叉搜索的方法来搜索,平均耗费对数级的时间.插入和删除的平均时间也是对数级的.这会比有序数组消耗的线性时间要快,而且二叉树拥有全部有序数组能够执行的操做,包含范围和模糊查找.

然而二叉搜索一般状况下比二叉搜索树的搜索更有效率,由于二叉搜索树极可能会彻底不平衡,致使性能稍差.这一样适用于 平衡二叉搜索树( balanced binary search trees) , 它平衡了它本身的节点稍微向彻底平衡树靠拢.虽然不太可能,可是树有可能只有少数节点有两个子节点致使严重不平衡,这种状况下平均时间损耗和最差的状况差很少都是 O(n) .二叉搜索树比有序数组占用更多的空间.

二叉搜索树由于能够高效的在文件系统中结构化,因此他们能够在硬盘中进行快速搜索.B-tree 泛化了这种树结构的方法.B-tree 经常使用于组织长时间的存储好比数据库(databases)文件系统(filesystems).

Linear search

线性搜索( Linear Search)是一种简单的搜索算法,它查找每个记录直到找到目标值.线性搜索能够在 链表(linked list) 上使用,它的插入和删除会比在数组上要快.二分搜索比线性搜索要快除非数组很短.若是数组必须先被排序,这个消耗必须在搜索中平摊.对数组进行排序还能够进行有效的近似匹配和其余操做.

Set membership algorithms

一个和搜索相关的问题是集合成员(set membership).全部有关查找的算法,好比二分搜索,均可以用于集合成员.还有一些更适用于集合成员的算法,位数组(bit array)是最简单的一个,在键的范围是有限的时候很是有用.它很是快,是须要O(1)的时间.朱迪矩阵(Judy array)能够高效的处理64位键.

对于近似结果,布隆过滤器(Bloom filters)是另一个基于哈希的几率性数据结构,经过存储使用bit array 和多重 hash 函数编码的键集合. Bloom filters 在大多数状况下空间效率比bit arrays 要高而不会慢太多:使用了 k 重hash 函数,成员查找只须要 O(k) 的时间.然而, Bloom filters 有必定的误判性.

其余的数据结构

转载请注明出处 leonchen1024.com/2018/08/14/…

这里存在一些数据结构在某些状况下比在有序数组上使用二分搜索进行查找或其余的操做更加高效.好比,在van Emde Boas trees, fusion trees, 前缀树(tries), 和位数组 上进行查找,近似匹配,以及其余可用的操做能够比在有序数组上进行二分搜索更加的高效.然而,尽管这些操做能够比在无视键的状况下比有序数组上使用更高效,这样的数据结构一般是由于利用了某些键的属性(键一般是一些小整数),所以若是键缺少那些属性将会消耗更多的空间或时间.一些结构如朱迪矩阵,使用了多种方式的组合来保证效率和执行近似匹配的能力.

变体

Uniform binary search

Uniform binary search 不是存储下限和上限的边界值,而是中间元素的索引,和从此次循环的中间元素到下次循环的中间元素的变化.每一步的变化减小一半.好比,要搜索的数组是[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],中间元素是6.Uniform binary search 同时对左边和右边的子数组进行操做.在这个状况下,左边的子数组([1, 2, 3, 4, 5]) 的中间元素 3 而右边的子数组 ([7, 8, 9, 10, 11]) 的中间元素是 9.而后存储3 做为两个中间元素和 6 的差异.为了减小搜索的空间使用,算法同时加上或减去这个和中间元素的改变.这个算法的好处是能够将每次循环的索引的差异存储到一个表里,在某些系统里能够提升算法的性能.

Exponential search

指数查找(Exponential Search)将二分搜索拓展到无边界数组.它最开始寻找第一个索引是2的幂次方而且要比目标值大的元素的索引.而后,它将这个元素索引设置为上边界,而后开始二分搜索.指数查找消耗 \lfloor log_2 x =1 \rfloor 次循环 ,而后二分搜索消耗 \lfloor log_2 x \rfloor 次循环, x 是目标值的位置.指数查找适用于有界列表,在目标值接近数组开始的位置的时候比二分查找性能有所提升. 转载请注明出处 leonchen1024.com/2018/08/14/…

Interpolation search

内插搜索(Interpolation search)忽略了目标值的位置,计算数组的最低和最高元素的距离即数组的长度.这只有在数组元素是数字的时候才能使用.它适用于中间值不是最好的猜想选择的状况.好比,若是目标值接近数组的最高元素,最好是定位在数组的末端.若是数组的分布是均匀的或者接近均匀的,它消耗 O(log log n) 次比较.

实际上,内插搜索在数组元素较少的状况下是比二分搜索更慢的,由于内插搜索须要额外的计算.尽管它的时间复杂度增加是小于二分搜索的,只有在在大数组的状况下这个计算的损耗能够被弥补.

Fractional cascading

分散层叠(Fractional cascading) 能够提升在多个有序数组里查找相同的元素或近似匹配的效率,分别在每一个数组里查找总共须要 O(klogn)的时间, k 是数组的数量.分散层叠经过将每一个数组的信息按指定的方式存储起来将这个时间下降到 O(k+logn) .

转载请注明出处 leonchen1024.com/2018/08/14/…

它将每一个数组里的值集合成一个数组,元素为 11[0,3,2,0] 的形式,括号内的数字是该值在对应数组中应该返回的数字)提升了在多个数组中查找相同值的效率,高效的解决了一系列计算几何和其余领域的查找问题

分散层叠被发明的时候是为了高效的解决各类计算几何学(computational geometry) 问题,可是它一样适用于其余地方,例如 数据挖掘(data mining)互联网协议(Internet Protocal) 等.

实现时的问题

要注意中间值的取值方法,若是使用 (L+R)/2 当数组的元素数量很大的时候回形成计算溢出.因此要使用L+(R-L)/2.

示例

C 版本- 递归

int binary_search(const int arr[], int start , int end , int khey){
    if (start > end)
      return -1;

    int mid = start +(end - start)/2;   //直接平都可能会溢位,因此用此算法
    if (arr[mid] > khey)
        return binary_search(arr , start , mid - 1 , khey);
    else if (arr[mid] < khey)
        return binary_search(arr , mid + 1 , end , khey);
    else
        return mid;    //最后才检测相等的状况是由于大多数搜寻状况不是大于就是小于

}

复制代码

C 版本- while 循环

int binary_search(const int arr[], int start, int end, int khey){
    int result = -1;    //若是没有搜索到数据返回 -1

    int mid;
    while (start <= end){
      mid = start + (end - start)/2 ;    //直接平都可能会溢位,因此用此算法
      if (arr[mid] > khey)
          end = mid-1;
      else if (arr[mid] < khey)
          start = mid + 1;
      else{    //最后才检测相等的状况是由于大多数搜寻状况不是大于就是小于
          result = mid;
          break;
      }
    }

    return result;

}

复制代码

Python3 递归

def binary_search(arr, start, end, hkey):
    if start > end:
        return -1

    mid = start + (end - start) / 2
    if arr[mid] > hkey:
        return binary_search(arr, start , mid - 1,hkey)
    if arr[mid] < hkey:
        return binary_search(arr, mid + 1, end, hkey)
    return mid

复制代码

Python3 while 循环

def binary_search(arr, start, end, hkey):
    result = -1

    while start <= end:
        mid = start + (end - start) / 2
        if arr[mid] > hkey :
            end = mid - 1
        elif arr[mid] < hkey :
            start = mid + 1
        else :
            result = mid
            break

    return result

复制代码

Java 递归

public static int binarySearch(int[] arr, int start, int end, int hkey){
    if (start > end)
        return -1;

    int mid = start + (end - start)/2;    //防止溢位
    if (arr[mid] > hkey)
        return binarySearch(arr, start, mid - 1, hkey);
    if (arr[mid] < hkey)
        return binarySearch(arr, mid + 1, end, hkey);
    return mid;  

}

复制代码

Java while 循环

public static int binarySearch(int[] arr, int start, int end, int hkey){
    int result = -1;

    while (start <= end){
        int mid = start + (end - start)/2;    //防止溢位
        if (arr[mid] > hkey)
            end = mid - 1;
        else if (arr[mid] < hkey)
            start = mid + 1;
        else {
            result = mid ;  
            break;
        }
    }

    return result;

}

复制代码

References

en.wikipedia.org/wiki/Binary…

转载请注明出处 leonchen1024.com/2018/08/14/…

About Me

个人博客 leonchen1024.com

个人 GitHub github.com/LeonChen102…

微信公众号

wechat
相关文章
相关标签/搜索