28 β Qidiruv algoritmlari¶
β¬ οΈ Oldingi: 27 β Saralash algoritmlari Β· π README Β· Keyingi: 29 β Graf algoritmlari I: BFS, DFS va qo'llanmalar β‘οΈ
Bu bobda: Kompyuterda eng ko'p bajariladigan amallardan biri β to'plamdan elementni topish. Avval har qanday ma'lumotda ishlaydigan, lekin sekin chiziqli qidiruvni (
O(n)), so'ng saralangan massivda mo'jiza ko'rsatadigan binar qidiruvni (O(log n)) chuqur o'rganamiz: uni to'g'ri yozish (mashhur xatolarga to'la!),lower_bound/upper_boundvariantlari, sikl invarianti orqali to'g'riligini isbotlash. Keyin interpolyatsion qidiruv, "javob ustida binar qidiruv" degan kuchli g'oya, saralangan-vs-hash trade-off va Pythonbisectmodulini ko'ramiz.Halollik / Eslatma: Bu yerdagi murakkablik chegaralari (chiziqli
O(n), binarO(log n), interpolyatsion o'rtachaO(log log n)lekin yomon holatdaO(n)) matematik aniq. Binar qidiruvning to'g'riligi sikl invarianti orqali isbotlanadi. Barcha Python namunalari haqiqatan ishga tushirib tekshirilgan; ko'rsatilgan chiqishlar kod bergan haqiqiy natijalar.
Qidiruv muammosi¶
Sizda bir to'plam (massiv, ro'yxat, jadval) bor va undan biror elementni topishingiz kerak. Bu β informatikadagi eng tez-tez uchraydigan masala. Lug'atda so'z izlash, telefon kitobida ismni topish, ma'lumotlar bazasidan yozuvni olish, kodda o'zgaruvchi nomini qidirish β barchasi tagida qidiruv yotadi.
Aniqlik kiritamiz. Qidiruv masalasi ikki ko'rinishda bo'ladi:
- Bor-yo'qlik (membership): "
xto'plamda bormi?" β javobha/yo'q. - Joyni topish (locate): "
xqaysi indeksda turibdi?" β javob indeks yokitopilmadi.
Joyni topish kuchliroq: indeksni bilsangiz, bor-yo'qlikni ham bilasiz. Shu sababli odatda joyni qaytaramiz va "topilmadi" uchun maxsus qiymat (-1) ishlatamiz.
Diqqat: Qidiruv tezligi ma'lumotning tartibiga keskin bog'liq. Saralanmagan ma'lumotda eng yaxshi ko'rsatkich
O(n)β boshqa iloj yo'q. Saralangan ma'lumotda esaO(log n)gacha tushiramiz. Bu farq β butun bobning markazidagi g'oya.
Chiziqli qidiruv¶
Intuitsiya: Stol ustida aralash kartalar yotibdi, "qarol"ni topishingiz kerak. Tartib yo'q, shuning uchun birinchidan oxirigacha har bir kartani ko'tarib qaraysiz. Bu β chiziqli qidiruv.
Chiziqli qidiruv (ingliz tilida linear yoki sequential search) β eng oddiy strategiya: elementlarni birinchidan oxirigacha navbat bilan tekshirib, qidirilayotganini topguncha (yoki ro'yxat tugaguncha) yuramiz.
def chiziqli_qidiruv(arr, x):
for i in range(len(arr)):
if arr[i] == x:
return i
return -1
print(chiziqli_qidiruv([4, 2, 7, 1, 9, 3], 9)) # -> 4
print(chiziqli_qidiruv([4, 2, 7, 1, 9, 3], 5)) # -> -1
Murakkablik. n ta element bo'lsa:
- Eng yaxshi holat
O(1)β qidirilayotgan element birinchi turibdi. - Eng yomon holat
O(n)β element oxirida yoki umuman yo'q (hamma elementni ko'ramiz). - O'rtacha holat
O(n)β element bor bo'lsa, o'rtachan/2taqqoslash; konstantani tashlaymiz.
Murakkablikni nega aynan shunday sanaymiz β bu haqda 9-bobda batafsil. Asosiy amal (taqqoslash) sonini hisoblaymiz.
Qachon ishlatamiz? Chiziqli qidiruv "yomon" emas β u saralanmagan ma'lumotda yagona yo'l. Bundan tashqari:
- Ma'lumot kichik bo'lsa (
no'nlab birlik), saralash ortiqcha β chiziqli yetarli. - Bir martagina qidirish kerak bo'lsa, saralash narxini (
O(n log n)) qoplab bo'lmaydi. - Ma'lumot bog'langan ro'yxat bo'lib, tasodifiy indeksga kirib bo'lmasa (13-bob), baribir chiziqli yuramiz.
Binar qidiruv¶
Endi saralangan massivda ishlaymiz. Bu shartdan ulkan kuch chiqadi.
Intuitsiya: Lug'atda "samolyot" so'zini izlaysiz. Birinchi betdan boshlab varaqlamaysiz β lug'atni o'rtasidan ochasiz. "M" ko'rdingiz; "s" undan keyin keladi, demak birinchi yarmini butunlay tashlab, ikkinchi yarmining o'rtasini ochasiz. Har ochishda izlanadigan hudud yarmiga kamayadi. Bu β binar qidiruv.
Binar qidiruv (ingliz tilida binary search) saralangan massivda ishlaydi:
- Joriy oraliqning o'rtasidagi elementni tekshir.
- Agar u qidirilayotganga teng bo'lsa β topdik.
- Agar qidirilayotgan kichikroq bo'lsa β javob faqat chap yarmida bo'lishi mumkin, o'ng yarmni tashla.
- Agar kattaroq bo'lsa β o'ng yarmda davom et.
Har qadamda oraliq yarmiga kamayadi, shuning uchun n element ustida ko'pi bilan βlogβ nβ + 1 qadam ketadi β bu O(log n). Bu β bo'lib hal qilish (divide & conquer) paradigmasining sof namunasi (aniqrog'i, decrease & conquer β har qadamda masala ikki barobar kichrayadi, lekin faqat bitta yarmida davom etamiz).
O(n) va O(log n) orasidagi farq ulkan. n = 1 000 000 uchun chiziqli qidiruv eng yomon holatda million taqqoslash qiladi; binar qidiruv esa atigi 20 ta (logβ 1 000 000 β 20). Bir milliardlik massivda β 30 ta. Bu shunchaki "tezroq" emas β bu boshqa o'yin.
Binar qidiruvni to'g'ri yozish¶
Binar qidiruv g'oyasi sodda, lekin uni xatosiz yozish mashhur darajada qiyin. Tajribali dasturchilar ham off-by-one (bir birlikka adashish), cheksiz sikl yoki chegaradan chiqib ketish xatolariga yo'l qo'yishadi. Bir afsona bor: binar qidiruv 1946-yilda chop etilgan, lekin birinchi xatosiz umumiy versiyasi 1962-yilgacha nashr etilmagan.
Asosiy savollar:
- Chegaralar yopiqmi yoki ochiq? Biz
[lo, hi]ni yopiq (inclusive) deb olamiz:lovahiham izlanish hududiga kiradi. midni qanday hisoblaymiz?mid = lo + (hi - lo) // 2. Nega oddiy(lo + hi) // 2emas? Chunki katta sonlardalo + hitoshib ketishi (overflow) mumkin. Python'da butun son cheksiz, lekin C/Java'da bu real xato β shuning uchun bu odatni boshidanoq egallang.- Sikl sharti
<=yoki<? Yopiq oraliqdawhile lo <= hiβ chunkilo == hibo'lganda ham hali bitta tekshirilmagan element qoladi. - Yangilash.
arr[mid] < xbo'lsalo = mid + 1(midni tashlaymiz, u kichik); aks holdahi = mid - 1.+1/-1shart βmidni qayta tekshirmaymiz, aks holda cheksiz sikl bo'ladi.
def binar_qidiruv(arr, x):
lo, hi = 0, len(arr) - 1 # yopiq oraliq [lo, hi]
while lo <= hi:
mid = lo + (hi - lo) // 2 # toshib ketishdan himoyalangan
if arr[mid] == x:
return mid
elif arr[mid] < x:
lo = mid + 1 # chap yarm + mid ni tashla
else:
hi = mid - 1 # o'ng yarm + mid ni tashla
return -1
a = [3, 8, 15, 19, 23, 42, 56, 71]
print(binar_qidiruv(a, 23)) # -> 4
print(binar_qidiruv(a, 56)) # -> 6
print(binar_qidiruv(a, 50)) # -> -1
print(binar_qidiruv(a, 3)) # -> 0
print(binar_qidiruv(a, 71)) # -> 7
Anti-pattern:
lo = midyokihi = middeb yozish (Β±1siz) β eng keng tarqalgan xato. Agararr[mid] < xbo'lgandalo = midqilsangiz va oraliqda ikki element qolsa,mido'zgarmay qoladi β cheksiz sikl. Yopiq oraliqda doimmid Β± 1ishlating.
Trassirovka jadvali¶
a = [3, 8, 15, 19, 23, 42, 56, 71] da x = 56 ni qidiramiz (indekslar 0..7):
| Qadam | lo | hi | mid | a[mid] | Taqqoslash | Harakat |
|---|---|---|---|---|---|---|
| 1 | 0 | 7 | 3 | 19 | 19 < 56 | lo = 4 |
| 2 | 4 | 7 | 5 | 42 | 42 < 56 | lo = 6 |
| 3 | 6 | 7 | 6 | 56 | 56 == 56 | topildi, indeks 6 |
Atigi 3 qadamda topdik (logβ 8 = 3). Quyidagi diagramma lo, hi, mid ko'rsatkichlarining har qadamda qanday siljishini va oraliqning 8 β 4 β 2 β 1 torayishini ko'rsatadi:
Nega ishlaydi: sikl invarianti¶
Bu nazariy kitob β algoritm nega to'g'ri ishlashini isbotlaymiz, faqat yozishni emas. Vosita β sikl invarianti: sikl har aylanishida saqlanadigan haqiqat (5-bob).
Isbot (eskiz). Invariant: agar
xmassivda mavjud bo'lsa, u doim[lo, hi]oralig'ida turadi.
- Boshlanish. Dastlab
lo = 0,hi = n-1β bu butun massiv. Agarxbor bo'lsa, albatta shu oraliqda. Invariant to'g'ri.- Saqlanish.
midni tekshiramiz. Massiv saralangan bo'lgani uchun: agararr[mid] < xbo'lsa,xmiddan kichik bo'la olmaydi, demak u[mid+1, hi]da.lo = mid + 1qilamiz β invariant saqlanadi. Simmetrik tarzdaarr[mid] > xuchunhi = mid - 1.- Tugash. Har qadamda
hi - lokamayadi, shuning uchun sikl albatta tugaydi. Sikllo > hibo'lganda to'xtaydi β oraliq bo'sh. Invariantga ko'ra, agarxbo'lganda u shu (endi bo'sh) oraliqda bo'lardi; oraliq bo'sh, demakxyo'q.-1qaytaramiz β to'g'ri.
Saralanganlik shartisiz "katta/kichik" deb butun yarmni tashlab bo'lmaydi β aynan shu sabab binar qidiruv saralangan massivni talab qiladi.
Variantlar: lower_bound va upper_bound¶
Ko'pincha "x bormi?" emas, "x qayerga kiradi?" yoki "takror qiymatlardan birinchisi qayerda?" kerak bo'ladi. Bunda ikki muhim variant bor.
lower_bound(x) β x dan kichik bo'lmagan birinchi elementning indeksi (ya'ni arr[i] >= x bo'lgan eng chap i). Bu yerda ochiq o'ng chegara [lo, hi) qulayroq:
def lower_bound(arr, x):
lo, hi = 0, len(arr) # ochiq oraliq [lo, hi)
while lo < hi:
mid = lo + (hi - lo) // 2
if arr[mid] < x:
lo = mid + 1
else:
hi = mid # mid ham nomzod -> tashlamaymiz
return lo
b = [1, 2, 2, 2, 3, 5, 8]
print(lower_bound(b, 2)) # -> 1 (birinchi 2)
print(lower_bound(b, 4)) # -> 5 (4 yo'q; 5 shu yerdan boshlanadi)
print(lower_bound(b, 0)) # -> 0 (hammadan kichik)
print(lower_bound(b, 9)) # -> 7 (hammadan katta -> massiv oxiri)
upper_bound(x) β x dan katta birinchi elementning indeksi (arr[i] > x bo'lgan eng chap i). Faqat shart < o'rniga <=:
def upper_bound(arr, x):
lo, hi = 0, len(arr)
while lo < hi:
mid = lo + (hi - lo) // 2
if arr[mid] <= x: # <= : teng bo'lsa ham o'ngga
lo = mid + 1
else:
hi = mid
return lo
b = [1, 2, 2, 2, 3, 5, 8]
print(upper_bound(b, 2)) # -> 4
print(upper_bound(b, 2) - lower_bound(b, 2)) # -> 3 (2 soni 3 marta)
Eslatma:
upper_bound(x) - lower_bound(x)βxqiymatning massivdagi takror soni.[lower_bound, upper_bound)β barchaxga teng elementlar diapazoni. Bu naqsh real loyihalarda juda ko'p ishlatiladi.
E'tibor bering: bu ikki variantda mid ni topganda darrov to'xtamaymiz β chap yoki o'ngdagi yana yaqinroq nomzodni izlashda davom etamiz. Aynan shu binar qidiruvni "topish"dan "chegara izlash"ga aylantiradi.
Birinchi va oxirgi uchrash¶
lower_bound/upper_bound ustiga qurib, takror qiymatning birinchi va oxirgi indeksini olamiz:
def lower_bound(arr, x):
lo, hi = 0, len(arr)
while lo < hi:
mid = lo + (hi - lo) // 2
if arr[mid] < x:
lo = mid + 1
else:
hi = mid
return lo
def upper_bound(arr, x):
lo, hi = 0, len(arr)
while lo < hi:
mid = lo + (hi - lo) // 2
if arr[mid] <= x:
lo = mid + 1
else:
hi = mid
return lo
def birinchi_uchrash(arr, x):
i = lower_bound(arr, x)
return i if i < len(arr) and arr[i] == x else -1
def oxirgi_uchrash(arr, x):
i = upper_bound(arr, x) - 1
return i if i >= 0 and arr[i] == x else -1
b = [1, 2, 2, 2, 3, 5, 8]
print(birinchi_uchrash(b, 2)) # -> 1
print(oxirgi_uchrash(b, 2)) # -> 3
print(birinchi_uchrash(b, 7)) # -> -1 (yo'q)
Python: bisect moduli¶
Amalda binar qidiruvni qo'lda yozish shart emas β Python standart kutubxonasida bisect moduli bor. U aynan lower_bound/upper_bound ni beradi:
import bisect
b = [1, 2, 2, 2, 3, 5, 8]
print(bisect.bisect_left(b, 2)) # -> 1 (lower_bound)
print(bisect.bisect_right(b, 2)) # -> 4 (upper_bound)
print(bisect.bisect_left(b, 4)) # -> 5
# Saralangan ro'yxatga element qo'shish (tartibni saqlab):
bisect.insort(b, 4)
print(b) # -> [1, 2, 2, 2, 3, 4, 5, 8]
Trade-off:
bisect_left/rightβO(log n), lekininsortsaralangan ro'yxatga qo'shganda elementlarni surishi kerak, shuning uchunO(n)(qidirish tez, qo'shish sekin). Ko'p qo'shish kerak bo'lsa, balanslangan daraxt yoki heap o'ylab ko'ring.
Interpolyatsion qidiruv¶
Intuitsiya: Telefon kitobida "Toshmatov"ni izlaysiz. Binar qidiruv har doim aniq o'rtadan ochadi. Lekin siz aqlliroq harakat qilasiz: "T" alifboning oxiriga yaqin, shuning uchun kitobni o'rtadan emas, oxirroqdan ochasiz. Mana shu g'oya β interpolyatsion qidiruv.
Interpolyatsion qidiruv (ingliz tilida interpolation search) β saralangan va qiymatlari taxminan bir tekis taqsimlangan massivda ishlaydi. Binar qidiruv mid ni doim o'rtaga qo'yadi; interpolyatsion esa qidirilayotgan x ning kattaligiga qarab taxminiy pozitsiyani hisoblaydi:
Bu β chiziqli interpolyatsiya: "x a[lo] va a[hi] orasida qaysi nisbatda turibdi β pozitsiyasi ham o'sha nisbatda".
def interpolyatsion_qidiruv(arr, x):
lo, hi = 0, len(arr) - 1
while lo <= hi and arr[lo] <= x <= arr[hi]:
if arr[lo] == arr[hi]: # nolga bo'lishni oldini olamiz
return lo if arr[lo] == x else -1
pos = lo + (x - arr[lo]) * (hi - lo) // (arr[hi] - arr[lo])
if arr[pos] == x:
return pos
elif arr[pos] < x:
lo = pos + 1
else:
hi = pos - 1
return -1
c = [10, 20, 30, 40, 50, 60, 70, 80, 90]
print(interpolyatsion_qidiruv(c, 90)) # -> 8 (bir sakrashda!)
print(interpolyatsion_qidiruv(c, 10)) # -> 0
print(interpolyatsion_qidiruv(c, 55)) # -> -1
print(interpolyatsion_qidiruv(c, 40)) # -> 3
Murakkablik. Bir tekis taqsimotda har sakrash qolgan qatlamlarning kvadrat ildizicha qisqartiradi, shuning uchun o'rtacha O(log log n) β bu binar O(log n) dan ham tezroq. Lekin taqsimot notekis bo'lsa (masalan [1, 2, 3, ..., 100, 10βΉ]), pozitsiya taxmini doim adashadi va eng yomon holatda O(n) ga aylanadi.
Trade-off: Interpolyatsion qidiruv faqat ma'lumot bir tekis taqsimlangan bo'lsa foydali (masalan ketma-ket ID'lar, vaqt belgilari). Ishonchsiz taqsimotda binar qidiruv barqaror tanlov β uning
O(log n)kafolati taqsimotga bog'liq emas.
Kengroq g'oya: javob ustida binar qidiruv¶
Binar qidiruvning eng kuchli qo'llanishi β massivda emas, javoblar diapazonida qidirish. Ingliz tilida bu binary search on the answer.
Intuitsiya: "Eng kichik yetarli sig'imni top" tipidagi masalalar. Agar biror sig'im yetsa, undan kattasi ham yetadi; biror sig'im yetmasa, undan kichigi ham yetmaydi. Bu monoton xossa: javob o'qida bir nuqtada
yo'q...yo'qha...haga o'tadi. Aynan shu o'tish nuqtasini binar qidiruv bilan topamiz β to'g'ridan-to'g'ri javoblar massivini hatto saqlamasdan.
Klassik masala: n ta paketni belgilangan tartibda k kunda jo'natish kerak. Kunlik konveyer sig'imi bir xil. Eng kichik sig'imni toping (shunda k kunga sig'ib qolinsin).
Predikat kun_yetadimi(sigim) β monoton: sig'im katta bo'lsa, kun kamayadi. Javob [max(paketlar), sum(paketlar)] oralig'ida yotadi; shu oraliqda binar qidiramiz:
def kun_yetadimi(paketlar, sigim, k):
kunlar, joriy = 1, 0
for p in paketlar:
if joriy + p > sigim: # bugun sig'maydi -> yangi kun
kunlar += 1
joriy = p
else:
joriy += p
return kunlar <= k
def min_sigim(paketlar, k):
lo, hi = max(paketlar), sum(paketlar)
while lo < hi:
mid = lo + (hi - lo) // 2
if kun_yetadimi(paketlar, mid, k):
hi = mid # yetadi -> kichikroqni sina
else:
lo = mid + 1 # yetmaydi -> kattaroq kerak
return lo
print(min_sigim([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 5)) # -> 15
print(min_sigim([3, 2, 2, 4, 1, 4], 3)) # -> 6
Bu β O(n log S) (S β javob diapazoni kengligi), brute force O(SΒ·n) o'rniga. Bir misol β butun sondan kvadrat ildiz: midΒ² <= n predikati monoton, javobni [0, n] da binar qidiramiz.
def isqrt_binar(n):
lo, hi = 0, n
while lo < hi:
mid = (lo + hi + 1) // 2 # yuqoriga yaxlitlash: lo=mid sikldan chiqish uchun
if mid * mid <= n:
lo = mid
else:
hi = mid - 1
return lo
print(isqrt_binar(16)) # -> 4
print(isqrt_binar(24)) # -> 4
print(isqrt_binar(25)) # -> 5
Eslatma:
lo = midishlatadigan variantdamidni yuqoriga yaxlitlash shart ((lo+hi+1)//2), aks holdalovahiikki element bo'lgandamid == loqotib qolib, cheksiz sikl bo'ladi. Bu β binar qidiruvning yana bir off-by-one tuzog'i.
Saralangan vs saralanmagan: trade-off¶
Binar qidiruv tez, lekin u bepul emas β massiv saralangan bo'lishini talab qiladi. Saralash esa O(n log n). Qaror qoidasi:
| Stsenariy | Eng yaxshi tanlov | Narx |
|---|---|---|
| Bir martalik qidiruv, saralanmagan | Chiziqli qidiruv | O(n) |
| Ko'p marta qidiruv, ma'lumot statik | Bir marta saralash + binar | O(n log n) + har qidiruv O(log n) |
| Ko'p marta qidiruv, tartib kerakmas | Hash jadval | qurish O(n), har qidiruv o'rtacha O(1) |
Diapazon so'rovlari (x dan y gacha) |
Saralash + binar (lower/upper bound) | O(log n) chegaralar |
Asosiy g'oya: saralash β bir martalik investitsiya. q ta qidiruv qilsangiz, chiziqli O(qΒ·n), saralash+binar esa O(n log n + q log n). q katta bo'lsa, ikkinchisi yutadi.
Trade-off: Hash jadval o'rtacha
O(1)bilan eng tez bor-yo'qlik beradi, lekin u tartibsiz β "xdan katta birinchi element" yoki diapazon so'roviga javob bera olmaydi. Aynan tartib kerak bo'lganda saralangan massiv + binar qidiruv (yoki BST) yutadi. "Tezlik" yagona o'lchov emas β qaysi amallar kerakligiga qarang.
Asosiy g'oyalar (bobni qisqacha)¶
- Chiziqli qidiruv β har elementni navbat bilan tekshiradi:
O(n). Saralanmagan ma'lumotda yagona yo'l; kichik yoki bir martalik qidiruvda yetarli. - Binar qidiruv β saralangan massivda oraliqni har qadamda yarmiga bo'ladi:
O(log n). Saralanganlik "katta/kichik" deb butun yarmni tashlashga imkon beradi. - Binar qidiruvni to'g'ri yozish qiyin: yopiq
[lo, hi]oraliq,mid = lo + (hi-lo)//2(toshib ketishdan himoya),while lo <= hi, doimmid Β± 1.lo=mid/hi=midniΒ±1siz ishlatish β cheksiz sikl. - To'g'rilik sikl invarianti bilan isbotlanadi: agar
xbor bo'lsa, u doim[lo, hi]da. lower_bound/upper_boundβ chap/o'ng chegara variantlari; ularning ayirmasi β takror soni. Python'da tayyor:bisect.- Interpolyatsion qidiruv β bir tekis taqsimotda qiymatga qarab sakraydi: o'rtacha
O(log log n), yomon taqsimotdaO(n). - Javob ustida binar qidiruv β monoton predikat bo'lsa, javoblar diapazonida qidirish. Kuchli, keng qo'llaniladigan g'oya.
- Trade-off: saralash β bir martalik investitsiya; ko'p qidiruvda o'zini qoplaydi. Tartib kerakmas bo'lsa, hash jadval o'rtacha
O(1).
Mashqlar¶
Oson¶
1-mashq. arr = [5, 9, 2, 7, 4, 1] da x = 7 ni chiziqli qidiruv bilan izlasangiz, nechta taqqoslash qilinadi? Va x = 3 uchun-chi?
2-mashq. a = [2, 5, 8, 12, 16, 23, 38, 56, 72, 91] (10 ta element) da x = 23 ni binar qidiruv bilan izlaymiz. Har qadamdagi lo, hi, mid, a[mid] qiymatlarini trassirovka jadvalida yozing. Nechta qadam ketadi?
3-mashq. n = 1000 element uchun binar qidiruv eng yomon holatda nechta taqqoslash qiladi? Formulani ko'rsating. n = 1 000 000 uchun-chi?
O'rta¶
4-mashq. Quyidagi binar qidiruvda off-by-one xato bor. Toping, tushuntiring va tuzating:
def buggy(arr, x):
lo, hi = 0, len(arr)
while lo <= hi:
mid = (lo + hi) // 2
if arr[mid] == x:
return mid
elif arr[mid] < x:
lo = mid + 1
else:
hi = mid - 1
return -1
5-mashq. lower_bound funksiyasidan foydalanib, saralangan massivda berilgan x qiymat nechta marta uchrashini O(log n) da qaytaruvchi son(arr, x) funksiyasini yozing. Misol: son([1,2,2,2,3], 2) -> 3, son([1,2,2,2,3], 4) -> 0.
6-mashq. Saralangan massivda x dan qat'iy katta birinchi elementning qiymatini qaytaruvchi (yo'q bo'lsa None) funksiyani bisect modulidan foydalanib yozing. Misol: keyingi([1,3,5,7], 4) -> 5, keyingi([1,3,5,7], 7) -> None.
Qiyin¶
7-mashq. Saralangan (takrorli) massivda berilgan x qiymatning birinchi va oxirgi indeksini [birinchi, oxirgi] ko'rinishida O(log n) da qaytaring; yo'q bo'lsa [-1, -1]. Misol: diapazon([5,7,7,7,8,8,10], 7) -> [1, 3].
8-mashq. (Javob ustida binar qidiruv) kitoblar β sahifalar soni ro'yxati; ularni k ta talabaga tartibni buzmasdan (ketma-ket bo'laklarga) taqsimlash kerak. Maqsad β eng ko'p yuklangan talabaning sahifalar yig'indisini minimallashtirish. Binar qidiruv bilan yeching. Misol: taqsimot([12, 34, 67, 90], 2) -> 113.
9-mashq. (Aylantirilgan massiv) Saralangan, lekin noma'lum nuqtadan aylantirilgan massivda (masalan [4,5,6,7,0,1,2]) x ni O(log n) da qidiring. Takror yo'q deb faraz qiling. Misol: aylangan_qidiruv([4,5,6,7,0,1,2], 0) -> 4, ... 3) -> -1.
Yechimlar
1-mashq yechimi¶
Chiziqli qidiruv chapdan o'ngga yuradi.
x = 7:5(1),9(2),2(3),7(4) β 4 ta taqqoslash (4-indeksda topildi).x = 3: massivda yo'q, hamma 6 elementni ko'ramiz β 6 ta taqqoslash, so'ng-1.
Umumiy qoida: element i-pozitsiyada (0-asosli) bo'lsa i+1 taqqoslash; yo'q bo'lsa n taqqoslash.
2-mashq yechimi¶
a = [2, 5, 8, 12, 16, 23, 38, 56, 72, 91], indekslar 0..9, x = 23:
| Qadam | lo | hi | mid | a[mid] | Harakat |
|---|---|---|---|---|---|
| 1 | 0 | 9 | 4 | 16 | 16 < 23 β lo = 5 |
| 2 | 5 | 9 | 7 | 56 | 56 > 23 β hi = 6 |
| 3 | 5 | 6 | 5 | 23 | 23 == 23 β topildi, indeks 5 |
3 ta qadam. (βlogβ 10β + 1 = 3 + 1 = 4 β yuqori chegara; bu yerda 3 ta yetdi.)
3-mashq yechimi¶
Binar qidiruv eng yomon holatda βlogβ nβ + 1 taqqoslash qiladi (har qadamda oraliq yarmiga kamayadi, oraliq 1 ga tushguncha).
n = 1000:βlogβ 1000β + 1 = 9 + 1 = 10taqqoslash (2βΉ = 512,2ΒΉβ° = 1024).n = 1 000 000:βlogβ 1 000 000β + 1 = 19 + 1 = 20taqqoslash (2ΒΉβΉ β 524 288,2Β²β° β 1.05M).
Tekshirish (har bir kirish qiymati uchun eng yomon qadam soni):
import math
def binar_qadamlar(arr, x):
lo, hi, qadam = 0, len(arr) - 1, 0
while lo <= hi:
qadam += 1
mid = lo + (hi - lo) // 2
if arr[mid] == x:
return qadam
elif arr[mid] < x:
lo = mid + 1
else:
hi = mid - 1
return qadam
n = 1000
arr = list(range(n))
worst = max(binar_qadamlar(arr, x) for x in range(-1, n + 1))
print(worst, math.floor(math.log2(n)) + 1) # -> 10 10
4-mashq yechimi¶
Xato: hi = len(arr) (oraliqdan tashqari indeks) bilan birga while lo <= hi ishlatilgan. Bu ikkovi bir-biriga zid. Birinchi iteratsiyada mid len(arr) gacha yetishi mumkin va arr[mid] β indeksdan chiqib ketish (IndexError). Masalan arr = [1], x = 5: lo=0, hi=1, mid=0, arr[0]=1<5 β lo=1; keyin lo=1<=hi=1, mid=1, arr[1] β xato.
Tuzatish β ikki to'g'ri variant bor:
Variant A (yopiq oraliq): hi = len(arr) - 1 qiling (sikl sharti <= qoladi).
def fixed_a(arr, x):
lo, hi = 0, len(arr) - 1 # -1 qo'shildi
while lo <= hi:
mid = lo + (hi - lo) // 2
if arr[mid] == x:
return mid
elif arr[mid] < x:
lo = mid + 1
else:
hi = mid - 1
return -1
print(fixed_a([1], 5)) # -> -1 (endi xato yo'q)
print(fixed_a([2, 4, 6, 8], 6)) # -> 2
Variant B (ochiq oraliq): hi = len(arr) ni qoldirib, sikl shartini while lo < hi ga, yangilashni hi = mid ga o'zgartiring. Har ikkalasi to'g'ri; aralashtirmaslik kerak.
5-mashq yechimi¶
upper_bound - lower_bound takror sonini beradi:
def lower_bound(arr, x):
lo, hi = 0, len(arr)
while lo < hi:
mid = lo + (hi - lo) // 2
if arr[mid] < x:
lo = mid + 1
else:
hi = mid
return lo
def upper_bound(arr, x):
lo, hi = 0, len(arr)
while lo < hi:
mid = lo + (hi - lo) // 2
if arr[mid] <= x:
lo = mid + 1
else:
hi = mid
return lo
def son(arr, x):
return upper_bound(arr, x) - lower_bound(arr, x)
print(son([1, 2, 2, 2, 3], 2)) # -> 3
print(son([1, 2, 2, 2, 3], 4)) # -> 0
Ikki O(log n) chaqiruv β jami O(log n).
6-mashq yechimi¶
bisect_right(arr, x) β x dan katta birinchi elementning indeksi. Agar u massiv uzunligiga teng bo'lsa, bunday element yo'q:
import bisect
def keyingi(arr, x):
i = bisect.bisect_right(arr, x)
return arr[i] if i < len(arr) else None
print(keyingi([1, 3, 5, 7], 4)) # -> 5
print(keyingi([1, 3, 5, 7], 7)) # -> None
print(keyingi([1, 3, 5, 7], 0)) # -> 1
7-mashq yechimi¶
lower_bound (birinchi) va upper_bound - 1 (oxirgi); element haqiqatan bor-yo'qligini tekshiramiz:
def lower_bound(arr, x):
lo, hi = 0, len(arr)
while lo < hi:
mid = lo + (hi - lo) // 2
if arr[mid] < x:
lo = mid + 1
else:
hi = mid
return lo
def upper_bound(arr, x):
lo, hi = 0, len(arr)
while lo < hi:
mid = lo + (hi - lo) // 2
if arr[mid] <= x:
lo = mid + 1
else:
hi = mid
return lo
def diapazon(arr, x):
lo = lower_bound(arr, x)
if lo == len(arr) or arr[lo] != x:
return [-1, -1]
return [lo, upper_bound(arr, x) - 1]
print(diapazon([5, 7, 7, 7, 8, 8, 10], 7)) # -> [1, 3]
print(diapazon([5, 7, 7, 7, 8, 8, 10], 6)) # -> [-1, -1]
print(diapazon([5, 7, 7, 7, 8, 8, 10], 10)) # -> [6, 6]
8-mashq yechimi¶
Bu β javob ustida binar qidiruv. Predikat: berilgan limit bilan k ta yoki kamroq talabaga sig'adimi? (ochko'zlik bilan to'ldiramiz). Javob [max(kitoblar), sum(kitoblar)] oralig'ida monoton:
def yetadimi(kitoblar, limit, k):
talaba, joriy = 1, 0
for sahifa in kitoblar:
if joriy + sahifa > limit:
talaba += 1
joriy = sahifa
else:
joriy += sahifa
return talaba <= k
def taqsimot(kitoblar, k):
lo, hi = max(kitoblar), sum(kitoblar)
while lo < hi:
mid = lo + (hi - lo) // 2
if yetadimi(kitoblar, mid, k):
hi = mid # limit yetarli -> kichikroqni sina
else:
lo = mid + 1 # yetmaydi -> kattaroq limit
return lo
print(taqsimot([12, 34, 67, 90], 2)) # -> 113
print(taqsimot([10, 20, 30, 40], 2)) # -> 60
[12,34,67,90], k=2: eng yaxshi bo'linish [12,34,67] | [90] β max(113, 90) = 113.
Murakkablik: O(n log S), bu yerda S = sum - max.
9-mashq yechimi¶
Aylantirilgan massivda har bir mid da kamida bitta yarm saralangan bo'ladi. Qaysi yarm saralanganini aniqlab, x shu saralangan yarmda yotadimi-yo'qmi tekshiramiz:
def aylangan_qidiruv(arr, x):
lo, hi = 0, len(arr) - 1
while lo <= hi:
mid = lo + (hi - lo) // 2
if arr[mid] == x:
return mid
if arr[lo] <= arr[mid]: # chap yarm saralangan
if arr[lo] <= x < arr[mid]:
hi = mid - 1
else:
lo = mid + 1
else: # o'ng yarm saralangan
if arr[mid] < x <= arr[hi]:
lo = mid + 1
else:
hi = mid - 1
return -1
print(aylangan_qidiruv([4, 5, 6, 7, 0, 1, 2], 0)) # -> 4
print(aylangan_qidiruv([4, 5, 6, 7, 0, 1, 2], 3)) # -> -1
print(aylangan_qidiruv([4, 5, 6, 7, 0, 1, 2], 6)) # -> 2
print(aylangan_qidiruv([4, 5, 6, 7, 0, 1, 2], 4)) # -> 0
Har qadamda oraliq yarmiga kamayadi β O(log n). Asosiy nayrang: arr[lo] <= arr[mid] shartidan qaysi yarm "toza" saralanganini aniqlab, faqat o'sha yarmda x ning diapazon tekshiruvini qilamiz.
β¬ οΈ Oldingi: 27 β Saralash algoritmlari Β· π README Β· Keyingi: 29 β Graf algoritmlari I: BFS, DFS va qo'llanmalar β‘οΈ