【问题标题】:Binary search (bisection) in PythonPython中的二分搜索(二分法)
【发布时间】:2010-09-17 18:17:14
【问题描述】:

是否有一个库函数对列表/元组执行二进制搜索,如果找到则返回项目的位置,如果没有则返回“假”(-1、无等)?

我在bisect module 中找到了 bisect_left/right 函数,但即使项目不在列表中,它们仍会返回一个位置。这对于他们的预期用途来说非常好,但我只想知道一个项目是否在列表中(不想插入任何东西)。

我想过使用bisect_left,然后检查该位置的项目是否等于我正在搜索的项目,但这似乎很麻烦(而且我还需要检查数字是否可以大于最大的我列表中的号码)。如果有更好的方法我想知道。

编辑为了澄清我需要这个做什么:我知道字典非常适​​合这个,但我试图保持尽可能低的内存消耗。我的预期用途是一种双向查找表。我在表中有一个值列表,我需要能够根据它们的索引访问这些值。而且我还希望能够找到特定值的索引,或者如果该值不在列表中,则为 None。

为此使用字典是最快的方法,但会(大约)使内存需求增加一倍。

我在问这个问题时认为我可能忽略了 Python 库中的某些内容。看来我必须按照 Moe 的建议编写自己的代码。

【问题讨论】:

  • 你想要完成什么?如果值是唯一的,请考虑使用集合和“如果集合中的值:某物”。
  • 对于它的价值,“-1”被认为是真实的; “0”是假的。
  • 我提到 -1 是因为返回数组中搜索项的索引的函数已经可以返回 0,因此如果未找到该项,则返回 -1(类似于子字符串搜索)。跨度>
  • 如果你使用 numpy,np.searchsorted 很有用。 docs.scipy.org/doc/numpy/reference/generated/…

标签: python binary-search bisection


【解决方案1】:

bisect_left 找到第一个位置p,在该位置可以将元素插入给定的排序范围,同时保持排序顺序。如果x 存在于该范围内,那将是x 的位置。如果p 是结束位置,则找不到x。否则,我们可以测试一下x是否存在,看看是否找到了x

from bisect import bisect_left

def binary_search(a, x, lo=0, hi=None):
    if hi is None: hi = len(a)
    pos = bisect_left(a, x, lo, hi)                  # find insertion position
    return pos if pos != hi and a[pos] == x else -1  # don't walk off the end

【讨论】:

  • @volcano binsearch 通常也是如此。
  • 降序呢?
  • 注意:你可以使用pos < hi,来支持a=(),x=-1,lo=1,hi=0的大小写(结果应该是-1对于一个空的范围)。此外,hi=min(hi, len(a)) 支持a=(0,), x=0, lo=0, hi=2(结果应该是0,而不是IndexError),lo 类似。鉴于binary_search 在边缘附近很棘手,最好是明确的,例如,为不支持的 lo, hi 值引发 ValueError。
【解决方案2】:

为什么不查看 bisect_left/right 的代码并对其进行调整以适合您的目的。

像这样:

def binary_search(a, x, lo=0, hi=None):
    if hi is None:
        hi = len(a)
    while lo < hi:
        mid = (lo+hi)//2
        midval = a[mid]
        if midval < x:
            lo = mid+1
        elif midval > x: 
            hi = mid
        else:
            return mid
    return -1

【讨论】:

  • 我最初为此 +1 了,但现在我得出结论,这不是一件好事。如果按照这个答案,会造成大量的代码重复,而且众所周知,二分查找真的很简单。
  • 不应该是elif中的hi = mid - 1吗?
  • @Paweł:它们是两个等效的变体,取决于上限是包含还是排除。你可以把hi = mid改成hi = mid-1,把hi = len(a)改成hi = len(a)-1,把while lo &lt; hi:改成while lo &lt;= hi,同样是正确的
  • 为什么不做类似的事情: def binary_search(a, x, lo = 0, hi = None): i = bisect(a, x, lo, hi) 如果 a[i] = 返回 i = x else -1 抱歉格式化 - 不确定如何在评论区正确执行此操作
  • 你应该使用bisect.bisect_left() 而不是这个。
