파이썬에서 이진 검색 (이분법)


177

목록 / 튜플에서 이진 검색을 수행하고 발견되면 항목의 위치를 ​​반환하고 그렇지 않으면 'False'(-1, None 등)를 반환하는 라이브러리 함수가 있습니까?

bisect 모듈 에서 bisect_left / right 함수를 찾았습니다 지만 항목이 목록에 없어도 여전히 위치를 반환합니다. 의도 된 사용법에는 완벽하게 적합하지만 항목이 목록에 있는지 여부를 알고 싶습니다 (삽입하고 싶지 않음).

bisect_left해당 위치의 항목이 검색중인 항목과 같은지 확인하고 사용하는 것을 생각 했지만 번거로운 것처럼 보입니다 (그리고 내 목록에서 가장 큰 수보다 큰 수인지 확인하는 경계 검사도 필요합니다). 더 좋은 방법이 있다면 그것에 대해 알고 싶습니다.

편집하다 내가 필요한 것을 명확히하려면 : 사전이 이것에 매우 적합하다는 것을 알고 있지만 가능한 한 메모리 소비를 줄이려고합니다. 내 의도 된 사용법은 일종의 양방향 조회 테이블입니다. 테이블에 값 목록이 있으며 해당 색인을 기반으로 값에 액세스 할 수 있어야합니다. 또한 특정 값의 인덱스를 찾거나 값이 목록에 없으면 None을 찾고 싶습니다.

이를 위해 사전을 사용하는 것이 가장 빠른 방법이지만 메모리 요구량을 (대략적으로) 두 배로 늘릴 것입니다.

파이썬 라이브러리에서 뭔가 간과했을 수도 있다고 생각 하면서이 질문을하고있었습니다. Moe가 제안한 것처럼 내 코드를 작성해야 할 것 같습니다.


1
당신이 이루려고하는 것이 무엇입니까? 값이 고유 한 경우 세트와 "세트의 경우 : 무언가"를 사용하십시오.
Kirk Strauser

가치있는 것은 "-1"이 사실로 간주된다는 것입니다. "0"은 거짓입니다.
글리프

3
배열에서 검색 된 항목의 인덱스를 반환하는 함수가 이미 0을 반환 할 수 있으므로 항목을 찾을 수 없으면 (하위 문자열 검색과 유사) -1이 반환되므로 -1을 언급했습니다.
rslite

3
numpy를 사용하면 np.searchsorted유용합니다. docs.scipy.org/doc/numpy/reference/generated/…
Roman Shapovalov

답변:


238
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

10
@volcano 일반적으로 binsearch도 수행합니다.
cubuspl42

4
@TomSwirly 당신만큼 간단하지는 않지만 정확하고 개선 :if hi is None: hi = len(a)
Mark Ransom

내림차순은 어떻습니까?
Parikshit Chalke 2018 년

2
코드 외부에 설명을 추가 할 수 있습니까? 여기의 표준이 변경되었습니다.
SS Anne

54

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

29
나는 원래 이것을 +1했지만 지금은 이것이 좋지 않다는 결론에 도달했다. 이 답변을 따르면 많은 코드 중복이 발생할 수 있으며 우리 모두 알다시피 바이너리 검색을 간단하게 수행 할 수 있습니다.
abyx

1
그것은 안 hi = mid - 1elif?
Paweł Prażak

7
@ Paweł : 그것들은 상한을 포함하는지 또는 배타적인지에 따라 두 개의 동등한 변형입니다. 당신은 변경할 수 있습니다 hi = midhi = mid-1hi = len(a)hi = len(a)-1while lo < hi:while lo <= hi, 그것은 동등하게 올바른 것
user102008

2
왜 def binary_search (a, x, lo = 0, hi = None) : i = bisect (a, x, lo, hi)는 a [i] == x else -1 인 경우 i를 반환합니다. 형식-코멘트 arrea 에서이 작업을 올바르게 수행하는 방법을 잘 모르겠습니다
Vitali

1
bisect.bisect_left()이보다 는 사용해야 합니다.
alastair

37

이것은 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()파이썬에서 추가 오버 헤드가 거의 발생하지 않습니다.


