Pencarian biner (membagi dua) dalam Python


176

Apakah ada fungsi perpustakaan yang melakukan pencarian biner pada daftar / tuple dan mengembalikan posisi item jika ditemukan dan 'Salah' (-1, Tidak ada, dll.) Jika tidak?

Saya menemukan fungsi bisect_left / right di modul bisect , tetapi mereka tetap mengembalikan posisi walaupun item tersebut tidak ada dalam daftar. Itu sangat baik untuk penggunaan yang dimaksudkan, tetapi saya hanya ingin tahu apakah suatu item ada dalam daftar atau tidak (tidak ingin memasukkan apa pun).

Saya berpikir untuk menggunakan bisect_leftdan kemudian memeriksa apakah item pada posisi itu sama dengan apa yang saya cari, tetapi tampaknya rumit (dan saya juga perlu melakukan pengecekan batas jika jumlahnya bisa lebih besar dari jumlah terbesar dalam daftar saya). Jika ada metode yang lebih baik saya ingin tahu tentang itu.

Sunting Untuk mengklarifikasi untuk apa saya memerlukan ini: Saya sadar bahwa kamus akan sangat cocok untuk ini, tetapi saya mencoba untuk menjaga konsumsi memori serendah mungkin. Penggunaan yang saya maksudkan akan menjadi semacam tabel pencarian dua arah. Saya sudah dalam tabel daftar nilai dan saya harus dapat mengakses nilai-nilai berdasarkan indeks mereka. Dan saya juga ingin dapat menemukan indeks nilai tertentu atau Tidak ada jika nilainya tidak ada dalam daftar.

Menggunakan kamus untuk ini akan menjadi cara tercepat, tetapi akan (kurang-lebih) menggandakan persyaratan memori.

Saya mengajukan pertanyaan ini berpikir bahwa saya mungkin telah mengabaikan sesuatu di perpustakaan Python. Sepertinya saya harus menulis kode sendiri, seperti yang disarankan Moe.


1
Apa yang ingin Anda capai? Jika nilainya unik, pertimbangkan untuk menggunakan set dan "jika nilai dalam set: sesuatu".
Kirk Strauser

Untuk apa nilainya, "-1" dianggap benar; "0" akan salah.
Glyph

3
Saya sebutkan -1 karena fungsi yang mengembalikan indeks item yang dicari dalam array dapat mengembalikan 0 sehingga -1 dikembalikan jika item tidak ditemukan (mirip dengan pencarian substring).
rslite

3
Jika Anda menggunakan numpy, np.searchsortedbermanfaat. docs.scipy.org/doc/numpy/reference/generated/…
Roman Shapovalov

Jawaban:


237
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 Begitu juga binsearch secara umum.
cubuspl42

4
@ TomSwirly tidak sesederhana milik Anda tetapi benar dan masih merupakan peningkatan:if hi is None: hi = len(a)
Mark Ransom

Bagaimana dengan pesanan menurun?
Parikshit Chalke

2
Bisakah Anda menambahkan beberapa penjelasan di luar kode? Standar di sini telah berubah.
SS Anne

54

Mengapa tidak melihat kode untuk bisect_left / right dan menyesuaikannya sesuai dengan tujuan Anda.

seperti ini:

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
Saya awalnya memberi ini +1, tetapi sekarang saya sampai pada kesimpulan ini bukan hal yang baik. Jika jawaban ini diikuti, itu akan menyebabkan banyak duplikasi kode, dan seperti yang kita semua tahu, sangat mudah untuk mendapatkan pencarian biner.
abyx

1
bukankah seharusnya hi = mid - 1di dalam elif?
Paweł Prażak

7
@ Paweł: mereka adalah dua varian yang setara, tergantung pada apakah batas atas inklusif atau eksklusif. Anda dapat mengubah hi = midke hi = mid-1dan hi = len(a)ke hi = len(a)-1dan while lo < hi:ke while lo <= hi, dan itu akan sama benar
user102008