【解决方案3】:

这有点离题(因为 Moe 对 OP 的问题的回答似乎很完整),但可能值得从头到尾查看整个过程的复杂性。如果您将事物存储在排序列表中(这是二进制搜索会有所帮助的地方),然后只是检查是否存在,那么您正在招致(最坏的情况,除非指定):

排序列表

  • O(n log n) 初始创建列表(如果它是未排序的数据。O(n),如果它是排序的)
  • O(log n) 次查找(这是二分查找部分)
  • O(n) 插入/删除(可能是 O(1) 或 O(log n) 平均情况,具体取决于您的模式)

而使用set(),您正在招致

  • O(n) 创建
  • O(1) 查找
  • O(1) 插入/删除

排序列表真正得到的东西是“下一个”、“上一个”和“范围”(包括插入或删除范围),它们是 O(1) 或 O(|range|),给定一个起始索引.如果您不经常使用这些类型的操作,那么存储为集合和排序以显示可能会更好。 set() 在 python 中几乎没有额外的开销。

【讨论】:

  • 排序列表还有另外一件事。 O(n) 有序遍历。使用 O(n log n) 的集合,您最终必须将对数据的引用复制到列表中。
  • 真的!感谢您扩展我所说的范围搜索的含义。 Fwiw,完全遍历与 min,max 之间的范围查询相同,即 O(k) 其中 k = n :)
  • 有重复的列表怎么样?
【解决方案4】:

值得一提的是,bisect 文档现在提供了搜索示例: http://docs.python.org/library/bisect.html#searching-sorted-lists

(引发 ValueError 而不是返回 -1 或 None 更符合 Python 风格 - 例如 list.index() 会这样做。当然,您可以根据需要调整示例。)

