Двоичный поиск (bisection) в Python

Есть ли функция библиотеки, которая выполняет бинарный поиск в списке/кортеже и возвращает позицию элемента, если найдена, и "False" (-1, Нет и т.д.), если нет?

Я нашел функции bisect_left/right в bisect module, но они все равно возвращают позицию, даже если элемент отсутствует в списке. Это прекрасно подходит для их предполагаемого использования, но я просто хочу знать, есть ли элемент в списке или нет (не хотите вставлять что-либо).

Я думал об использовании bisect_left, а затем проверял, соответствует ли элемент в этой позиции тому, что я ищу, но это кажется громоздким (и мне также нужно сделать проверку границ, если число может быть больше самого большого число в моем списке). Если есть более хороший метод, я хотел бы узнать об этом.

Изменить. Чтобы уточнить, для чего мне это нужно: я знаю, что словарь будет очень хорошо подходит для этого, но я стараюсь, чтобы потребление памяти было как можно ниже. Мое предполагаемое использование было бы своего рода двухсторонней справочной таблицей. У меня есть в таблице список значений, и мне нужно иметь доступ к значениям на основе их индекса. А также я хочу, чтобы найти индекс определенного значения или None, если значение отсутствует в списке.

Использование словаря для этого было бы самым быстрым способом, но (приблизительно) удвоить требования к памяти.

Я задавал этот вопрос, думая, что, возможно, что-то забыл в библиотеках Python. Кажется, мне придется написать свой собственный код, как предположил Мо.

Ответ 1

from bisect import bisect_left

def binary_search(a, x, lo=0, hi=None):  # can't use a to specify default for hi
    hi = hi if hi is not None else len(a)  # hi defaults to 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

Ответ 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

Ответ 3

Это немного не по теме (поскольку ответ Moe кажется полным вопросом OP), но, возможно, стоит посмотреть на сложность всей вашей процедуры из конца в конец. Если вы храните вещи в отсортированных списках (где будет использоваться двоичный поиск), а затем просто проверяйте существование, вы будете выполнять (в худшем случае, если не указано):

Сортированные списки

  • O (n log n) для первоначального создания списка (если он несортировал данные O (n), если он отсортирован)
  • O (log n) поиск (это часть двоичного поиска)
  • O (n) insert/delete (может быть O (1) или O (log n) средний случай, в зависимости от вашего шаблона)

Если set(), вы несете

  • O (n) для создания
  • O (1) поиск
  • O (1) insert/delete

Вещь, которую вы выбрали отсортированным списком, - это "следующий", "предыдущий" и "диапазон" (включая вставку или удаление диапазонов), которые являются O (1) или O (| диапазон |), с учетом начального индекса, Если вы не часто используете такие виды операций, то хранение в виде наборов и сортировка для отображения могут быть лучше в целом. set() вносит очень небольшие дополнительные накладные расходы в python.

Ответ 4

Проще всего использовать 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

Ответ 5

Возможно, стоит отметить, что теперь данные bisect предоставляют примеры поиска: http://docs.python.org/library/bisect.html#searching-sorted-lists

(Raising ValueError вместо возврата -1 или None больше pythonic - list.index() делает это, например. Но, конечно, вы можете адаптировать примеры к вашим потребностям.)

Ответ 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

Я согласен, что @DaveAbrahams ответ, используя модуль bisect, является правильным подходом. Он не упомянул об одной важной детали в своем ответе.

Из docs bisect.bisect_left(a, x, lo=0, hi=len(a))

Модуль bisection не требует предварительного вычисления массива поиска заранее. Вы можете просто представить конечные точки bisect.bisect_left вместо него, используя значения по умолчанию 0 и len(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)

Ответ 8

Если вы просто хотите посмотреть, присутствует ли это, попробуйте перевести список в dict:

# 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

На моей машине "если n в l" заняло 37 секунд, а "если n в d" заняло 0,4 секунды.

Ответ 9

Решение Дейва Абрахама хорошее. Хотя я бы сделал это минималистичным:

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

Ответ 10

Хотя в Python нет явного алгоритма бинарного поиска, существует модуль - bisect - предназначен для поиска точки вставки для элемента в отсортированном списке с использованием двоичного поиска. Это может быть "обмануто" при выполнении двоичного поиска. Самым большим преимуществом этого является то же преимущество, что и у большинства библиотечных кодов, - это высокопроизводительные, хорошо проверенные и справедливые работы (в частности, двоичный поиск может быть довольно сложно успешно реализуются - особенно, если краевые случаи не рассматриваются должным образом).

Основные типы

Для базовых типов, таких как Strings или int, это довольно просто - все, что вам нужно - это модуль 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

Ответ 11

Это:

  • не рекурсивный (что делает его более эффективным с точки зрения памяти, чем большинство рекурсивных подходов)
  • на самом деле рабочий
  • быстро, так как он работает без лишних if и условий
  • на основе математического утверждения, что пол (низкий + высокий)/2 всегда меньше, чем высокий, где низкий нижний предел и высокий верхний предел.
  • проверено: D

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

Ответ 12

Использование dict не позволит удвоить использование вашей памяти, если только объекты, которые вы храните, действительно крошечные, поскольку значения являются только указателями на фактические объекты:

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

В этом примере "foo" хранится только один раз. Это имеет значение для вас? И точно, сколько вещей мы говорим в любом случае?

Ответ 13

Этот код работает с целыми списками рекурсивным способом. Ищет простейший случайный сценарий, который: длина списка меньше 2. Это означает, что ответ уже существует, и выполняется проверка для проверки правильного ответа. Если нет, то среднее значение устанавливается и проверяется на правильность, если не выполняется биссекция, снова вызывая функцию, но устанавливая среднее значение как верхний или нижний предел, сдвигая ее влево или вправо

def binary_search(intList, intValue, lowValue, highValue):
    if(highValue - lowValue) &lt 2:
        return intList[lowValue] == intValue or intList[highValue] == intValue
    middleValue = lowValue + ((highValue - lowValue)/2)
    if intList[middleValue] == intValue:
        return True
    if intList[middleValue] > intValue:
        return binary_search(intList, intValue, lowValue, middleValue - 1)
   return binary_search(intList, intValue, middleValue + 1, highValue)

Ответ 14

Ознакомьтесь с примерами в Wikipedia 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

Мне нужен бинарный поиск в 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

Ответ 18

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

Ответ 19

Двоичный поиск:

// 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))