Python'da ikili arama (ikiye bölme)


177

Bir listede / grupta ikili arama yapan ve bulunursa öğenin konumunu döndüren ve yoksa 'False' (-1, None, vb.) Yapan bir kütüphane işlevi var mı?

Ben bisect_left / right işlevlerini bisect modülünde buldum , ancak öğe listede olmasa bile yine de bir konum döndürüyorlar . Amaçlanan kullanım için mükemmel bir şey, ancak sadece bir öğenin listede olup olmadığını bilmek istiyorum (hiçbir şey eklemek istemiyorum).

Kullanmayı bisect_leftve sonra o pozisyondaki öğenin aradığım şeye eşit olup olmadığını kontrol etmeyi düşündüm , ancak bu hantal görünüyor (ve ayrıca numaraların listemdeki en büyük sayıdan daha büyük olup olmadığını kontrol etmem gerekiyor). Daha güzel bir yöntem varsa bunu bilmek isterim.

Düzenleme Bunun için neye ihtiyacım olduğunu açıklığa kavuşturmak için: Bunun için çok uygun bir sözlük olduğunun farkındayım, ancak bellek tüketimini mümkün olduğunca düşük tutmaya çalışıyorum. Amaçladığım kullanım bir tür çift yönlü arama tablosu olurdu. Tabloda bir değerler listesi var ve ben onların indeks dayalı değerlere erişmek gerekir. Ve ayrıca değer listede yoksa belirli bir değerin dizinini veya Yok'u bulmak istiyorum.

Bunun için bir sözlük kullanmak en hızlı yol olacaktır, ancak (yaklaşık olarak) bellek gereksinimlerini iki katına çıkarır.

Bu soruyu Python kütüphanelerinde göz ardı edebileceğimi düşünerek soruyordum. Moe'nın önerdiği gibi, kendi kodumu yazmam gerekecek gibi görünüyor.


1
Ne yapmaya çalışıyorsun? Değerler benzersizse, bir set ve "set içindeki değer: bir şey" kullanmayı düşünün.
Kirk Strauser

Değeri için "-1" doğru kabul edilir; "0" yanlış olur.
Glif

3
Dizide aranan öğenin dizinini döndüren bir işlev zaten 0 döndürdüğü için -1'den bahsetmiştim, bu nedenle öğe bulunmazsa -1 döndürülür (alt dize aramasına benzer).
rslite

3
Numpy kullanıyorsanız np.searchsortedfaydalıdır. docs.scipy.org/doc/numpy/reference/generated/…
Roman Shapovalov

Yanıtlar:


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 Genel olarak binsearch de yapar.
cubuspl42

4
@TomSwirly seninki kadar basit değil ama doğru ve hala bir gelişme:if hi is None: hi = len(a)
Mark Ransom

Azalan düzen ne olacak?
Parikshit Chalke

2
Kodun dışına biraz açıklama ekleyebilir misiniz? Buradaki standartlar değişti.
SS Anne

54

Neden bisect_left / right koduna bakmıyor ve amacınıza uyacak şekilde uyarlamıyorsunuz.

bunun gibi:

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
Aslında bunu + 1'ledim, ama şimdi sonuca geldim, bu iyi bir şey değil. Bu yanıt izlenirse, çok sayıda kod çoğaltmasına neden olur ve hepimizin bildiği gibi, ikili aramayı f * ck yapmak gerçekten basittir.
abyx

1
olmamalı hi = mid - 1içinde elif?
Paweł Prażak

7
@ Paweł: üst sınırın kapsayıcı veya dışlayıcı olmasına bağlı olarak iki eşdeğer varyanttır. değiştirebileceğiniz hi = midiçin hi = mid-1ve hi = len(a)için hi = len(a)-1ve while lo < hi:için while lo <= hive eşdeğer doğru olacaktır
user102008

2
neden böyle bir şey yapmıyorsunuz: def binary_search (a, x, lo = 0, hi = Yok): i = bisect (a, x, lo, hi) eğer bir [i] == x else -1 için üzgünüm biçimlendirme - yorum alanında bunu doğru şekilde nasıl yapacağınızdan emin değilim
Vitali