7
정렬 된 목록으로 얻을 수있는 또 다른 것이 있습니다. O (n)은 순회를 주문했습니다. 세트는 O (n log n)이며 데이터에 대한 참조를 목록에 복사해야합니다.
Omnifarious

1
충분합니다! 범위 검색의 의미를 확장 해 주셔서 감사합니다. Fwiw, 전체 순회는 min, max 사이의 범위 쿼리와 동일합니다. k (n) 여기서 k = n :)
Gregg Lind


11

가장 간단한 방법은 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

2
멋지지만 'hi'값을 전달하지 않으면 코드가 손상됩니다. "def binary_search (a, x, lo = 0, hi = None) : bisect에서 가져 오기 bisect i = bisect (a, x, lo, hi 또는 len (a)) return (i- a [i-1] == x else -1) "인 경우 1이고 다음과 같이 테스트합니다 :"range (1, 20)의 i의 경우 a : a의 경우 a = list (range (i)) : a = j (a, aa) if j! = aa : print i, aa, j "
hughdbrown

8

이것은 매뉴얼에서 옳습니다.

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

8.5.1. 정렬 된 목록 검색

위의 bisect () 함수는 삽입 점을 찾는 데 유용하지만 일반적인 검색 작업에 사용하기 까다 롭거나 어색 할 수 있습니다. 다음 5 가지 함수는 정렬 된 목록에 대한 표준 조회로 변환하는 방법을 보여줍니다.

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

6

@DaveAbrahams의 답변에 동의합니다.bisect 모듈을 사용하는 이 올바른 접근 방법 . 그는 그의 대답에 중요한 세부 사항을 언급하지 않았습니다.

로부터 문서 bisect.bisect_left(a, x, lo=0, hi=len(a))

이 분할 모듈은 검색 배열을 미리 미리 계산할 필요가 없습니다. 당신은 단지에 엔드 포인트를 제시 할 수 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)

이것은 깨끗하지 않습니다. scipy.optimize이것을 위해 사용하십시오 .
Neil G

4

존재하는지 확인하려면 목록을 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

내 컴퓨터에서 "if n in l"은 37 초, "if n in d"는 0.4 초가 걸렸습니다.


2
몇 가지 이유로 항상 좋은 옵션은 아닙니다. 1) dicts / sets가 더 많은 메모리를 차지합니다. 2) 목록에 많은 것이 없으면 이진 검색이 더 빠를 수 있습니다. 3) 이진 검색이 O (log n) 인 동안 목록을 dict로 변환하는 것은 O (n) 연산입니다.
Jason Baker

3
참고로, 파이썬 목록과 비교하여 파이썬에서 "설정된"오버 헤드는 매우 낮습니다. 그리고 조회 속도가 매우 빠릅니다. 이진 검색이 실제로 뛰어난 곳은 범위를 찾는 것입니다.
Gregg Lind

목록을 변환하는 것은 O (n) 일 수 있지만 이진 검색 전에 수행 해야하는 목록의 데이터 정렬은 더 나쁩니다. 데이터가 어디에서 왔는지에 따라 사전에 데이터를 삽입 할 수 있습니다. 메모리 문제 일 수 있음에 동의합니다.
Mark Baker

4

이것은 :

  • 재귀 적 아님 ( 대부분의 재귀 적 접근법보다 메모리 효율성이 높음)
  • 실제로 작동
  • 불필요한 if 와 조건 없이 실행 되기 때문에 빠릅니다.
  • 수학적 주장에 기초 한다는 의 층 (저 + 높은)은 / 2 보다 항상 작 높은낮음 하한이고 높이가 상한이다.

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

테스트 사례를 공유 할 수 있습니까?
수명 균형

2

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

2

파이썬에는 명시적인 이진 검색 알고리즘이 없지만 이진 검색을 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']

분명히 원하는 경우 해당 색인의 값 대신 색인을 반환 할 수 있습니다.

사물

사용자 정의 유형 또는 객체의 경우 상황이 약간 까다로워집니다. 이등분을 올바르게 비교하려면 풍부한 비교 방법을 구현해야합니다.

>>> 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에서 작동해야합니다.


