vlambda博客
学习文章列表

[算法总结]二分查找-binarySearch(1)

二分查找法作为一种常见的查找算法,基本上在所有的算法教程中都涉及到了。将常规的算法耗时从线性时间范畴(简单查找)提高到了对数时间范畴(二分查找)。
对于算法学习,一般理解都不难,难的在于灵活应用及举一反三。如果想做到这一点,需要学会抽象总结,理解算法背后的逻辑和原理,只是背题和刷题可能只是事倍功半的结果。

因此,从最基本的方面对二分查找做个总结,本篇是二分查找的第一篇,后续还会有比较深入的讨论,计划一共写三篇。


第一部分,二分查找的约束条件

输入:有序的元素列表
输出:返回需要查找的元素的在列表中的位置,或返回Null(未找到)
首先,输入的列表必须是有序,这里面还涉及到一个问题,列表是升序还是降序。升序或者降序的差别会对列表在查找过程中的收敛方向产生影响。
其次,输出的结果是所查找元素在列表中的位置,就意味着查找的终止条件是完成了列表查找的二分操作,给出了确定的结论。
第二部分,基本的二分查找
常规方式:
def binary_search(list, item): left = 0 right = len(list) - 1 while left <= right: mid = int(((right-left) >> 1) + left) guess = list[mid] if guess == item: return mid if guess > item: right = mid - 1 else: left = mid + 1 return None
代码本身不复杂,只是有几个地方要注意:
  1.  二分查找的循环条件是 left<=right
    1. 这里是以升序列表为例,left是小,right是大,当left=right的时候,二分查找还没有完成最后一个元素的比较,因此还需要循环最后一次;
  2. 找中间位置的方法不同于常规的做法
    1. (低位+高位)/ 2 取整
    2. (高位-低位)/2 + 低位 取整
    3. 而是用了二进制移位符 >> 来直接进行二进制的移位完成了除以2的操作,比用除法,性能要好一些。二进制数移动一位相当于十进制数除以2,这里要注意下>>的优先级,如果写成left+(right-left) >> 1的话,二分查找就会变成死循环了,大家可以自己亲自动手试试,找到其中的原因。
  3. 当二分位置上元素的数值和要查找的元素的数值不一致的时候,低位和高位的处理方法要根据是升序列表还是将序列表来决定。
第三部分:一个小小的变化
常规的二分查找如果没有找到需要查找的元素,则会返回Null,现在稍微修改下要求,如果没有找到需要查找的元素,则返回按顺序需要插入的位置。
这里我们的查找列表是左闭右闭区间,所以二分查找的完成的时候Left > Right,也就是说左侧游标已经到了右侧游标的右侧(有点绕口),而这一步的上一步一定是Left = Right,且mid=Left=Right这个时候我们分类讨论下:
  1. 如果目标值item>List[mid],则Left += 1,此时目标值应该插入到Left的位置,如insert(left, item);

  2. 如果目标值item<List[mid],则Right -= 1,此时目标值小于例表中Left位置的元素,目标职业需要插入到Left的位置,如insert(left, item)

所以,可以看出,如果查询不到目标值,则应该返回Left的位置。代码如下:

def binary_search(list, item): left = 0 right = len(list) - 1 while left <= right: mid = int(((right - left)>>1) +left) guess = list[mid] if guess < item: left = mid + 1 elif guess >= item: right = mid - 1    return left
对比这段代码,我把之前比较直观的三段if判断,修改为了两段if判断,返回的结果是一样的,大家可以想一下为什么这么做是可以的。