1
Bunun bisect.bisect_left()yerine kullanmalısınız .
alastair

37

Bu biraz konu dışı (Moe'nin cevabı OP'nin sorusuna tam olarak göründüğü için), ancak tüm prosedürünüzün karmaşıklığını uçtan uca incelemeye değer olabilir. Bir şeyi sıralı listelerde (ikili aramanın yardımcı olacağı yer) saklıyorsanız ve daha sonra varlığını kontrol ediyorsanız, (belirtmedikçe en kötü durum) ortaya çıkıyorsunuz:

Sıralama Listeleri

  • Listeyi başlangıçta oluşturmak için O (n günlüğü n) (sıralanmamış veriler varsa. Sıralanmışsa O (n))
  • O (log n) aramaları (bu ikili arama kısmıdır)
  • O (n) ekleme / silme (deseninize bağlı olarak ortalama O (1) veya O (log n) olabilir)

Bir ile Halbuki set(), sen oluşturduğunuzu

  • O (n) oluşturmak
  • O (1) arama
  • O (1) ekle / sil

Sıralanmış bir listenin size gerçekten getirdiği şey, bir başlangıç ​​dizini verildiğinde O (1) veya O (| aralık |) olan "sonraki", "önceki" ve "aralıklar" (aralık ekleme veya silme dahil). Bu tür işlemleri sık sık kullanmıyorsanız, küme olarak saklamak ve görüntüleme için sıralama genel olarak daha iyi bir anlaşma olabilir. set()Python'da çok az ek yük oluşturur.


7
Sıralanmış bir listenin size ulaştığı başka bir şey daha var. O (n) emretti. O (n log n) olan bir kümeyle ve verilere referansları bir listeye kopyalamak zorunda kalırsınız.
Yönlü

1
Yeterince doğru! Aralık aramasıyla ne demek istediğimi genişlettiğiniz için teşekkür ederiz. Fwiw, tam bir çapraz geçiş, min, maks arasında bir aralık sorgusu ile aynıdır; bu, O (k) 'dir; burada k = n :)
Gregg Lind


11

En basit olanı bisect kullanmak ve öğenin orada olup olmadığını görmek için bir konumu geri kontrol etmektir:

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
Güzel, ama 'hi' değeri geçmezseniz kod barfs. Ben şöyle yazarım: "def binary_search (a, x, lo = 0, hi = Yok): bisect ithalat bisect'ten i = bisect (a, x, lo, hi veya len (a)) dönüş (i- 1 ise [i-1] == x başka -1) "olarak test edin ve şu şekilde test edin:" (1, 20) aralığında i için: a = a: için aa için liste (aralık (i)): j = binary_search (a, aa) eğer j! = aa: print i, aa, j "
hughdbrown

8

Bu kılavuzdan doğru:

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

8.5.1. Sıralama Listelerini Arama

Yukarıdaki bisect () işlevleri ekleme noktalarını bulmak için faydalıdır, ancak yaygın arama görevleri için kullanımı zor veya garip olabilir. Aşağıdaki beş işlev, bunların sıralı listeler için standart aramalara nasıl dönüştürüleceğini gösterir:

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

Yani küçük bir değişiklikle kodunuz şöyle olmalıdır:

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'ın bisect modülünü kullanarak yanıtının doğru yaklaşım olduğunu kabul ediyorum . Cevabında önemli bir ayrıntıdan bahsetmedi.

Gönderen docs bisect.bisect_left(a, x, lo=0, hi=len(a))

İkiye bölme modülü, arama dizisinin önceden hesaplanmasını gerektirmez. Sadece için son noktalarını sunabilirler bisect.bisect_leftbunun yerine bir varsayılan kullanarak 0ve len(a).

Kullanımım için daha da önemlisi, belirli bir fonksiyonun hatasını en aza indirecek şekilde X değerini aramak. Bunu yapmak için, bisect_left algoritmasının benim hesaplamamı çağırmasının bir yoluna ihtiyacım vardı. Bu gerçekten basit.

