15-bo‘lim

Qidiruv algoritmlari

Chiziqli va ikkilik qidiruv, ularning taqqoslanishi, bisect moduli va ikkilik qidiruvning noodatiy qo'llanishlari.

🕑 10 daqiqa o‘qish 📄 216 so‘z 👁 7 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. Chiziqli qidiruv
  2. Ikkilik qidiruv
  3. Rekursiv variant
  4. Farqni o'lchash
  5. bisect moduli
  6. bisect bilan foydali funksiyalar
  7. Baholarni harfga aylantirish
  8. Ikkilik qidiruvning kengaytmalari
  9. Birinchi va oxirgi uchrashini topish
  10. Javob bo'yicha ikkilik qidiruv
  11. Taqqoslash jadvali
  12. Xulosa

Qidiruv - dasturlashdagi eng ko'p bajariladigan amal. To'g'ri algoritm tanlash farqi juda katta bo'lishi mumkin.

Chiziqli qidiruv #

Eng sodda usul: birinchidan oxirigacha tekshirib chiqamiz.

Python
def chiziqli_qidir(massiv, qidirilayotgan):
    """Ketma-ket qidiruv. O(n). Tartiblangan bo'lishi shart emas."""
    for indeks, element in enumerate(massiv):
        if element == qidirilayotgan:
            return indeks
    return -1


baholar = [85, 92, 78, 95, 88]
print(chiziqli_qidir(baholar, 95))
print(chiziqli_qidir(baholar, 70))
Natija
3
-1
HolatSolishtirishlarMurakkablik
Element birinchi1O(1)
Element o'rtadan/2O(n)
Element oxirida yoki yo'qnO(n)
Chiziqli qidiruv qachon to'g'ri tanlov?
  • Ma'lumot tartiblanmagan va uni saralash qimmat
  • Massiv kichik (taxminan 50 elementgacha)
  • Qidiruv bir marta bajariladi
  • Ma'lumot doimiy o'zgarib turadi

Python'da bu shunchaki x in royxat yoki royxat.index(x).

Ikkilik qidiruv #

Agar massiv tartiblangan bo'lsa, har bir solishtirishda qidiruv maydonining yarmini tashlab yuborish mumkin.

Bu lug'atdan so'z qidirishga o'xshaydi: kitobni birinchi sahifasidan boshlamaysiz, o'rtasidan ochasiz.

23 ni qidiramiz: 2 7 11 15 19 23 31 o'rta = 15, 23 > 15 → o'ngga 2 7 11 15 19 23 31 o'rta = 23 → topildi! 7 ta elementdan 2 ta solishtirishda topildi 1 000 element → 10 ta solishtirish 1 000 000 element → 20 ta solishtirish 1 000 000 000 element → 30 ta solishtirish
Har bir qadamda qidiruv maydoni ikki barobar qisqaradi
Python
def ikkilik_qidir(massiv, qidirilayotgan):
    """Ikkilik qidiruv. O(log n). Massiv TARTIBLANGAN bo'lishi SHART."""
    chap, ong = 0, len(massiv) - 1

    while chap <= ong:
        orta = (chap + ong) // 2

        if massiv[orta] == qidirilayotgan:
            return orta
        if massiv[orta] < qidirilayotgan:
            chap = orta + 1            # o'ng yarimda qidiramiz
        else:
            ong = orta - 1             # chap yarimda qidiramiz

    return -1


sonlar = [2, 7, 11, 15, 19, 23, 31]
print(ikkilik_qidir(sonlar, 23))
print(ikkilik_qidir(sonlar, 20))
Natija
5
-1
Uchta klassik xato

1. Tartiblanmagan massiv. Ikkilik qidiruv tartibsiz massivda jim ravishda noto'g'ri javob beradi:

Python
tartibsiz = [5, 2, 8, 1, 9]
print(ikkilik_qidir(tartibsiz, 8))     # -1, garchi 8 mavjud bo'lsa ham

2. while chap < ong yozish. <= bo'lishi kerak, aks holda oxirgi element tekshirilmay qoladi.

