09 β Murakkablikni hisoblash: vaqt va xotira¶
β¬ οΈ Oldingi: 08 β Asimptotik notatsiya: Big-O, Ξ©, Ξ Β· π README Β· Keyingi: 10 β Rekurrent munosabatlar va Master teorema β‘οΈ
Bu bobda: Oldingi bobda Big-O notatsiyasini ta'rifladik; endi uni amalda qo'llaymiz. Tayyor koddan turib uning vaqt murakkabligini qanday topishni β amallarni sanash, hukmron a'zoni ajratish va Big-O ga o'tishni β qoidalar va misollar bilan o'rganamiz. So'ng bir algoritmning eng yaxshi, o'rtacha va eng yomon holatini farqlaymiz va nihoyat xotira (space) murakkabligiga β in-place, qo'shimcha massiv va rekursiya steki chuqurligiga β o'tamiz.
Halollik / Eslatma: Bu yerdagi barcha Big-O baholari matematik aniq va to'g'ri. O'rtacha holat tahlili soddalashtirilgan (to'liq ehtimollik hisobi emas β uni qisqacha ko'rsatamiz). Barcha Python namunalari haqiqatan ishga tushirib, chiqishi tekshirilgan.
Kirish: koddan Big-O ga¶
Oldingi bobda biz O(n), O(nΒ²), O(log n) kabi belgilarning ma'nosini o'rgandik: ular algoritmning o'sish tartibini tasvirlaydi β kirish hajmi n kattalashganda ish vaqti qanchalik tez ko'payishini. Lekin u yerda murakkabliklar bizga tayyor berilgan edi. Bu bobning asosiy savoli:
Qo'limda kod bor. Uning Big-O sini qanday topaman?
Javobi uch qadamli oddiy retsept:
1-qadam: Asosiy amallarni SANA β har bir qator necha marta bajariladi?
2-qadam: Hammasini qo'shib, T(n) ifodasini yoz (masalan 3n + 4).
3-qadam: HUKMRON a'zoni ol, konstantalarni tashla -> Big-O (masalan O(n)).
Bu retsept 7-bobdagi "amallarni sanash" g'oyasiga tayanadi va 8-bobdagi "konstantani e'tiborsiz qoldirish" qoidasi bilan tugaydi. Keling, har bir qadamni amalda ko'raylik.
Eslatma: Biz "amal" deganda doimiy vaqt (
O(1)) oladigan oddiy ishni nazarda tutamiz: qo'shish, taqqoslash, massiv katagini o'qish/yozish, o'zgaruvchiga qiymat berish. Bu β 7-bobdagi RAM modelining asosiy farazi.
Asosiy qoidalar amalda¶
Murakkablikni qo'lda hisoblash uchun bir necha oddiy qoidani bilish kifoya. Ularni eslab qolsangiz, ko'pchilik kodning Big-O sini bir qarashda ayta olasiz.
Qoida 1 β Ketma-ket bloklar: yig'indi, keyin maksimum¶
Agar kod ikkita bo'lakdan iborat bo'lsa va ular birin-ketin (biri tugagach, ikkinchisi) bajarilsa, ularning vaqtlari qo'shiladi. Ammo Big-O da yig'indining eng katta a'zosi (maksimum) hukmron bo'ladi:
Nega? Chunki n kattalashganda nΒ² n dan ancha tez o'sadi, shuning uchun katta n larda umumiy vaqtni deyarli butunlay B bloki belgilaydi. Demak:
Qoida:
O(f) + O(g) = O(max(f, g)). Ketma-ket bloklarda eng "qimmat" qism g'olib chiqadi.
Qoida 2 β Ichma-ich sikllar: ko'paytma¶
Bir sikl ichida ikkinchi sikl bo'lsa, ularning takrorlanish sonlari ko'payadi:
for i in range(n): # n marta
for j in range(n): # har biri uchun n marta
amal() # -> n * n = n^2 amal
Agar tashqi sikl n, ichki sikl m marta aylansa, jami n Β· m. Ikkalasi ham n bo'lsa, nΒ². Uchta ichma-ich sikl β nΒ³, va hokazo. Bu eng ko'p uchraydigan murakkablik manbai.
Qoida 3 β Har safar yarmiga bo'linish: logarifm¶
Agar har bir qadamda ishlanadigan hajm ikki barobar kichraysa (yoki sikl o'zgaruvchisi har safar 2 ga ko'paysa), takrorlar soni logβ n ga teng bo'ladi:
n = 16 bo'lsa: 16 -> 8 -> 4 -> 2 -> 1, ya'ni 4 qadam, chunki logβ 16 = 4. Bu β binar qidiruvning yuragidagi g'oya: qidiruv maydonini har qadamda yarmiga qisqartirib, O(log n) ga erishamiz.
Eslatma: Big-O da logarifm asosi muhim emas:
logβ n,logββ nvaln nbir-biridan faqat konstanta koeffitsienti bilan farq qiladi, u esa tashlanadi. Shuning uchun shunchakiO(log n)yozamiz.
Qoida 4 β Sikl ichida logarifmik amal: n log n¶
Agar n marta aylanadigan sikl ichida har safar log n lik ish bajarilsa, jami n Β· log n bo'ladi:
O(n log n) β samarali saralash algoritmlari (merge sort, quicksort, heapsort)ning murakkabligidir va amalda juda yaxshi hisoblanadi: nΒ² dan sezilarli tez, lekin chiziqlidan biroz sekin.
Konkret misollar: kod + tahlil¶
Endi qoidalarni real kod ustida qo'llaymiz. Har bir misol uchun: kod -> amallarni sanaymiz -> Big-O.
Misol 1 β Chiziqli qidiruv: O(n)¶
def chiziqli_qidiruv(a, target):
for i in range(len(a)):
if a[i] == target:
return i
return -1
print(chiziqli_qidiruv([3, 9, 1, 7, 5, 8], 7)) # -> 3
print(chiziqli_qidiruv([3, 9, 1, 7, 5, 8], 42)) # -> -1
Tahlil. Sikl eng yomon holatda (element yo'q yoki oxirida) n marta aylanadi. Har aylanishda doimiy son amal: bitta indekslash a[i], bitta taqqoslash == target. Demak T(n) β c Β· n, hukmron a'zo n, natija O(n) β chiziqli.
Misol 2 β Ikki ichma-ich sikl (barcha juftliklar): O(nΒ²)¶
def juftliklar(a):
juft = []
for i in range(len(a)):
for j in range(i + 1, len(a)):
juft.append((a[i], a[j]))
return juft
print(juftliklar([1, 2, 3])) # -> [(1, 2), (1, 3), (2, 3)]
Tahlil. Ikkita ichma-ich sikl bor (Qoida 2 -> ko'paytma). E'tibor bering: ichki sikl j ni i+1 dan boshlaydi, ya'ni har safar kamroq aylanadi. Lekin baribir bu O(nΒ²). Nega aynan shunday β buni keyingi misolda aniq hisoblaymiz.
Misol 3 β Uchburchak sikl: nega yarim bo'lsa ham O(nΒ²)¶
Bu β boshlovchilar eng ko'p adashadigan joy. Ichki sikl har safar kamroq aylanadigi uchun "demak bu yarmi, ya'ni O(n/2) yoki yengilroq" deb o'ylash xato. Aniq sanab ko'raylik:
def uchburchak_amallar(n):
son = 0
for i in range(n):
for j in range(i, n):
son += 1
return son
print(uchburchak_amallar(5)) # -> 15
print(uchburchak_amallar(10)) # -> 55
Tahlil. Tashqi i ning har qiymati uchun ichki sikl n β i marta aylanadi:
i |
ichki sikl necha marta (n β i, n = 5) |
|---|---|
| 0 | 5 |
| 1 | 4 |
| 2 | 3 |
| 3 | 2 |
| 4 | 1 |
| Jami | 5 + 4 + 3 + 2 + 1 = 15 |
Bu β mashhur arifmetik yig'indi:
Endi 3-qadamni qo'llaymiz: hukmron a'zo nΒ²/2, konstanta 1/2 ni va kichik n/2 ni tashlaymiz ->
T(n) = n(n+1)/2 = Ξ(nΒ²).
Ya'ni "yarim uchburchak" bo'lsa-da, o'sish tartibi baribir nΒ², faqat konstanta koeffitsienti 1/2. n ikki barobar oshsa, ish 4 barobar oshadi β to'liq kvadrat sikldagidek. Big-O da 1/2 koeffitsienti yo'qoladi.
Yuqoridagi diagrammada to'liq kvadrat (nΒ²) va uchburchak (nΒ²/2) bir-biriga taqqoslangan β ikkalasi ham bir xil parabolik o'sishga ega.
Diqqat: "Ish yarmiga kamaydi" β bu Big-O ni o'zgartirmaydi, faqat konstantani kamaytiradi. Big-O ni faqat o'sish tartibi o'zgargandagina o'zgartiramiz (masalan
nΒ²dann log nga). Yarmiga bo'lish (logarifm) bilan yarmini qo'shmaslik (konstanta) farqini chalkashtirmang.
Misol 4 β Har safar yarmlash: O(log n)¶
def necha_marta_yarmlash(n):
qadam = 0
while n > 1:
n = n // 2
qadam += 1
return qadam
print(necha_marta_yarmlash(16)) # -> 4
print(necha_marta_yarmlash(1000)) # -> 9
Trassirovka (n = 16):
| qadam | sikl boshida n |
n // 2 dan keyin |
qadam |
|---|---|---|---|
| 1 | 16 | 8 | 1 |
| 2 | 8 | 4 | 2 |
| 3 | 4 | 2 | 3 |
| 4 | 2 | 1 | 4 |
| β | 1 | sikl tugadi | 4 |
Tahlil. n har qadamda 2 ga bo'linadi, shuning uchun 1 ga yetguncha taxminan logβ n qadam kerak (Qoida 3). 16 = 2β΄ uchun aynan 4 qadam; 1000 uchun βlogβ 1000β = 9. Natija O(log n) β juda sekin o'sadigan, ajoyib murakkablik: n million bo'lsa ham atigi ~20 qadam.
Misol 5 β Aralash: O(n log n)¶
def n_log_n_ish(a):
son = 0
for x in a: # n marta
m = len(a)
while m > 1: # har biri log n marta
m = m // 2
son += 1
return son
print(n_log_n_ish([0] * 8)) # -> 24
Tahlil. Tashqi sikl n marta aylanadi; har bir aylanishda ichki while logβ n marta ishlaydi (Qoida 4 -> ko'paytma). n = 8 uchun: 8 Β· logβ 8 = 8 Β· 3 = 24, kod ham aynan 24 chiqaradi. Natija O(n log n).
Trade-off: Bir xil masalani
O(nΒ²)da ham,O(n log n)da ham yechish mumkin (masalan saralash).n = 1 000 000danΒ²taxminan10ΒΉΒ²amal (daqiqalar),n log nesa taxminan2Β·10β·amal (soniyaning ulushi). Mana shu uchun murakkablik amaliyotda muhim β bu shunchaki nazariy o'yin emas.
Eng yaxshi, o'rtacha va eng yomon holat¶
Hozirgacha biz "sikl n marta aylanadi" deb keldik. Lekin Misol 1 dagi chiziqli qidiruvga diqqat qiling: agar element birinchi katakda bo'lsa, sikl darhol to'xtaydi β bor-yo'g'i 1 amal! Demak bir xil algoritmning ish vaqti aynan qaysi kirish berilganiga bog'liq. Shuning uchun uch xil holatni ajratamiz.
- Eng yaxshi holat (best case): algoritm uchun eng qulay kirishdagi ish vaqti β eng kam amal.
- Eng yomon holat (worst case): eng noqulay kirishdagi ish vaqti β eng ko'p amal.
- O'rtacha holat (average case): barcha mumkin kirishlar bo'yicha o'rtacha ish vaqti (ehtimollikni talab qiladi).
Chiziqli qidiruv misolida (massivda n ta element, target ni qidiramiz):
| Holat | Vaziyat | Taqqoslashlar | Big-O |
|---|---|---|---|
| Eng yaxshi | target birinchi katakda |
1 | O(1) |
| O'rtacha | target o'rtada (tasodifiy joyda) |
~n/2 |
O(n) |
| Eng yomon | target oxirda yoki umuman yo'q |
n |
O(n) |
Big-O odatda eng yomon holatni bildiradi¶
Shartlashuv bo'yicha, "algoritm O(f(n))" deganda biz odatda eng yomon holatni nazarda tutamiz. Sababi amaliy: eng yomon holat β bu kafolat. "Bu algoritm hech qachon O(nΒ²) dan sekin ishlamaydi" degani β har qanday kirishda ishonchli yuqori chegara. Eng yaxshi holat esa kamdan-kam foydali: u faqat omadli kirishda nima bo'lishini aytadi, lekin yomonida himoya bermaydi.
Diqqat: "Eng yomon holat" va "Big-O" β boshqa-boshqa tushunchalar, garchi ko'pincha birga ishlatilsa ham. Big-O β yuqori chegara (har qanday holat uchun yozish mumkin: "eng yaxshi holat
O(1)"). Eng yomon holat β qaysi kirish haqida gapirayotganimiz. Ikkalasini birga aytib: "chiziqli qidiruvning eng yomon holatiΞ(n)" β eng aniq ifoda. Ξ (theta) haqida 8-bobga qarang.
O'rtacha holat β qisqacha (ehtimollik kerak)¶
O'rtacha holatni topish ehtimollik talab qiladi: barcha kirishlar bo'yicha ish vaqtining kutilgan qiymati (matematik o'rtachasi). Chiziqli qidiruvda, agar target massivda bo'lsa va uning har qanday pozitsiyada bo'lishi teng ehtimolli bo'lsa:
Demak o'rtacha ~n/2, ya'ni O(n) β eng yomon holat bilan bir xil tartib, faqat ikki marta kichik konstanta. Ko'p algoritmlarda o'rtacha va eng yomon holat bir xil tartibda bo'ladi, lekin ba'zilarida farq qiladi (klassik misol β quicksort: o'rtacha O(n log n), eng yomon O(nΒ²); buni saralash bobida ko'rasiz).
Eslatma: Aniq o'rtacha holat tahlili ko'pincha qiyin, chunki "tipik" kirishni matematik ta'riflash kerak. Shuning uchun amaliyotda dasturchilar asosan eng yomon holatga tayanadi β u har doim aniq va himoyalangan baho beradi.
Xotira (space) murakkabligi¶
Vaqt β bu algoritm tahlilining yarmi xolos. Ikkinchi yarmi β xotira: algoritm ishlash uchun qancha xotira talab qiladi? Buni ham xuddi vaqtdek Big-O bilan o'lchaymiz, faqat amallar emas, xotira katakchalarini sanaymiz.
Qo'shimcha (auxiliary) xotira vs umumiy¶
Ikki xil o'lchov bor:
- Umumiy xotira: kirish + algoritm ishlatadigan hamma narsa.
- Qo'shimcha (auxiliary) xotira: kirishdan tashqari ishlatilgan xotira β algoritmning "ish stoli".
Odatda "xotira murakkabligi" deganda qo'shimcha xotira nazarda tutiladi, chunki kirishni baribir saqlashga majburmiz; bizni qiziqtiradigani β algoritm qo'shimcha qancha joy talab qilishi.
O(1) xotira β in-place¶
Agar algoritm faqat bir nechta o'zgaruvchi (sanagichlar, vaqtinchalik qiymat) ishlatib, kirish hajmidan mustaqil joy egallasa, u O(1) qo'shimcha xotira ishlatadi. Massivni o'z joyida o'zgartiradigan algoritmlar in-place deyiladi:
def teskari_inplace(a):
chap, ong = 0, len(a) - 1
while chap < ong:
a[chap], a[ong] = a[ong], a[chap]
chap += 1
ong -= 1
return a
print(teskari_inplace([1, 2, 3, 4, 5])) # -> [5, 4, 3, 2, 1]
Bu yerda faqat chap va ong β ikki o'zgaruvchi. Massiv 5 ta yoki 5 million elementli bo'lsin, qo'shimcha xotira o'zgarmaydi -> O(1).
O(n) xotira β qo'shimcha massiv¶
Agar algoritm natija yoki nusxa uchun kirishga mutanosib yangi massiv/to'plam ochsa, qo'shimcha xotira O(n):
def teskari_yangi(a):
natija = []
for x in a:
natija.insert(0, x)
return natija
print(teskari_yangi([1, 2, 3, 4, 5])) # -> [5, 4, 3, 2, 1]
Bu yerda natija ro'yxati n ta elementgacha o'sadi -> O(n) qo'shimcha xotira. Natija bir xil bo'lsa-da ([5, 4, 3, 2, 1]), bu versiya teskari_inplace dan ko'proq xotira talab qiladi. Hash to'plam (set), BFS navbati va kirish nusxasi ham odatda O(n) xotira.
Rekursiya steki β chuqurlik = xotira¶
Boshlovchilar tez-tez unutadigan narsa: rekursiv chaqiruvlar ham xotira egallaydi. Har bir hal qilinmagan chaqiruv chaqiruvlar stekida (call stack) bitta "ramka" (frame) saqlaydi β lokal o'zgaruvchilar va qaytish manzili bilan. Demak rekursiyaning xotira murakkabligi = eng chuqur chaqiruv zanjiri uzunligi. Rekursiya stekini eslang.
def yigindi(a, i=0):
if i == len(a):
return 0
return a[i] + yigindi(a, i + 1)
print(yigindi([1, 2, 3, 4, 5])) # -> 15
Tahlil. Bu funksiya yigindi(a, 0) -> yigindi(a, 1) -> ... -> yigindi(a, n) zanjirini hosil qiladi β chuqurlik n. Hech qanday massiv ochmasa ham, stekda bir vaqtning o'zida n + 1 ta ramka turadi -> O(n) xotira. (Bu kod hatto O(1) xotirali oddiy sikl bilan yozilishi mumkin edi β rekursiya bu yerda xotira jihatidan qimmatroq.)
Aksincha, balanslangan rekursiya (har qadamda muammoni yarmiga bo'lish β masalan binar qidiruv yoki merge sort) chuqurligi log n bo'ladi -> O(log n) xotira.
| Algoritm namunasi | Qo'shimcha xotira |
|---|---|
| In-place teskari aylantirish, sanagichli o'tish | O(1) |
| Balanslangan rekursiya steki (binar qidiruv) | O(log n) |
| Natija massivi, hash to'plam, BFS navbati | O(n) |
Chiziqli rekursiya (chuqurlik n) |
O(n) |
n Γ n matritsa yaratish |
O(nΒ²) |
Diqqat:
O(n)vaqt vaO(n)xotira β ikki boshqa narsa. AlgoritmO(n)vaqtda ishlab, ammoO(1)xotira ishlatishi mumkin (in-place o'tish), yokiO(nΒ²)vaqtda ishlabO(n)xotira ishlatishi mumkin. Har doim ikkalasini alohida baholang.
Vaqtβxotira trade-off¶
Ko'pincha tezlik bilan xotira o'rtasida savdolashuv (trade-off) bor: ko'proq xotira sarflab, vaqtni tejash mumkin β yoki aksincha. Klassik vosita β memoizatsiya: hisoblangan natijalarni keshda saqlab, qayta hisoblashdan qutulamiz.
def fib_memo(n, kesh=None):
if kesh is None:
kesh = {}
if n < 2:
return n
if n in kesh:
return kesh[n]
kesh[n] = fib_memo(n - 1, kesh) + fib_memo(n - 2, kesh)
return kesh[n]
print(fib_memo(30)) # -> 832040
Oddiy (memoizatsiyasiz) rekursiv Fibonachchi O(2βΏ) vaqt talab qiladi β chunki bir xil qiymatlarni qayta-qayta hisoblaydi. Kesh qo'shsak, har bir fib(k) faqat bir marta hisoblanadi -> vaqt O(n) ga tushadi. Lekin buning evaziga kesh lug'ati O(n) xotira egallaydi. Mana shu β sof vaqtβxotira trade-off: O(2βΏ) vaqtni O(n) vaqtga almashtirdik, narxi O(n) qo'shimcha xotira. Bu g'oya β dinamik dasturlashning poydevori.
Trade-off: Keshlash, oldindan hisoblangan jadvallar (lookup table), indekslar β bularning hammasi "xotira sarflab, tezlik ol" tamoyiliga asoslanadi. Doim savol bering: menda nima ko'proq cheklangan β vaqtmi yoki xotira? Javob arxitektura qaroringizni belgilaydi.
Amaliy maslahat: nimaga e'tibor berish kerak¶
Murakkablik tahlili β kuchli qurol, lekin uni to'g'ri vaqtda ishlating:
- Avval o'sish tartibini ko'r. Real loyihada
nkatta bo'lgandaO(nΒ²)vaO(n log n)farqi hayotiy ahamiyatga ega. Konstantalar (2x, 3x tezlik) esa odatda keyingi tashvish. - Vaqt va xotira β ikkalasini ham. "Tez, lekin xotirani yeydi" yechim cheklangan qurilmada (telefon, mikrokontroller) yaroqsiz bo'lishi mumkin.
- Eng yomon holatni bilib tur. Tizimingiz "odatda tez", lekin yiliga bir marta eng yomon kirishda qotib qolsa β bu jiddiy muammo.
- Lekin erta optimallashtirishdan ehtiyot bo'l. Donald Knuth aytganidek: "erta optimallashtirish β barcha yomonliklarning ildizi" (ma'lum bir ma'noda). Avval to'g'ri va o'qiladigan kod yozing; haqiqiy "issiq nuqta" (bottleneck)ni o'lchov (profiling) bilan toping; faqat shu yerni optimallashtiring.
nhar doim 10 dan oshmaydigan joyniO(nΒ²)danO(n log n)ga ko'chirish β ko'pincha behuda mehnat.
Anti-pattern: Har bir satrni "tezroq" qilishga urinib, kodni o'qib bo'lmas holga keltirish. Murakkablik β ko'lamlanadigan (scalable) qismlar uchun; qolgan joyda aniqlik muhimroq. To'g'ri Big-O ni tanlash > konstantani qisish.
Asosiy g'oyalar (bobni qisqacha)¶
- Koddan Big-O ga uch qadam: amallarni sana ->
T(n)ifodasini yoz -> hukmron a'zoni ol. - Ketma-ket bloklar qo'shiladi (
O(max)), ichma-ich sikllar ko'payadi (n Β· m). - Har safar yarmlash =
O(log n); sikl ichida logarifmik amal =O(n log n). - Uchburchak sikl (ichki sikl
j = i..n)n(n+1)/2 = Ξ(nΒ²): yarim bo'lsa ham o'sish tartibinΒ², faqat konstanta1/2. Yarmiga bo'lish (log) bilan yarmini qo'shmaslik (konstanta) β boshqa narsa. - Eng yaxshi / o'rtacha / eng yomon holat farqlanadi. Big-O odatda eng yomon holatni bildiradi, chunki u kafolat beradi. O'rtacha holat ehtimollik talab qiladi.
- Xotira murakkabligi ham muhim:
O(1)(in-place),O(n)(qo'shimcha massiv),O(log n)(balanslangan rekursiya steki). Rekursiya chuqurligi = stek xotirasi. - Vaqt va xotira β alohida baholanadi va ko'pincha trade-off qilinadi (memoizatsiya: xotira evaziga tezlik).
- Avval to'g'ri kod, keyin o'lchov, oxirida β kerak bo'lsa β optimallashtirish. Erta optimallashtirishdan saqlaning.
Mashqlar¶
Oson¶
1-mashq. Quyidagi kod parchasining vaqt murakkabligini Big-O da ayting:
2-mashq. Bu kodning vaqt murakkabligi qanday?
3-mashq. Quyidagi funksiyaning qo'shimcha xotira murakkabligini ayting (kirish massivini hisobga olmang):
O'rta¶
4-mashq. Quyidagi kodning vaqt murakkabligini toping va nega shunday ekanini tushuntiring:
5-mashq. target ni saralangan massivda chiziqli qidiruv bilan qidiramiz va element topilsa yoki a[i] > target bo'lsa to'xtaymiz. Bu algoritmning eng yaxshi va eng yomon holatini va ularning Big-O sini ayting.
6-mashq. Quyidagi kod ketma-ket ikki blokdan iborat. Umumiy vaqt murakkabligi qanday?
# A bloki
for x in a: # n marta
print(x)
# B bloki
for i in range(n):
for j in range(n):
print(i * j)
Qiyin¶
7-mashq. Quyidagi sikl aniq necha marta son += 1 ni bajaradi (yopiq formula bilan), va Big-O si nima?
8-mashq. Quyidagi rekursiv funksiyaning vaqt va xotira murakkabligini alohida ayting. Xotira nega aynan shunday?
9-mashq. Sizga massiv berilgan. Ikki versiya bor: (a) ikki ichma-ich sikl bilan massivda takrorlangan element borligini tekshirish, (b) hash to'plam (set) bilan tekshirish. Har birining vaqt va xotira murakkabligini ayting va bu yerdagi vaqtβxotira trade-offini izohlang.
Yechimlar
1-mashq yechimi¶
Bitta sikl, n ta element bo'yicha bir marta aylanadi; har aylanishda doimiy ish (+=). Demak T(n) = c Β· n, murakkablik O(n) β chiziqli.
2-mashq yechimi¶
Ikkita ichma-ich sikl, ikkalasi ham n marta (Qoida 2 -> ko'paytma): n Β· n = nΒ². Murakkablik O(nΒ²).
3-mashq yechimi¶
Faqat bitta qo'shimcha o'zgaruvchi β eng_katta (va sikl o'zgaruvchisi x). Massiv qanchalik katta bo'lsa ham qo'shimcha joy o'zgarmaydi. Qo'shimcha xotira O(1) β in-place o'tish.
4-mashq yechimi¶
Tashqi sikl n marta aylanadi. Ichki while har safar m ni 2 ga bo'ladi, ya'ni logβ n marta ishlaydi (Qoida 3). Ular ichma-ich, demak ko'payadi (Qoida 4): n Β· logβ n. Murakkablik O(n log n). Sababi: "yarmiga bo'lish" logarifm beradi, va u n martalik tashqi sikl ichida takrorlanadi.
5-mashq yechimi¶
- Eng yaxshi holat:
targetbirinchi katakda (a[0] == target) yokia[0] > target(darhol to'xtash) β 1 ta taqqoslash,O(1). - Eng yomon holat:
targetmassivning oxirgi elementidan katta yoki teng β sikl oxirigacha boradi,nta taqqoslash,O(n).
Saralanganlik o'rtacha holatda erta to'xtashga imkon beradi, lekin eng yomon holatni O(n) dan tushirmaydi. (Saralangan massivda haqiqiy tezlik uchun binar qidiruv β O(log n) β kerak.)
6-mashq yechimi¶
A bloki O(n), B bloki ikki ichma-ich sikl O(nΒ²). Ular ketma-ket, demak qo'shiladi (Qoida 1): O(n) + O(nΒ²) = O(max(n, nΒ²)) = O(nΒ²). Umumiy murakkablik O(nΒ²) β kattaroq blok hukmron.
7-mashq yechimi¶
Tashqi i uchun ichki sikl j ni i + 1 dan n β 1 gacha, ya'ni n β 1 β i marta aylanadi:
n = 5 uchun: 4 + 3 + 2 + 1 + 0 = 10 = 5Β·4/2. Yopiq formula n(nβ1)/2. Hukmron a'zo nΒ²/2, demak Big-O O(nΒ²) (aniqrog'i Ξ(nΒ²)). (Bu β matnda ko'rilgan uchburchak siklning j = i+1 dan boshlangan varianti.)
8-mashq yechimi¶
- Vaqt: funksiya har element uchun bir marta chaqiriladi va bir marta
printqiladi β jaminta chaqiruv, har biriO(1). VaqtO(n). - Xotira: rekursiya
teskari_chop(a, 0) -> ... -> teskari_chop(a, n)zanjirini hosil qiladi, chuqurlikn.printlar bajarilmasdan oldin barcha chaqiruvlar stekka tushadi (chunki rekursiv chaqiruvprintdan oldin), shuning uchun bir vaqtning o'zidan + 1ramka stekda turadi. XotiraO(n)β rekursiya steki chuqurligi tufayli, garchi hech qanday massiv ochmasa ham.
9-mashq yechimi¶
(a) Ikki ichma-ich sikl:
- Vaqt:
O(nΒ²)(har juftlikni tekshiradi, uchburchak sikl). - Xotira:
O(1)β faqat sanagichlar, qo'shimcha struktura yo'q.
(b) Hash to'plam:
def birinchi_takror(a):
korilgan = set()
for x in a:
if x in korilgan:
return x
korilgan.add(x)
return None
print(birinchi_takror([2, 5, 1, 5, 3])) # -> 5
- Vaqt:
O(n)β har element bir marta, set ichida tekshirish o'rtachaO(1)(hash jadval). - Xotira:
O(n)β set eng yomon holatdanta elementni saqlaydi.
Trade-off: (a) versiya xotira tejaydi (O(1)) lekin vaqt qimmat (O(nΒ²)); (b) versiya O(n) xotira "sotib olib" vaqtni O(n) ga tushiradi. Bu β klassik vaqtβxotira savdolashuvi: agar n katta va xotira yetarli bo'lsa, (b) ancha tezroq; agar xotira juda cheklangan bo'lsa, (a) afzal. Aksariyat amaliy holatlarda (b) β to'g'ri tanlov.
β¬ οΈ Oldingi: 08 β Asimptotik notatsiya: Big-O, Ξ©, Ξ Β· π README Β· Keyingi: 10 β Rekurrent munosabatlar va Master teorema β‘οΈ