Sadece bir nesne sağlamak tanımlar __getitem__olaraka

Örneğin, bisect algoritmasını keyfi hassasiyetle kare bir kök bulmak için kullanabiliriz!

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)

Bu temiz değil. Bunun için kullanın scipy.optimize.
Neil G

4

Sadece mevcut olup olmadığını görmek istiyorsanız, listeyi bir dikte haline getirmeyi deneyin:

# 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

Makinemde "eğer l'de n" 37 saniye, "d'de n ise" 0.4 saniye sürdü.


2
Bu her zaman birkaç nedenden ötürü iyi bir seçenek değildir: 1) dikmeler / setler daha fazla bellek kaplar. 2) listede fazla bir şey yoksa, ikili arama daha hızlı olabilir. 3) listeyi bir dikteye dönüştürmek, ikili arama O (log n) iken O (n) işlemidir.
Jason Baker

3
Bir FYI olarak, python listelerine kıyasla python'daki "set" yükü çok düşüktür. Ve aramalar için son derece hızlılar. İkili aramanın gerçekten mükemmel olduğu yerler aralıkları aramak içindir.
Gregg Lind

Listeyi dönüştürmek O (n) olabilir, ancak listedeki ikili aramadan önce yapmanız gereken verileri sıralamak daha kötüdür. Veriler nereden geliyor, gittiğinizde muhtemelen sözlüğe ekleyebilirsiniz. Hafızanın bir sorun olabileceğini kabul ediyorum.
Mark Baker

4

Bu:

  • özyinelemeli değil (bu, çoğu özyinelemeli yaklaşımlardan daha bellek verimli hale getirir )
  • aslında çalışıyor
  • hızlı beri gereksiz olmadan çalışır yıllardan eğer ve koşullar
  • matematiksel iddia göre bu taban (düşük + yüksek) / 2 daima daha küçüktür en yüksek burada düşük alt sınır olup , yüksek bir üst limit yoktur.

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

Test senaryolarını paylaşabilir misiniz?
cankurtaran

2

Dave Abrahams'ın çözümü iyi. Ben minimalist yapmış olurdu rağmen:

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

2

Python'da açık bir ikili arama algoritması olmasa da, bisectikili bir arama kullanarak sıralı bir listedeki bir öğenin ekleme noktasını bulmak için tasarlanmış bir modül vardır . Bu bir ikili arama yapmak için "kandırılabilir". Bunun en büyük avantajı, çoğu kütüphane kodunun sahip olduğu aynı avantajdır - yüksek performanslı, iyi test edilmiş ve sadece çalışır (özellikle ikili aramaların başarılı bir şekilde uygulanması oldukça zor olabilir - özellikle uç durumlar dikkatle dikkate alınmazsa).

Temel tipler

Dizeler veya ints gibi temel türler için oldukça kolaydır - tek ihtiyacınız olan bisectmodül ve sıralı bir listedir:

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

Bunu kopyaları bulmak için de kullanabilirsiniz:

...
>>> 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']

Açıkça istenirse bu dizindeki değer yerine dizini döndürebilirsiniz.

Nesneler

Özel türler veya nesneler için, işler biraz daha zordur: bisect'in doğru şekilde karşılaştırılmasını sağlamak için zengin karşılaştırma yöntemleri uyguladığınızdan emin olmanız gerekir.

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

Bu en azından Python 2.7 -> 3.3'te çalışmalıdır


1

Bir dikte kullanmak, sakladığınız nesneler gerçekten küçük olmadığı sürece bellek kullanımınızı iki katına çıkarmak istemez, çünkü değerler yalnızca gerçek nesnelere işaret eder:

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

Bu örnekte, 'foo' yalnızca bir kez saklanır. Bu sizin için bir fark yaratıyor mu? Ve tam olarak kaç öğe hakkında konuşuyoruz?