1

값이 실제 객체에 대한 포인터 일 뿐이므로 저장하는 객체가 실제로 작지 않으면 dict를 사용하는 것이 메모리 사용량을 두 배로 늘리는 것을 좋아하지 않습니다.

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

이 예에서 'foo'는 한 번만 저장됩니다. 그것이 당신에게 차이가 있습니까? 어쨌든 우리는 몇 개의 항목에 대해 이야기하고 있습니까?


그것은 숫자와 많은 것들에 관한 것입니다. :) 컴퓨터 메모리만큼 큰 배열을 사용하고 싶습니다. 내 문제의 기초가 잘못되었다는 것을 알고 있지만 이진 검색 방법이 부족한 것이 궁금했습니다.
rslite

1
여기서 "정말 작은"것으로 간주 할만큼 작은 키 개체를 가질 수 없습니다. 객체의 최소 비용은 3 워드 (유형, 참조 횟수, 페이로드)이며 목록은 1 워드를 추가하고 세트는 1 워드를 추가하며 dict는 2 워드를 추가합니다. 세 가지 (목록 / 설정 / dict) 모두 공간을 어떤 방식으로도 미리 할당하는데, 이것은 또 다른 승수이지만 여전히 중요하지 않습니다.
Rhamphoryncus

1

이 코드는 정수 목록과 재귀적인 방식으로 작동합니다. 가장 간단한 시나리오를 찾습니다.리스트 길이가 2보다 작습니다. 이는 이미 답변이 있고 정답을 확인하기위한 테스트를 수행함을 의미합니다. 그렇지 않은 경우, 중간 값이 설정되고 올바른지 테스트합니다. 이분법을 다시 호출하여이 분할을 수행하지 않고 중간 값을 왼쪽 또는 오른쪽으로 이동하여 중간 값을 상한 또는 하한으로 설정

데프 바이너리 _ 검색 (intList, intValue, lowValue, highValue) :
    if (highValue-lowValue) <2 :
        intList [lowValue] == intValue 또는 intList [highValue] == intValue를 반환합니다.
    middleValue = lowValue + ((고가-저가) / 2)
    intList [middleValue] == intValue 인 경우 :
        True를 반환
    intList [middleValue]> intValue 인 경우 :
        binary_search (intList, intValue, lowValue, middleValue-1) 반환
   binary_search (intList, intValue, middleValue + 1, highValue) 반환

1

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

0
'''
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

나는 이것이 훨씬 더 좋고 효과적이라고 생각한다. 나를 수정하십시오 :). 감사합니다


0
  • 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)

0
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

0

이진 검색 :

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

0

파이썬에서 바이너리 검색이 필요했고 장고 모델에는 일반 검색이 필요했습니다. 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

0

위의 많은 좋은 솔루션을 보았지만 이진 검색을 수행하기 위해 Python 내장 / 일반 bisect 함수를 간단하게 사용하는 것은 보지 못했습니다 (KISS 간단하게 유지하기 때문에). 나는 작은 문자열 배열의 이름에 대해 모든 경우를 테스트 한 아래의 예가 있다고 생각합니다. 위의 솔루션 중 일부는 이것을 암시하거나 말하지만, 아래의 간단한 코드가 저처럼 혼란스러워하는 사람을 도울 것입니다.

Python bisect는 새로운 값 / 검색 항목을 정렬 된 목록에 삽입 할 위치를 나타내는 데 사용됩니다. bisect_left를 사용하는 아래 코드는 목록 / 배열에서 검색 항목을 찾은 경우 적중의 색인을 리턴합니다 (참고 사항 bisect 및 bisect_right는 적중 또는 일치 후 요소의 색인을 삽입 점으로 리턴합니다) bisect_left는 정렬 된 목록의 다음 항목에 대한 색인을 반환합니다.이 값은 == 검색 값이 아닙니다. 유일한 다른 경우는 검색 항목이 목록의 끝 부분으로 이동하고 반환 된 색인이 목록 / 배열의 끝 부분을 넘어서고 "및"논리 핸들이있는 Python의 초기 종료 아래 코드에서 발생합니다. (첫 번째 조건 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
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.