2
mengapa tidak melakukan sesuatu seperti: def binary_search (a, x, lo = 0, hi = Tidak ada): i = membagi dua (a, x, lo, hai) mengembalikan i jika [i] == x lagi -1 maaf untuk format - tidak yakin bagaimana melakukan ini dengan benar di komentar arrea
Vitali

1
Anda harus menggunakan bisect.bisect_left()daripada ini.
alastair

37

Ini sedikit di luar topik (karena jawaban Moe tampaknya lengkap untuk pertanyaan OP), tetapi mungkin layak untuk melihat kompleksitas untuk seluruh prosedur Anda dari ujung ke ujung. Jika Anda menyimpan sesuatu di daftar yang disortir (yang mana pencarian biner akan membantu), dan kemudian hanya memeriksa keberadaannya, Anda mengalami (kasus terburuk, kecuali ditentukan):

Daftar yang Diurutkan

  • O (n log n) untuk awalnya membuat daftar (jika itu data yang tidak disortir. O (n), jika itu diurutkan)
  • O (log n) pencarian (ini adalah bagian pencarian biner)
  • O (n) masukkan / hapus (mungkin O (1) atau O (log n) kasus rata-rata, tergantung pada pola Anda)

Sedangkan dengan set(), Anda dikenai

  • O (n) untuk membuat
  • O (1) pencarian
  • O (1) masukkan / hapus

Hal yang benar-benar Anda dapatkan dari daftar yang disortir adalah "berikutnya", "sebelumnya", dan "rentang" (termasuk rentang menyisipkan atau menghapus), yaitu O (1) atau O (| range |), diberi indeks awal. Jika Anda tidak sering menggunakan operasi semacam itu, maka menyimpan sebagai set, dan menyortir untuk tampilan mungkin merupakan kesepakatan yang lebih baik secara keseluruhan. set()mengeluarkan sedikit biaya tambahan tambahan dalam python.


7
Ada satu hal lagi yang disortir dari daftar yang disortir. O (n) memerintahkan traversal. Dengan set yang O (n log n) dan Anda akhirnya harus menyalin referensi ke data ke dalam daftar.
Mahakuasa

1
Cukup benar! Terima kasih telah memperluas apa yang saya maksud dengan pencarian rentang. Fwiw, traversal penuh adalah kueri rentang yang sama antara min, maks, yaitu O (k) di mana k = n :)
Gregg Lind


11

Paling sederhana adalah dengan menggunakan dua bagian dan periksa satu posisi kembali untuk melihat apakah item ada di sana:

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
Bagus, tetapi kode muntah jika Anda tidak memberikan nilai 'hi'. Saya akan menulis seperti ini: "def binary_search (a, x, lo = 0, hi = Tidak Ada): dari dua bagian impor dua bagian i = bisect (a, x, lo, hi atau len (a)) return (i- 1 jika a [i-1] == x else -1) "dan mengujinya seperti ini:" untuk i dalam rentang (1, 20): a = daftar (rentang (i)) untuk aa dalam a: j = binary_search (a, aa) if j! = aa: print i, aa, j "
hughdbrown

8

Ini benar dari manual:

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

8.5.1. Mencari Daftar yang Diurutkan

Fungsi membagi dua () berguna untuk menemukan titik penyisipan tetapi bisa rumit atau canggung untuk digunakan untuk tugas pencarian umum. Lima fungsi berikut ini menunjukkan cara mengubahnya menjadi pencarian standar untuk daftar yang diurutkan:

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

Jadi dengan sedikit modifikasi kode Anda harus:

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

Saya setuju bahwa jawaban @ DaveAbrahams menggunakan modul dua bagian adalah pendekatan yang benar. Dia tidak menyebutkan satu detail penting dalam jawabannya.

Dari dokumen bisect.bisect_left(a, x, lo=0, hi=len(a))

Modul pembagian dua tidak memerlukan array pencarian yang harus dihitung sebelumnya. Anda bisa menyajikan titik akhir ke bisect.bisect_leftbukan menggunakan menggunakan default 0danlen(a) .