Sayılar ve birçoğu hakkında :) Bilgisayar hafızası kadar büyük bir dizi kullanmak istiyorum. Sorunumun temelinin yanlış olabileceğini biliyorum, ancak ikili bir arama yönteminin eksikliğini merak ediyordum.
rslite

1
Burada "gerçekten küçük" olarak nitelendirilebilecek kadar küçük bir anahtar nesneniz olamaz. Bir nesnenin minimum maliyeti 3 kelimedir (tür, yeniden sayım, yük), bir liste 1 kelime eklerken, bir küme 1 kelime ekler ve bir dikte 2 kelime ekler. Üçü de (list / set / dict), başka bir çarpan olan alanı yine de bir şekilde yeniden konumlandırın, ancak yine de önemli değildir.
Rhamphoryncus

1

Bu kod, tamsayı listeleriyle özyinelemeli bir şekilde çalışır. En basit senaryoyu arar, yani: liste uzunluğu 2'den az. Bu, cevabın zaten orada olduğu ve doğru cevabı kontrol etmek için bir test yapıldığı anlamına gelir. Değilse, bir ikincil değer doğru olarak ayarlanmış ve test edilmiştir, eğer ikiye bölünmezse işlevi tekrar çağırarak, ancak orta değeri üst veya alt sınır olarak sola veya sağa kaydırarak ayarlanır

def binary_search (intList, intValue, lowValue, highValue):
    eğer (highValue - lowValue) <2:
        dönüş intList [lowValue] == intValue veya intList [highValue] == intValue
    middleValue = lowValue + ((highValue - lowValue) / 2)
    intList [middleValue] == intValue ise:
        True döndür
    intList [middleValue]> intValue ise:
        return binary_search (intList, intValue, lowValue, middleValue - 1)
   binary_search döndür (intList, intValue, middleValue + 1, highValue)

1

Wikipedia'daki örneklere göz atın 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

Sanırım bu çok daha iyi ve etkili. lütfen düzelt beni :) . teşekkür ederim


0
  • s bir listedir.
  • binary(s, 0, len(s) - 1, find) ilk çağrıdır.
  • İşlev, sorgulanan öğenin bir dizinini döndürür. Böyle bir öğe yoksa geri döner -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

Ikili arama :

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

// Yukarıdaki işlevi çağırmak için şunu kullanın:

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 modelleri için python ve generic'de ikili aramaya ihtiyacım vardı. Django modellerinde, bir modelin başka bir modele yabancı anahtarı olabilir ve geri getirilen model nesneleri üzerinde biraz arama yapmak istedim. Ben aşağıdaki fonksiyonu yazabilirsiniz bunu kullanabilirsiniz.

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

Yukarıdaki birçok iyi çözüm ama bir ikili arama yapmak için basit / (KISS basit tutmak (çünkü ben) Python yerleşik / genel bisect işlevi aptal kullanımı görmedim. Bisect işlevi etrafında biraz kod ile, Ben küçük bir dizi dize dizisi için tüm durumlarda test nerede aşağıda bir örnek var düşünüyorum.Yukarıdaki çözümlerden bazıları bunu söylemek / söylemek, ama umarım aşağıdaki basit kod herkes gibi benim şaşkın yardımcı olacaktır.

Python bisect, sıralanmış bir listeye yeni bir değer / arama öğesinin nereye ekleneceğini belirtmek için kullanılır. Listedeki / dizideki arama öğesi bulunursa isabetin dizinini döndürecek bisect_left kullanan aşağıdaki kod (not bisect ve bisect_right, isabetten sonra öğenin dizinini veya ekleme noktası olarak eşleşir) , bisect_left, sıralı listedeki bir sonraki öğeye arama değerini döndürmeyecek bir dizin döndürür. Diğer tek durum, arama öğesinin listenin sonuna gittiği ve döndürülen dizinin liste / dizinin sonunun ötesine geçeceği ve Python tarafından "ve" mantık tanıtıcılarıyla erken çıkışın altındaki kodda bulunduğu durumdur. (ilk koşul Yanlış Python sonraki koşulları denetlemez)

#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
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.