3. Cheksiz sikl. chap = orta yozsangiz (orta + 1 o'rniga), sikl hech qachon tugamaydi.

Rekursiv variant #

Python
def ikkilik_qidir_rekursiv(massiv, qidirilayotgan, chap=0, ong=None):
    """Rekursiv ikkilik qidiruv."""
    if ong is None:
        ong = len(massiv) - 1

    if chap > ong:
        return -1

    orta = (chap + ong) // 2

    if massiv[orta] == qidirilayotgan:
        return orta
    if massiv[orta] < qidirilayotgan:
        return ikkilik_qidir_rekursiv(massiv, qidirilayotgan, orta + 1, ong)
    return ikkilik_qidir_rekursiv(massiv, qidirilayotgan, chap, orta - 1)


print(ikkilik_qidir_rekursiv([2, 7, 11, 15, 19, 23, 31], 11))
Natija
2

Farqni o'lchash #

Python
import time

N = 10_000_000
katta_massiv = list(range(N))
qidirilayotgan = N - 1              # eng yomon holat

boshlanish = time.perf_counter()
chiziqli_qidir(katta_massiv, qidirilayotgan)
chiziqli_vaqt = time.perf_counter() - boshlanish

boshlanish = time.perf_counter()
ikkilik_qidir(katta_massiv, qidirilayotgan)
ikkilik_vaqt = time.perf_counter() - boshlanish

print(f"Chiziqli qidiruv: {chiziqli_vaqt:.6f} soniya")
print(f"Ikkilik qidiruv:  {ikkilik_vaqt:.6f} soniya")
print(f"Farq:             {chiziqli_vaqt / ikkilik_vaqt:,.0f} barobar")
Natija
Chiziqli qidiruv: 0.512340 soniya
Ikkilik qidiruv:  0.000009 soniya
Farq:             56,927 barobar
Saralash narxini unutmang

Ikkilik qidiruv uchun massiv tartiblangan bo'lishi kerak, saralash esa O(n log n).

VaziyatTo'g'ri tanlov
1 marta qidiruv, tartibsiz massivChiziqli (saralash qimmatroq)
Ko'p marta qidiruv, massiv o'zgarmaydiBir marta saralang + ikkilik
Ko'p marta qidiruv, massiv o'zgaradiXesh-jadval yoki muvozanatli daraxt

bisect moduli #

Python'da ikkilik qidiruvni qo'lda yozish shart emas:

Python
import bisect

sonlar = [2, 7, 11, 15, 19, 23, 31]

# Element qayerga qo'yilishi kerakligini topadi
print(bisect.bisect_left(sonlar, 15))     # 15 ning o'zi turgan joy
print(bisect.bisect_right(sonlar, 15))    # 15 dan keyingi joy
print(bisect.bisect_left(sonlar, 20))     # 20 qayerga qo'yilishi kerak

# Tartibni saqlagan holda qo'shish
bisect.insort(sonlar, 20)
print(sonlar)
Natija
3
4
5
[2, 7, 11, 15, 19, 20, 23, 31]

bisect bilan foydali funksiyalar #

Python
import bisect


def bormi(massiv, qiymat):
    """Element mavjudligini tekshiradi. O(log n)."""
    i = bisect.bisect_left(massiv, qiymat)
    return i < len(massiv) and massiv[i] == qiymat


def nechta_marta(massiv, qiymat):
    """Element necha marta uchrashini sanaydi. O(log n)."""
    chap = bisect.bisect_left(massiv, qiymat)
    ong = bisect.bisect_right(massiv, qiymat)
    return ong - chap


def oraliqda_nechta(massiv, past, yuqori):
    """Berilgan oraliqdagi elementlar sonini qaytaradi. O(log n)."""
    return bisect.bisect_right(massiv, yuqori) - bisect.bisect_left(massiv, past)


baholar = [60, 70, 78, 85, 85, 85, 92, 95, 100]

print("85 bormi:            ", bormi(baholar, 85))
print("85 necha marta:      ", nechta_marta(baholar, 85))
print("80-95 oralig'ida:    ", oraliqda_nechta(baholar, 80, 95), "ta")
Natija
85 bormi:             True
85 necha marta:       3
80-95 oralig'ida:     5 ta

Baholarni harfga aylantirish #

bisect ning eng chiroyli qo'llanishi - if/elif zanjirini almashtirish:

Python
import bisect

CHEGARALAR = [60, 70, 80, 90]
HARFLAR = ["F", "D", "C", "B", "A"]


def harfli_baho(ball):
    """if/elif zanjiri o'rniga O(log n) qidiruv."""
    return HARFLAR[bisect.bisect_right(CHEGARALAR, ball)]


for ball in [45, 65, 75, 85, 95, 100]:
    print(f"{ball:>4} ball -> {harfli_baho(ball)}")
Natija
  45 ball -> F
  65 ball -> D
  75 ball -> C
  85 ball -> B
  95 ball -> A
 100 ball -> A

Ikkilik qidiruvning kengaytmalari #

Birinchi va oxirgi uchrashini topish #

Python
def birinchi_uchrashi(massiv, qiymat):
    """Takrorlanuvchi qiymatning birinchi indeksini topadi."""
    chap, ong = 0, len(massiv) - 1
    natija = -1

    while chap <= ong:
        orta = (chap + ong) // 2
        if massiv[orta] == qiymat:
            natija = orta
            ong = orta - 1              # chapda yana bormi?
        elif massiv[orta] < qiymat:
            chap = orta + 1
        else:
            ong = orta - 1

    return natija


def oxirgi_uchrashi(massiv, qiymat):
    chap, ong = 0, len(massiv) - 1
    natija = -1

    while chap <= ong:
        orta = (chap + ong) // 2
        if massiv[orta] == qiymat:
            natija = orta
            chap = orta + 1             # o'ngda yana bormi?
        elif massiv[orta] < qiymat:
            chap = orta + 1
        else:
            ong = orta - 1

    return natija


baholar = [60, 70, 85, 85, 85, 92, 95]
print("Birinchi 85:", birinchi_uchrashi(baholar, 85))
print("Oxirgi 85:  ", oxirgi_uchrashi(baholar, 85))
Natija
Birinchi 85: 2
Oxirgi 85:   4

Javob bo'yicha ikkilik qidiruv #

Bu kutilmagan, lekin juda kuchli usul: ikkilik qidiruvni massivda emas, javoblar oralig'ida bajarish.

Python
def kvadrat_ildiz(son, aniqlik=1e-9):
    """Ikkilik qidiruv bilan kvadrat ildiz topish."""
    if son < 0:
        raise ValueError("Manfiy sondan ildiz chiqarilmaydi")
    if son == 0:
        return 0.0

    chap, ong = 0.0, max(1.0, son)

    while ong - chap > aniqlik:
        orta = (chap + ong) / 2
        if orta * orta < son:
            chap = orta
        else:
            ong = orta

    return (chap + ong) / 2


print(f"{kvadrat_ildiz(144):.6f}")
print(f"{kvadrat_ildiz(2):.6f}")
Natija
12.000000
1.414214

Amaliy misol - kitoblarni javonlarga taqsimlash:

Python
def eng_kam_javon(kitoblar, javon_uzunligi):
    """Berilgan uzunlikdagi javonlarga necha marta bo'lish kerakligini hisoblaydi."""
    javonlar = 1
    joriy = 0
    for kitob in kitoblar:
        if joriy + kitob > javon_uzunligi:
            javonlar += 1
            joriy = kitob
        else:
            joriy += kitob
    return javonlar


def minimal_javon_uzunligi(kitoblar, javonlar_soni):
    """Kitoblarni k ta javonga sig'dirish uchun minimal javon uzunligini topadi."""
    chap = max(kitoblar)                 # kamida eng katta kitob sig'ishi kerak
    ong = sum(kitoblar)                  # ko'pi bilan hammasi bitta javonda

    while chap < ong:
        orta = (chap + ong) // 2
        if eng_kam_javon(kitoblar, orta) <= javonlar_soni:
            ong = orta                   # kichikroq javon ham yetadimi?
        else:
            chap = orta + 1

    return chap


kitoblar = [30, 20, 45, 15, 25, 40, 35]
for javonlar in [2, 3, 4]:
    uzunlik = minimal_javon_uzunligi(kitoblar, javonlar)
    print(f"{javonlar} ta javon uchun minimal uzunlik: {uzunlik} sm")
Natija
2 ta javon uchun minimal uzunlik: 115 sm
3 ta javon uchun minimal uzunlik: 80 sm
4 ta javon uchun minimal uzunlik: 60 sm
Javob bo'yicha ikkilik qidiruvni qachon ishlatish mumkin?

Agar javobingiz monoton bo'lsa: "X qiymat ishlaydimi?" degan savolga javob ma'lum nuqtagacha "yo'q", undan keyin doim "ha" bo'lsa. Shunda ikkilik qidiruv o'sha chegara nuqtasini topadi. Bu olimpiada masalalarida juda ko'p uchraydi.

Taqqoslash jadvali #

AlgoritmMurakkablikTalabQachon
Chiziqli qidiruvO(n)Yo'qKichik yoki tartibsiz massiv
Ikkilik qidiruvO(log n)TartiblanganKatta, o'zgarmas massiv
Xesh-jadvalO(1)Xeshlanuvchi kalitKo'p qidiruv, tartib kerak emas
BSTO(log n)SolishtiriluvchiTartib va oraliq so'rovlar kerak
Amaliy topshiriq
  1. Aylantirilgan tartiblangan massivda ([15, 19, 23, 2, 7, 11]) ikkilik qidiruv yozing.
  2. Cheksiz uzunlikdagi tartiblangan oqimda element qidiring (oldin chegarani toping).
  3. Ikkita tartiblangan massivning medianasini O(log n) da toping.
  4. Javob bo'yicha ikkilik qidiruv bilan: n ta ishchi m ta vazifani bajarishi uchun minimal vaqtni toping.

Xulosa #

  • Chiziqli qidiruv O(n), lekin hech qanday talab qo'ymaydi.
  • Ikkilik qidiruv O(log n), lekin massiv tartiblangan bo'lishi shart.
  • Milliard elementda ikkilik qidiruv atigi 30 ta solishtirish qiladi.
  • Python'ning bisect moduli tayyor va xatosiz amalga oshirishni beradi.
  • bisect bilan "necha marta uchradi" va "oraliqda nechta" savollariga O(log n) da javob berish mumkin.
  • Javob bo'yicha ikkilik qidiruv - optimallashtirish masalalarini yechishning kuchli usuli.

Keyingi bo'limda rekursiya va "bo'l va hukmronlik qil" yondashuvini chuqurroq o'rganamiz.

Xatolik topdingizmi?

Imlo xatosi, ishlamaydigan kod yoki noto‘g‘ri ma‘lumotni ko‘rsangiz - bizga xabar bering. Har bir xabar administrator tomonidan ko‘rib chiqiladi.