Bahkan lebih penting untuk saya gunakan, mencari nilai X sedemikian rupa sehingga kesalahan fungsi yang diberikan diminimalkan. Untuk melakukan itu, saya membutuhkan cara agar algoritma bisect_left memanggil komputasi saya sebagai gantinya. Ini sangat sederhana.

Cukup sediakan objek yang didefinisikan __getitem__sebagaia

Sebagai contoh, kita bisa menggunakan algoritme dua-bagian untuk menemukan akar kuadrat dengan presisi sewenang-wenang!

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)

Ini tidak bersih. Gunakan scipy.optimizeuntuk ini.
Neil G

4

Jika Anda hanya ingin melihat apakah ada, coba ubah daftar menjadi 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

Di mesin saya, "if n in l" butuh 37 detik, sementara "if n in d" butuh 0,4 detik.


2
Itu tidak selalu merupakan pilihan yang baik karena beberapa alasan: 1) dikt / set membutuhkan lebih banyak memori. 2) jika dia tidak memiliki banyak dalam daftar, pencarian biner mungkin lebih cepat. 3) mengubah daftar menjadi dikt adalah operasi O (n) sementara pencarian biner adalah O (log n).
Jason Baker

3
Sebagai FYI, "set" overhead dalam python dibandingkan dengan daftar python, sangat sangat rendah. Dan mereka sangat cepat untuk pencarian. Di mana pencarian biner benar-benar unggul adalah untuk mencari rentang.
Gregg Lind

Mengonversi daftar mungkin O (n) tetapi mengurutkan data dalam daftar, yang harus Anda lakukan sebelum pencarian biner, lebih buruk. Dari mana data berasal, Anda mungkin bisa memasukkannya ke dalam kamus saat Anda pergi. Saya setuju bahwa memori mungkin menjadi masalah.
Mark Baker

4

Yang ini adalah:

  • tidak rekursif (yang membuatnya lebih hemat-memori daripada kebanyakan pendekatan rekursif)
  • sebenarnya bekerja
  • cepat karena berjalan tanpa perlu jika ini dan kondisi
  • berdasarkan pada pernyataan matematika bahwa lantai (rendah + tinggi) / 2 selalu lebih kecil dari tinggi di mana rendah adalah batas bawah dan tinggi adalah batas atas.

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

Bisakah Anda berbagi kasus uji?
lifebalance

2

Solusi Dave Abrahams baik. Meskipun saya akan melakukannya minimalis:

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

2

Meskipun tidak ada algoritma pencarian biner eksplisit di Python, ada modul - bisect- yang dirancang untuk menemukan titik penyisipan elemen dalam daftar yang diurutkan menggunakan pencarian biner. Ini bisa "diakali" untuk melakukan pencarian biner. Keuntungan terbesar dari ini adalah keuntungan yang sama dengan kode perpustakaan - ini berkinerja tinggi, teruji dengan baik dan hanya berfungsi (pencarian biner pada khususnya bisa sangat sulit untuk diterapkan dengan sukses - terutama jika kasus tepi tidak dipertimbangkan dengan hati-hati).

Tipe Dasar

Untuk tipe dasar seperti Strings atau ints sangat mudah - yang Anda butuhkan hanyalah bisectmodul dan daftar yang diurutkan:

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

Anda juga dapat menggunakan ini untuk menemukan duplikat:

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

Jelas Anda hanya bisa mengembalikan indeks daripada nilai pada indeks itu jika diinginkan.

Benda

Untuk jenis atau objek khusus, hal-hal sedikit lebih rumit: Anda harus memastikan untuk menerapkan metode perbandingan kaya agar bisect untuk membandingkan dengan benar.

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

Ini harus bekerja setidaknya di Python 2.7 -> 3.3


1

Menggunakan dict tidak akan suka menggandakan penggunaan memori Anda kecuali objek yang Anda simpan benar-benar kecil, karena nilainya hanya petunjuk ke objek yang sebenarnya:

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

Dalam contoh itu, 'foo' hanya disimpan satu kali. Apakah itu membuat perbedaan bagi Anda? Dan berapa banyak barang yang kita bicarakan?