【讨论】:

    【解决方案5】:

    最简单的方法是使用bisect 并检查一个位置以查看该项目是否存在:

    def binary_search(a,x,lo=0,hi=-1):
        i = bisect(a,x,lo,hi)
        if i == 0:
            return -1
        elif a[i-1] == x:
            return i-1
        else:
            return -1
    

    【讨论】:

    • 很好,但是如果你不传入“hi”值,代码就会出错。我会这样写:“def binary_search(a,x,lo=0,hi=None): from bisect import bisect i = bisect(a,x,lo,hi or len(a)) return (i- 1 if a[i-1] == x else -1)" 并像这样测试它:" for i in range(1, 20): a = list(range(i)) for aa in a: j = binary_search (a, aa) 如果 j != aa: 打印 i, aa, j"
    【解决方案6】:

    这是正确的手册:

    http://docs.python.org/2/library/bisect.html

    8.5.1。搜索排序列表

    上面的 bisect() 函数对于查找插入点很有用,但对于常见的搜索任务可能会很棘手或尴尬。以下五个函数展示了如何将它们转换为排序列表的标准查找:

    def index(a, x):
        'Locate the leftmost value exactly equal to x'
        i = bisect_left(a, x)
        if i != len(a) and a[i] == x:
            return i
        raise ValueError
    

    所以稍微修改一下你的代码应该是:

    def index(a, x):
        'Locate the leftmost value exactly equal to x'
        i = bisect_left(a, x)
        if i != len(a) and a[i] == x:
            return i
        return -1
    

    【讨论】:

      【解决方案7】:

      这个是:

      • 非递归(这使得它比大多数递归方法内存效率更高
      • 实际上工作
      • 速度很快,因为它在没有任何不必要的 if 和条件的情况下运行
      • 基于数学断言,即 (low + high)/2 的下限总是小于 high 其中 low 是下限,high 是上限。

      def binsearch(t, key, low = 0, high = len(t) - 1):
          # bisecting the range
          while low < high:
              mid = (low + high)//2
              if t[mid] < key:
                  low = mid + 1
              else:
                  high = mid
          # at this point 'low' should point at the place
          # where the value of 'key' is possibly stored.
          return low if t[low] == key else -1
      

      【讨论】:

        【解决方案8】:

        我同意@DaveAbrahams's answer 使用 bisect 模块是正确的方法。他在回答中没有提到一个重要的细节。

        来自docsbisect.bisect_left(a, x, lo=0, hi=len(a))

        二等分模块不需要提前预先计算搜索数组。您可以将端点呈现给bisect.bisect_left,而不是使用0len(a) 的默认值。

        对于我的使用来说更重要的是,寻找一个值 X 以使给定函数的误差最小化。为此,我需要一种方法来让 bisect_left 的算法调用我的计算。这真的很简单。

        只需提供一个将__getitem__ 定义为a 的对象

        例如,我们可以使用 bisect 算法找到任意精度的平方根!

        import bisect
        
        class sqrt_array(object):
            def __init__(self, digits):
                self.precision = float(10**(digits))
            def __getitem__(self, key):
                return (key/self.precision)**2.0
        
        sa = sqrt_array(4)
        
        # "search" in the range of 0 to 10 with a "precision" of 0.0001
        index = bisect.bisect_left(sa, 7, 0, 10*10**4)
        print 7**0.5
        print index/(10**4.0)
        

        【讨论】:

        • 这不干净。为此使用scipy.optimize
        【解决方案9】:

        如果您只想查看它是否存在,请尝试将列表转换为字典:

        # Generate a list
        l = [n*n for n in range(1000)]
        
        # Convert to dict - doesn't matter what you map values to
        d = dict((x, 1) for x in l)
        
        count = 0
        for n in range(1000000):
            # Compare with "if n in l"
            if n in d:
                count += 1
        

        在我的机器上,“if n in l”耗时 37 秒,而“if n in d”耗时 0.4 秒。

        【讨论】:

        • 这并不总是一个好的选择,原因有两个:1) dicts/sets 占用更多内存。 2)如果他的列表中没有太多,二分查找可能会更快。 3) 将列表转换为字典是 O(n) 操作,而二分查找是 O(log n)。
        • 作为仅供参考,与 python 列表相比,python 中的“设置”开销非常低。而且它们的查找速度非常快。二进制搜索真正擅长的地方是查找范围。
        • 转换列表可能是 O(n),但对列表中的数据进行排序(在二进制搜索之前必须这样做)会更糟。数据来自哪里,您可以随时将其插入字典中。我同意内存可能是个问题。
        【解决方案10】:

        Dave Abrahams 的解决方案很好。虽然我会做的很简约:

        def binary_search(L, x):
            i = bisect.bisect_left(L, x)
            if i == len(L) or L[i] != x:
                return -1
            return i
        

        【讨论】:

          【解决方案11】:

          虽然 Python 中没有明确的二进制搜索算法,但有一个模块 - bisect - 旨在使用二进制搜索在排序列表中查找元素的插入点。这可以被“欺骗”执行二进制搜索。这样做的最大优势与大多数库代码所具有的优势相同 - 它性能出色、经过良好测试并且可以正常工作(尤其是二进制搜索可以是 quite difficult to implement successfully - 特别是在没有仔细考虑边缘情况的情况下)。

          基本类型

          对于像 Strings 或 ints 这样的基本类型,这非常简单 - 您只需要 bisect 模块和一个排序列表:

          >>> import bisect
          >>> names = ['bender', 'fry', 'leela', 'nibbler', 'zoidberg']
          >>> bisect.bisect_left(names, 'fry')
          1
          >>> keyword = 'fry'
          >>> x = bisect.bisect_left(names, keyword)
          >>> names[x] == keyword
          True
          >>> keyword = 'arnie'
          >>> x = bisect.bisect_left(names, keyword)
          >>> names[x] == keyword
          False
          

          您也可以使用它来查找重复项:

          ...
          >>> names = ['bender', 'fry', 'fry', 'fry', 'leela', 'nibbler', 'zoidberg']
          >>> keyword = 'fry'
          >>> leftIndex = bisect.bisect_left(names, keyword)
          >>> rightIndex = bisect.bisect_right(names, keyword)
          >>> names[leftIndex:rightIndex]
          ['fry', 'fry', 'fry']
          

          显然,如果需要,您可以只返回索引而不是该索引处的值。

          对象

          对于自定义类型或对象,事情有点棘手:您必须确保实现丰富的比较方法才能正确比较 bisect。

          >>> import bisect
          >>> class Tag(object):  # a simple wrapper around strings
          ...     def __init__(self, tag):
          ...         self.tag = tag
          ...     def __lt__(self, other):
          ...         return self.tag < other.tag
          ...     def __gt__(self, other):
          ...         return self.tag > other.tag
          ...
          >>> tags = [Tag('bender'), Tag('fry'), Tag('leela'), Tag('nibbler'), Tag('zoidbe
          rg')]
          >>> key = Tag('fry')
          >>> leftIndex = bisect.bisect_left(tags, key)
          >>> rightIndex = bisect.bisect_right(tags, key)
          >>> print([tag.tag for tag in tags[leftIndex:rightIndex]])
          ['fry']
          

          这应该至少适用于 Python 2.7 -> 3.3

          【讨论】:

            【解决方案12】:

            除非您存储的对象非常小,否则使用 dict 不会使您的内存使用量翻倍,因为这些值只是指向实际对象的指针:

            >>> a = 'foo'
            >>> b = [a]
            >>> c = [a]
            >>> b[0] is c[0]
            True
            

            在该示例中,“foo”仅存储一次。这对你有影响吗?我们到底在谈论多少项目?

            【讨论】:

            • 这是关于数字和很多数字的:) 我想使用一个几乎和计算机内存一样大的数组。我知道我的问题的根源可能是错误的,但我对缺乏二分搜索方法感到好奇。
            • 你不能有一个足够小的关键对象来限定这里的“非常小”。一个对象的最小成本为 3 个单词(类型、引用计数、有效负载),而列表添加 1 个单词,集合添加 1 个单词,字典添加 2 个单词。所有三个(list/set/dict)也以某种方式预先分配空间,这是另一个乘数,但仍然不够重要。
            【解决方案13】:

            此代码以递归方式处理整数列表。寻找最简单的情况,即:列表长度小于 2。这意味着答案已经存在,并执行测试以检查正确答案。 如果不是,则设置一个中间值并测试是否正确,如果不是,则通过再次调用函数执行二等分,但将中间值设置为上限或下限,将其向左或向右移动

            def binary_search(intList,intValue,lowValue,highValue): 如果(高值 - 低值)&lt 2: 返回 intList[lowValue] == intValue 或 intList[highValue] == intValue 中值 = 低值 + ((高值 - 低值)/2) 如果 intList[middleValue] == intValue: 返回真 如果 intList[middleValue] > intValue: return binary_search(intList, intValue, lowValue, middleValue - 1) return binary_search(intList, intValue, middleValue + 1, highValue)

            【讨论】:

              【解决方案14】:

              查看维基百科http://en.wikipedia.org/wiki/Binary_search_algorithm上的示例

              def binary_search(a, key, imin=0, imax=None):
                  if imax is None:
                      # if max amount not set, get the total
                      imax = len(a) - 1
              
                  while imin <= imax:
                      # calculate the midpoint
                      mid = (imin + imax)//2
                      midval = a[mid]
              
                      # determine which subarray to search
                      if midval < key:
                          # change min index to search upper subarray
                          imin = mid + 1
                      elif midval > key:
                          # change max index to search lower subarray
                          imax = mid - 1
                      else:
                          # return index number 
                          return mid
                  raise ValueError
              

              【讨论】:

                【解决方案15】:
                '''
                Only used if set your position as global
                '''
                position #set global 
                
                def bst(array,taget): # just pass the array and target
                        global position
                        low = 0
                        high = len(array)
                    while low <= high:
                        mid = (lo+hi)//2
                        if a[mid] == target:
                            position = mid
                            return -1
                        elif a[mid] < target: 
                            high = mid+1
                        else:
                            low = mid-1
                    return -1
                

                我想这会更好更有效。请纠正我:)。谢谢

                【讨论】:

                  【解决方案16】:
                  • s 是一个列表。
                  • binary(s, 0, len(s) - 1, find) 是初始呼叫。
                  • 函数返回查询项的索引。如果没有这样的项目,则返回-1

                    def binary(s,p,q,find):
                        if find==s[(p+q)/2]:
                            return (p+q)/2
                        elif p==q-1 or p==q:
                            if find==s[q]:
                                return q
                            else:
                                return -1
                        elif find < s[(p+q)/2]:
                            return binary(s,p,(p+q)/2,find)
                        elif find > s[(p+q)/2]:
                            return binary(s,(p+q)/2+1,q,find)
                    

                  【讨论】:

                    【解决方案17】:
                    def binary_search_length_of_a_list(single_method_list):
                        index = 0
                        first = 0
                        last = 1
                    
                        while True:
                            mid = ((first + last) // 2)
                            if not single_method_list.get(index):
                                break
                            index = mid + 1
                            first = index
                            last = index + 1
                        return mid
                    

                    【讨论】:

                      【解决方案18】:

                      二分搜索:

                      // List - values inside list
                      // searchItem - Item to search
                      // size - Size of list
                      // upperBound - higher index of list
                      // lowerBound - lower index of list
                      def binarySearch(list, searchItem, size, upperBound, lowerBound):
                              print(list)
                              print(upperBound)
                              print(lowerBound)
                              mid = ((upperBound + lowerBound)) // 2
                              print(mid)
                              if int(list[int(mid)]) == value:
                                     return "value exist"
                              elif int(list[int(mid)]) < value:
                                   return searchItem(list, value, size, upperBound, mid + 1)
                              elif int(list[int(mid)]) > value:
                                     return searchItem(list, value, size, mid - 1, lowerBound)
                      

                      // 调用上面的函数使用:

                      list = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
                      searchItem = 1        
                      print(searchItem(list[0], item, len(list[0]) -1, len(list[0]) - 1, 0))
                      

                      【讨论】:

                        【解决方案19】:

                        我需要 python 中的二进制搜索和 Django 模型的泛型。在 Django 模型中,一个模型可以有另一个模型的外键,我想对检索到的模型对象执行一些搜索。我写了以下函数,你可以使用它。

                        def binary_search(values, key, lo=0, hi=None, length=None, cmp=None):
                            """
                            This is a binary search function which search for given key in values.
                            This is very generic since values and key can be of different type.
                            If they are of different type then caller must specify `cmp` function to
                            perform a comparison between key and values' item.
                            :param values:  List of items in which key has to be search
                            :param key: search key
                            :param lo: start index to begin search
                            :param hi: end index where search will be performed
                            :param length: length of values
                            :param cmp: a comparator function which can be used to compare key and values
                            :return: -1 if key is not found else index
                            """
                            assert type(values[0]) == type(key) or cmp, "can't be compared"
                            assert not (hi and length), "`hi`, `length` both can't be specified at the same time"
                        
                            lo = lo
                            if not lo:
                                lo = 0
                            if hi:
                                hi = hi
                            elif length:
                                hi = length - 1
                            else:
                                hi = len(values) - 1
                        
                            while lo <= hi:
                                mid = lo + (hi - lo) // 2
                                if not cmp:
                                    if values[mid] == key:
                                        return mid
                                    if values[mid] < key:
                                        lo = mid + 1
                                    else:
                                        hi = mid - 1
                                else:
                                    val = cmp(values[mid], key)
                                    # 0 -> a == b
                                    # > 0 -> a > b
                                    # < 0 -> a < b
                                    if val == 0:
                                        return mid
                                    if val < 0:
                                        lo = mid + 1
                                    else:
                                        hi = mid - 1
                            return -1
                        

                        【讨论】:

                          【解决方案20】:

                          上面有很多好的解决方案,但我还没有看到一个简单的(KISS 保持简单(因为我)愚蠢地使用 Python 内置/通用 bisect 函数进行二进制搜索。周围有一些代码bisect 函数,我想我在下面有一个示例,我在其中测试了所有情况下的一个小字符串数组。上面的一些解决方案暗示/说这一点,但希望下面的简单代码能帮助像我一样困惑的任何人。

                          Python bisect 用于指示将新值/搜索项插入排序列表的位置。下面的代码使用 bisect_left 如果找到列表/数组中的搜索项,则返回命中的索引(注意 bisect 和 bisect_right 将返回命中或匹配后元素的索引作为插入点)如果没有找到, bisect_left 将返回排序列表中下一项的索引,该索引不会 == 搜索值。唯一的另一种情况是搜索项将位于列表的末尾,返回的索引将超出列表/数组的末尾,并且在 Python 的早期退出下面的代码中使用“and”逻辑句柄。 (第一个条件False Python不检查后续条件)

                          #Code
                          from bisect import bisect_left
                          names=["Adam","Donny","Jalan","Zach","Zayed"]
                          search=""
                          lenNames = len(names)
                          while search !="none":
                              search =input("Enter name to search for or 'none' to terminate program:")
                              if search == "none":
                                  break
                              i = bisect_left(names,search)
                              print(i) # show index returned by Python bisect_left
                              if i < (lenNames) and names[i] == search:
                                  print(names[i],"found") #return True - if function
                              else:
                                  print(search,"not found") #return False – if function
                          ##Exhaustive test cases:
                          ##Enter name to search for or 'none' to terminate program:Zayed
                          ##4
                          ##Zayed found
                          ##Enter name to search for or 'none' to terminate program:Zach
                          ##3
                          ##Zach found
                          ##Enter name to search for or 'none' to terminate program:Jalan
                          ##2
                          ##Jalan found
                          ##Enter name to search for or 'none' to terminate program:Donny
                          ##1
                          ##Donny found
                          ##Enter name to search for or 'none' to terminate program:Adam
                          ##0
                          ##Adam found
                          ##Enter name to search for or 'none' to terminate program:Abie
                          ##0
                          ##Abie not found
                          ##Enter name to search for or 'none' to terminate program:Carla
                          ##1
                          ##Carla not found
                          ##Enter name to search for or 'none' to terminate program:Ed
                          ##2
                          ##Ed not found
                          ##Enter name to search for or 'none' to terminate program:Roger
                          ##3
                          ##Roger not found
                          ##Enter name to search for or 'none' to terminate program:Zap
                          ##4
                          ##Zap not found
                          ##Enter name to search for or 'none' to terminate program:Zyss
                          ##5
                          ##Zyss not found
                          

                          【讨论】:

                            【解决方案21】:

                            你好,这里是我没有对分的 python 实现。让我知道是否可以改进。

                            def bisectLeft(a, t):
                                lo = 0
                                hi = len(a) - 1
                                ans = None
                                # print("------lower------")
                                # print(a, t)
                                while lo <= hi:
                                    mid = (lo + hi) // 2
                                    # print(a[lo:mid], [a[mid]], a[mid:hi])
                                    if a[mid] < t:
                                        lo = mid + 1
                                    elif a[mid] > t:
                                        hi = mid - 1
                                    elif a[mid] == t:
                                        if mid == 0: return 0
                                        if a[mid-1] != t: return mid
                                        hi = mid - 1
                                        
                                return ans
                            
                            def bisectRight(a, t):
                                lo = 0
                                hi = len(a) - 1
                                ans = None
                                # print("------upper------")
                                # print(a, t)
                                while lo <= hi:
                                    mid = (lo + hi) // 2
                                    # print(a[lo:mid], [a[mid]], a[mid:hi])
                                    if a[mid] == t:
                                        ans = mid
                                    if a[mid] <= t:
                                        lo = mid + 1
                                    else:
                                        hi = mid - 1
                                return ans
                            
                            

                            【讨论】:

                              猜你喜欢
                              • 1970-01-01
                              • 1970-01-01
                              • 2021-03-23
                              • 1970-01-01
                              • 2013-11-28
                              • 2017-12-31
                              相关资源
                              最近更新 更多