Ini tentang angka dan banyak dari mereka :) Saya ingin menggunakan array hampir sebesar memori komputer. Saya tahu dasar masalah saya bisa saja salah, tetapi saya ingin tahu tentang kurangnya metode pencarian biner.
rslite

1
Anda tidak dapat memiliki objek kunci yang cukup kecil untuk memenuhi syarat sebagai "sangat kecil" di sini. Suatu objek akan memiliki biaya minimum 3 kata (tipe, refcount, payload), sementara daftar menambahkan 1 kata, satu set menambahkan 1 kata, dan sebuah dict menambahkan 2 kata. Ketiga (daftar / set / dikt) ruang preallocate dalam beberapa mode juga, yang merupakan pengganda lain, tetapi masih belum cukup untuk masalah.
Rhamphoryncus

1

Kode ini berfungsi dengan daftar bilangan bulat dengan cara rekursif. Mencari skenario kasus paling sederhana, yaitu: panjang daftar kurang dari 2. Ini berarti jawabannya sudah ada di sana dan tes dilakukan untuk memeriksa jawaban yang benar. Jika tidak, nilai tengah ditetapkan dan diuji untuk menjadi benar, jika tidak membagi dua dilakukan dengan memanggil lagi fungsi, tetapi menetapkan nilai tengah sebagai batas atas atau bawah, dengan menggesernya ke kiri atau kanan.

def binary_search (intList, intValue, lowValue, highValue):
    if (highValue - lowValue) <2:
        kembalikan intList [lowValue] == intValue atau intList [highValue] == intValue
    middleValue = lowValue + ((highValue - lowValue) / 2)
    jika intList [middleValue] == intValue:
        mengembalikan True
    jika intList [middleValue]> intValue:
        kembalikan binary_search (intList, intValue, lowValue, middleValue - 1)
   kembalikan binary_search (intList, intValue, middleValue + 1, highValue)

1

Lihatlah contoh-contoh di 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

Saya kira ini jauh lebih baik dan efektif. tolong perbaiki saya :). Terima kasih


0
  • s adalah daftar.
  • binary(s, 0, len(s) - 1, find) adalah panggilan awal.
  • Fungsi mengembalikan indeks item yang diminta. Jika tidak ada barang yang dikembalikan -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

Pencarian Biner:

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

// Untuk memanggil fungsi di atas gunakan:

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

Saya membutuhkan pencarian biner dalam python dan generik untuk model Django. Dalam model Django, satu model dapat memiliki kunci asing ke model lain dan saya ingin melakukan beberapa pencarian pada objek model yang diambil. Saya menulis fungsi berikut Anda dapat menggunakan ini.

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

Banyak solusi bagus di atas tetapi saya belum melihat yang sederhana (CIUM membuatnya sederhana (karena saya) bodoh menggunakan Python built in / fungsi membagi dua generik untuk melakukan pencarian biner. Dengan sedikit kode di sekitar fungsi membagi dua, Saya pikir saya punya contoh di bawah ini di mana saya telah menguji semua kasus untuk array string nama kecil. Beberapa solusi di atas menyinggung / mengatakan ini, tetapi mudah-mudahan kode sederhana di bawah ini akan membantu orang yang bingung seperti saya.

Python bisect digunakan untuk menunjukkan di mana memasukkan nilai baru / item pencarian ke dalam daftar yang diurutkan. Kode di bawah ini yang menggunakan bisect_left yang akan mengembalikan indeks klik jika item pencarian dalam daftar / array ditemukan (Catatan bisect_right akan mengembalikan indeks elemen setelah hit atau cocok sebagai titik penyisipan) Jika tidak ditemukan , bisect_left akan mengembalikan indeks ke item berikutnya dalam daftar yang diurutkan yang tidak akan == nilai pencarian. Satu-satunya kasus lain adalah di mana item pencarian akan pergi di akhir daftar di mana indeks kembali akan berada di luar akhir daftar / array, dan yang dalam kode di bawah pintu keluar awal oleh Python dengan "dan" pegangan logika. (kondisi pertama False Python tidak memeriksa kondisi selanjutnya)

#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
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.