12 β Massiv va dinamik massiv¶
β¬ οΈ Oldingi: 11 β Amortizatsiyalangan tahlil Β· π README Β· Keyingi: 13 β Bog'langan ro'yxatlar (linked list) β‘οΈ
Bu bobda: Kitobning III qismi β ma'lumotlar strukturalari β boshlanadi. Eng asosiy va eng ko'p ishlatiladigan strukturadan, massivdan, boshlaymiz. Massiv xotirada qanday yotishini, nega indekslash
O(1)ekanini, qaysi amallar arzon va qaysilariO(n)ekanini ko'ramiz. So'ng o'lchami o'sib boradigan dinamik massiv (Pythonlist, C++vector, JavaArrayList) ichkarida qanday ishlashini β sig'im, ikkilantirish, nusxalash β ochib beramiz.Halollik / Eslatma: Bu yerdagi murakkablik chegaralari (indekslash
O(1), o'rtaga qo'shishO(n),appendamortizedO(1)) matematik aniq; amortizedO(1)isboti to'liq 11-bobda berilgan. Manzil hisobi (baza + i*hajm) past darajadagi (C kabi) tillar uchun to'g'ridan-to'g'ri, yuqori tillarda esa interpretator orqasidan ham shu g'oya ishlaydi. CPythonlistichidagi aniq sig'im o'sish jadvali β implementatsiya tafsiloti va versiyaga qarab o'zgarishi mumkin; Python namunalari haqiqatan ishga tushirib tekshirilgan, chiqishlar haqiqiy.
Ma'lumotlar strukturasi nima va nega kerak¶
Shu paytgacha biz algoritmlar β amallar ketma-ketligi β haqida gapirdik. Endi savol boshqacha: bu amallar qaysi ma'lumot ustida ishlaydi va u ma'lumot xotirada qanday tashkillangan?
Ma'lumotlar strukturasi β bu ma'lumotni saqlash va unga amal qilishni tashkillashtirishning usuli. U ikki narsani belgilaydi: (1) elementlar xotirada qanday yotadi, (2) qaysi amallar (qo'shish, o'chirish, qidirish, kirish) qanchalik tez bajariladi.
Intuitsiya: Bir xil kitoblarni uyga olib kelsangiz, ularni qanday joylashtirsangiz β bir uyumga tashlaysizmi, alifbo bo'yicha javonga teraysizmi, indeksli kartoteka tuzasizmi β keyin "falon kitob qayerda?" degan savolga javob tezligi shunga bog'liq. Ma'lumot bir xil, lekin tashkilot amallar narxini belgilaydi.
Shuning uchun "qaysi struktura yaxshi?" degan savol ma'nosiz β to'g'ri savol: qaysi amallar tez-tez kerak bo'ladi? Struktura tanlash β shu amallarni arzonlashtirish uchun qilinadigan kelishuv (trade-off). Massiv bir amallarni juda tez, boshqalarini sekin bajaradi; bog'langan ro'yxat aksincha. Bu qismda har bir strukturani aynan shu nuqtai nazardan o'rganamiz.
Massiv (array): uzluksiz xotira¶
Massiv β bu xotirada uzluksiz (contiguous) joylashgan, bir xil tipdagi elementlar to'plami. "Uzluksiz" degani β elementlar yonma-yon, oraliqsiz yotadi. "Bir xil tip" degani β har bir element bir xil bayt soni egallaydi (masalan, har bir 32-bitli butun son 4 bayt).
Aynan shu ikki xususiyat massivning eng kuchli tomonini beradi.
Nega indekslash O(1)¶
Massivning birinchi elementi qaysidir bazaviy manzilda (base address) yotadi β uni baza deylik. Har bir element hajm bayt egallaydi. U holda i-elementning manzili oddiy arifmetika bilan topiladi:
Bu β bitta ko'paytirish va bitta qo'shish. Kirish kattaligiga (n) umuman bog'liq emas: massivda million ta element bo'lsa ham, a[999999] ga a[3] bilan bir xil tezlikda yetib boramiz. Hech qayerda qidirib yurmaymiz β indeksdan to'g'ridan-to'g'ri manzilga sakraymiz. Shuning uchun:
Massivga indeks bo'yicha kirish va o'zgartirish β
O(1)(doimiy vaqt). Bu massivning belgilovchi xususiyati va "tasodifiy kirish" (random access) deb ataladi: istalgan elementga bir xil narxda yetiladi.
Diagrammadagi misolda baza = 1000, hajm = 4, demak manzil(3) = 1000 + 3*4 = 1012 β va aynan o'sha manzildagi qiymat 99.
Statik massiv¶
Eng sof ko'rinishida massiv statik bo'ladi: o'lchami yaratilganda belgilanadi va keyin o'zgarmaydi. C da int a[10]; β bu xotirada 10 ta butun son uchun aniq blok ajratadi, na ortiq, na kam. Statik massivning afzalligi β soddalik va tezlik; kamchiligi β oldindan nechta element kerakligini bilish shart. Agar 10 ta joy ajratib, 11-element kerak bo'lib qolsa β muammo. Aynan shu muammoni keyinroq dinamik massiv hal qiladi.
Asosiy amallar va ularning murakkabligi¶
Massiv ustida tipik amallarni ko'rib chiqaylik. Har birining narxini aniq Big-O da beramiz.
1. Kirish / o'zgartirish a[i] β O(1). Yuqorida ko'rdik: manzil hisobi bir amal.
2. Qidiruv β joylashuviga bog'liq.
- Saralanmagan massivda
xni qidirish: birma-bir tekshirib chiqishdan boshqa yo'l yo'q (chiziqli qidiruv) β eng yomon holatda hammanta elementni ko'ramiz,O(n). - Saralangan massivda esa har solishtirishda qidiruv oralig'ining yarmini tashlab yuborishimiz mumkin β binar qidiruv,
O(log n). Bu massivning yana bir ustunligi: uzluksiz joylashuv tufayli o'rtadagi elementgaO(1)da yetib, "chap yarmidami yoki o'ng yarmida?" degan qarorni darrov beramiz. Binar qidiruvni 28-bobda batafsil ko'ramiz.
3. Oxiriga qo'shish β joy bo'lsa O(1). Agar massivda bo'sh katak qolgan bo'lsa, yangi elementni shunchaki keyingi bo'sh joyga yozamiz β bir amal. (Dinamik massivda joy tugaganda bu vaqti-vaqti bilan qimmatga tushadi β keyingi bo'limga qarang.)
4. Boshiga yoki o'rtaga qo'shish β O(n). Mana bu yer massivning zaif tomoni. Agar i-pozitsiyaga yangi element qo'ymoqchi bo'lsak, undan keyingi barcha elementlarni bittadan o'ngga siljitishimiz kerak β aks holda yangi element uchun joy yo'q (uzluksizlikni buzolmaymiz). Eng yomon holatda (boshiga qo'shish) n ta element siljiydi β O(n).
[10, 20, 30, 40] ga 1-pozitsiyaga 99 qo'shamiz:
bosqich: 40 ni o'ngga -> [10, 20, 30, _, 40]
30 ni o'ngga -> [10, 20, _, 30, 40]
20 ni o'ngga -> [10, _, 20, 30, 40]
99 ni joyiga -> [10, 99, 20, 30, 40]
5. Oxiridan o'chirish β O(1). Hech narsani siljitish shart emas, faqat o'lchamni bittaga kamaytiramiz.
6. Boshidan yoki o'rtadan o'chirish β O(n). Element olib tashlangach hosil bo'lgan "teshik"ni yopish uchun undan keyingi hamma elementlarni bittadan chapga siljitamiz β yana O(n).
Xulosa: Massiv kirish va oxirgi tomondagi amallar uchun ajoyib (
O(1)), lekin bosh/o'rta tomonda qo'shish-o'chirish uchun yomon (O(n)), chunki uzluksizlikni saqlash uchun siljitish kerak.
Dinamik massiv (dynamic array)¶
Statik massivning muammosi β qat'iy o'lcham. Amalda biz ko'pincha nechta element kelishini oldindan bilmaymiz. Dinamik massiv shu muammoni hal qiladi: u o'sib boradi. Python list, C++ std::vector, Java ArrayList, JavaScript Array β hammasi dinamik massiv.
Sehr yo'q: ichkarida baribir statik (qat'iy o'lchamli) blok yotadi. Hiyla ikkita sonni alohida saqlashda:
- sig'im (capacity) β ajratilgan blokda jami nechta katak bor;
- o'lcham (size) β hozir nechta katak haqiqatan to'ldirilgan.
Doimo size β€ capacity. Massivda hali bo'sh joy bor (size < capacity) ekan, append shunchaki keyingi bo'sh katakka yozadi β O(1). Lekin size == capacity bo'lib qolsa (blok to'ldi), yangi, kattaroq blok ajratiladi, eski elementlar unga nusxalanadi, va eski blok tashlab yuboriladi.
Odatda yangi sig'im eskisining 2 baravari qilib olinadi (ba'zi tizimlarda 1.5 baravar). Demak bitta append ikki xil narxga ega bo'lishi mumkin:
- ko'p hollarda β
O(1)(bo'sh joyga yozish); - vaqti-vaqti bilan, blok to'lganda β
O(n)(yangi blok +nta elementni nusxalash).
Append nega amortized O(1)¶
To'g'ridan-to'g'ri qarasak qo'rqinchli: bitta append O(n) bo'lishi mumkin, demak n ta append O(nΒ²) emasmi? Yo'q. Sababini 11-bobda to'liq isbotladik; bu yerda qisqa takror.
Sig'im 1, 2, 4, 8, β¦, n tarzida ikkilanadi, shuning uchun nusxalashlar faqat shu chegaralarda sodir bo'ladi. n ta append davomida jami nusxalanadigan elementlar soni:
Bu geometrik yig'indi β u 2n dan kichik, ya'ni O(n). Demak jami n ta appendning narxi O(n), har bittasiga O(n)/n = O(1) amortized to'g'ri keladi. Qimmat amallar bor, lekin ular shu qadar kam-ki, narxi arzon amallar orasida "yutib yuboriladi".
Diqqat: Amortized
O(1)β bu o'rtacha ketma-ketlik narxi haqida kafolat, bitta alohidaappendhaqida emas. Real vaqtli tizimda, har bir amal qattiq vaqt chegarasiga sig'ishi shart bo'lsa, "ba'zan bittaappendO(n)bo'ladi" yetarli bo'lmasligi mumkin.
Nega 2x (yoki 1.5x) β kelishuv¶
Nega aynan ikkilantirish? Nega har safar bittaga emas yoki uchtaga emas?
- Agar sig'imni har safar konstantaga (masalan, +1 yoki +10 ga) oshirsak, ikkilantirishlar tez-tez sodir bo'ladi va jami nusxalash
1 + 2 + 3 + β¦ + n β nΒ²/2, ya'niO(nΒ²)βappendamortizedO(n)bo'lib ketadi. Falokat. - Agar ko'paytma faktor (2x, 1.5x) ishlatsak, nusxalashlar geometrik kamayadi, jami
O(n)bo'ladi va amortizedO(1)chiqadi.
Demak faktor 1 dan katta bo'lishi shart. Aniq qiymat β 2 yoki 1.5 β bu yana bir trade-off:
Trade-off: Kattaroq faktor (2x) ikkilantirishlarni kamaytiradi (kamroq nusxalash, biroz tezroq), lekin ko'proq sarflanmagan xotira qoldiradi (eng yomon holatda massiv yarim bo'sh bo'lishi mumkin). Kichikroq faktor (1.5x) xotirani tejaydi, lekin tez-tez nusxalaydi va eski blokdan bo'shagan joyni qayta ishlatishga ko'proq imkon beradi. Ikkalasi ham amortized
O(1); tanlov xotira va vaqt o'rtasidagi muvozanat.
Quyida ikkala faktorni sonlar bilan taqqoslaymiz.
Ko'p o'lchovli massiv (matritsa)¶
Ko'pincha bizga jadval (matritsa) kerak β qatorlar va ustunlar. Mantiqan bu ikki o'lchovli, lekin xotira bir o'lchovli (chiziqli). Shuning uchun 2D massiv ham bir o'lchovli blokga yoyiladi. Eng keng tarqalgan usul β row-major (qatorlab): avval birinchi qatorning hamma ustunlari, keyin ikkinchi qatorniki, va hokazo.
Mantiqiy matritsa (2 qator, 3 ustun):
[ 1 2 3 ]
[ 4 5 6 ]
Xotirada (row-major) chiziqli:
1 2 3 4 5 6
^qator0^ ^qator1^
grid[q][u] elementining chiziqli o'rni oddiy formula bilan topiladi (U β ustunlar soni):
Masalan grid[1][2] uchun 1*3 + 2 = 5 β chiziqli blokdagi 5-o'rin (0-dan boshlab), ya'ni qiymat 6. Bu yana O(1) kirish: ko'p o'lchov bo'lsa-da, manzil bitta arifmetik ifoda bilan hisoblanadi.
Python list β aslida dinamik massiv¶
Muhim fakt: CPython da list β bu dinamik massiv (bog'langan ro'yxat EMAS, nomiga qaramay). Ichkarida u elementlarga ko'rsatkichlardan iborat uzluksiz blokni saqlaydi va to'lganda sig'imini oshiradi. Shuning uchun list amallarining murakkabligi aynan yuqorida ko'rganimizdek:
| Amal | Murakkablik | Izoh |
|---|---|---|
a[i] (kirish/o'zgartirish) |
O(1) |
tasodifiy kirish |
a.append(x) |
O(1) amortized |
oxiriga, ba'zan ikkilantirish |
a.pop() (oxiridan) |
O(1) |
siljitish yo'q |
a.insert(0, x) (boshiga) |
O(n) |
hamma element o'ngga siljiydi |
a.pop(0) (boshidan) |
O(n) |
hamma element chapga siljiydi |
x in a (qidiruv) |
O(n) |
chiziqli qidiruv |
len(a) |
O(1) |
o'lcham alohida saqlanadi |
Anti-pattern:
a.pop(0)yokia.insert(0, x)ni siklda ishlatish β bu har galO(n), demak butun siklO(nΒ²). Agar ikkala uchdan ham tez qo'shib-o'chirish kerak bo'lsa,listo'rnigacollections.dequeishlating (14-bob) β uning ikkala uchi hamO(1).
Massiv qachon to'g'ri tanlov, qachon emas¶
Massivni tanlang, agar:
- indeks bo'yicha tez-tez kirish kerak bo'lsa (
a[i]βO(1)); - elementlar asosan oxiridan qo'shilib-o'chirilsa (stek kabi);
- xotira zichligi va kesh-do'stlik muhim bo'lsa (uzluksiz blok protsessor keshida yaxshi yotadi).
Massivdan qoching, agar:
- ko'p marta boshiga yoki o'rtaga qo'shish-o'chirish kerak bo'lsa (har biri
O(n)siljitish); - ma'lumot o'lchami juda katta va kutilmaganda o'zgarsa-yu, qayta-nusxalash qimmatga tushsa.
Aynan shu "o'rtaga qo'shish qimmat" muammosini keyingi struktura β bog'langan ro'yxat β hal qiladi: u elementlarni uzluksiz saqlamaydi, balki har biri keyingisiga ko'rsatkich bilan bog'lanadi, shuning uchun o'rtaga qo'shish O(1) bo'ladi (lekin indekslash O(n) bo'lib qoladi β qarama-qarshi kelishuv). Buni 13-bobda ko'ramiz.
Python misollar¶
Avval β eng oddiy: indekslash va o'zgartirish O(1).
# 1. Indekslash O(1)
xs = [42, 17, 8, 99, 23, 5, 61]
print(xs[3]) # -> 99
xs[3] = 100
print(xs[3]) # -> 100
print(len(xs)) # -> 7
Endi dinamik o'sishni o'z ko'zimiz bilan ko'ramiz. sys.getsizeof list obyekti egallagan baytlarni qaytaradi; append qilgan sayin kuzatib borsak, sig'im qachon "sakrashini" sezamiz.
import sys
# CPython listi to'lganda ichki sig'imni oshiradi.
# getsizeof bilan baytlardagi o'sishni kuzatamiz.
xs = []
prev = sys.getsizeof(xs)
print("boshlang'ich:", prev, "bayt")
for i in range(17):
xs.append(i)
cur = sys.getsizeof(xs)
if cur != prev: # sig'im sakradi
print(f"n={len(xs):2d} -> {cur} bayt (sig'im oshdi)")
prev = cur
# Mumkin chiqish (aniq raqamlar platforma/versiyaga bog'liq):
# boshlang'ich: 56 bayt
# n= 1 -> 88 bayt (sig'im oshdi)
# n= 5 -> 120 bayt (sig'im oshdi)
# n= 9 -> 184 bayt (sig'im oshdi)
# n=17 -> 248 bayt (sig'im oshdi)
E'tibor bering: sig'im sakrashlari tobora kamroq uchraydi (n=1, 5, 9, 17 da). Aynan shu siyraklik appendni amortized O(1) qiladi.
Eslatma: Yuqoridagi aniq bayt raqamlari CPython versiyasi va platformaga bog'liq β shuning uchun ularni "mumkin chiqish" deb berdik. Muhimi β naqsh: sakrashlar siyraklashib boradi. Quyidagi o'z implementatsiyamiz esa har doim aynan bir xil, bashorat qilinadigan natija beradi.
Endi dinamik massivni 0 dan o'zimiz yozamiz β sig'im, o'lcham va nusxalash sanagichi bilan. Bu mexanizmni butunlay ochib beradi.
# Dinamik massivni 0 dan implement qilamiz.
# Ichkarida statik blok bo'lib, size va capacity ni o'zimiz boshqaramiz.
class DinamikMassiv:
def __init__(self):
self._cap = 1
self._n = 0
self._data = [None] * self._cap
self.kochirishlar = 0 # jami nechta element nusxalandi
def __len__(self):
return self._n
def __getitem__(self, i):
if not 0 <= i < self._n:
raise IndexError("indeks chegaradan tashqarida")
return self._data[i]
def _resize(self, yangi_cap):
yangi = [None] * yangi_cap
for i in range(self._n): # eski elementlarni nusxalash β O(n)
yangi[i] = self._data[i]
self.kochirishlar += 1
self._data = yangi
self._cap = yangi_cap
def append(self, x):
if self._n == self._cap: # to'ldi -> ikkilantirish
self._resize(2 * self._cap)
self._data[self._n] = x
self._n += 1
m = DinamikMassiv()
for i in range(16):
m.append(i)
print("size =", len(m)) # -> size = 16
print("kochirishlar =", m.kochirishlar) # -> kochirishlar = 15
print("element[10] =", m[10]) # -> element[10] = 10
16 ta append uchun jami atigi 15 ta nusxalash bo'ldi (1+2+4+8). Bu 2*16 = 32 dan kichik β geometrik yig'indi < 2n chegarasini o'z ko'zimiz bilan tasdiqladik. Massiv kattalashganda ham bu nisbat saqlanadi, shuning uchun append amortized O(1).
Nihoyat, siljitishni va 2D indekslashni ko'rsatuvchi qisqa misol:
# insert va delete o'rtaga: nechta element siljiydi
xs = [10, 20, 30, 40, 50]
xs.insert(1, 99) # 1-indeksga qo'shamiz: 20,30,40,50 (4 ta) siljiydi
print(xs) # -> [10, 99, 20, 30, 40, 50]
del xs[1] # qaytarib o'chiramiz
print(xs) # -> [10, 20, 30, 40, 50]
# 2D massiv (matritsa) row-major
grid = [[1, 2, 3],
[4, 5, 6]]
print(grid[1][2]) # -> 6
# row-major: chiziqli indeks = qator*ustunlar_soni + ustun
qator, ustun, U = 1, 2, 3
print(qator * U + ustun) # -> 5 (0-dan boshlangan chiziqli o'rin)
Asosiy g'oyalar (bobni qisqacha)¶
- Massiv β uzluksiz xotirada yotgan, bir xil tipdagi elementlar. Indekslash
manzil(i) = baza + i*hajmformulasi bilanO(1)(tasodifiy kirish). - Kirish/o'zgartirish
O(1); saralanmagan qidiruvO(n), saralangan binar qidiruvO(log n). - Oxirga qo'shish/o'chirish
O(1); bosh/o'rtaga qo'shish-o'chirishO(n)β uzluksizlikni saqlash uchun siljitish kerak. - Dinamik massiv o'sib boradi:
sizevacapacityfarqlanadi; to'lganda kattaroq (odatda 2x) blok ajratilib, elementlar nusxalanadi. appendbitta amaldaO(n)bo'lishi mumkin, lekin geometrik o'sish tufayli amortizedO(1)(jami nusxalash< 2n).- Faktor 1 dan katta bo'lishi shart; 2x vs 1.5x β vaqt va sarflanmagan xotira o'rtasidagi kelishuv.
- Python
listβ aynan dinamik massiv.append/pop()O(1),insert(0)/pop(0)O(n).
Mashqlar¶
Oson¶
1-mashq. Bazaviy manzili 2000, har bir element 8 bayt bo'lgan massiv berilgan. manzil = baza + i*hajm formulasidan foydalanib, a[0], a[5] va a[9] elementlarining xotira manzilini hisoblang.
2-mashq. Quyidagi har bir amal uchun massivdagi murakkablikni Big-O da ayting va bir jumlada sababini izohlang: (a) a[7] ni o'qish; (b) saralanmagan massivdan x ni qidirish; (c) dinamik massiv oxiriga append; (d) massiv boshiga element qo'shish.
O'rta¶
3-mashq. 7 elementli massivga 2-indeks (0-dan boshlab) pozitsiyasiga yangi element insert qilinmoqda. Nechta mavjud element o'ngga siljishi kerak? Umumiy formula bilan ayting: n elementli massivga i-pozitsiyaga qo'shsak, nechta siljish bo'ladi?
4-mashq. 4 qator, 5 ustunli matritsa row-major tartibda chiziqli blokda yotibdi. grid[2][3] elementi chiziqli blokda nechinchi o'rinda (0-dan boshlab)? Formulani yozing va qiymatni hisoblang.
5-mashq. Dasturchi n ta elementni list boshiga birma-bir a.insert(0, x) bilan qo'shmoqda. Butun sikl qaysi murakkablikda ishlaydi? Nega? Qanday qilib uni O(n) ga tushirish mumkin?
Qiyin¶
6-mashq. Dinamik massivni 0 dan implement qiling: append metodi bilan, sig'im to'lganda 2 baravar o'sadigan. Qo'shimcha kochirishlar sanagichi bilan, n = 16 element qo'shilganda jami nechta nusxalash bo'lganini chiqaring.
7-mashq. O'sish faktorini taqqoslang: n = 1000 element qo'shilganda 2x va 1.5x strategiyalari jami nechta element nusxalaydi va oxirida qancha sarflanmagan (isrof) katak qoladi? Qaysi biri kamroq nusxalaydi, qaysi biri kamroq xotira isrof qiladi? Natijani izohlang.
8-mashq. Massivni joyida (in-place, O(1) qo'shimcha xotira) teskari o'giradigan funksiya yozing. Toq va juft uzunlikdagi massivlarda ishlashini tekshiring. Murakkabligi qancha?
Yechimlar
1-mashq yechimi¶
manzil(i) = 2000 + i*8:
a[0]β2000 + 0*8 = 2000a[5]β2000 + 5*8 = 2040a[9]β2000 + 9*8 = 2072
baza, hajm = 2000, 8
for i in (0, 5, 9):
print(f"manzil({i}) = {baza + i*hajm}")
# manzil(0) = 2000
# manzil(5) = 2040
# manzil(9) = 2072
Har bir manzil bitta ko'paytirish va qo'shish bilan topiladi β shuning uchun kirish O(1).
2-mashq yechimi¶
- (a)
a[7]ni o'qish βO(1). Manzil bevosita formula bilan hisoblanadi,nga bog'liq emas. - (b) Saralanmagan qidiruv β
O(n). Tartib yo'q, shuning uchun eng yomon holatda hammanta elementni ko'rib chiqamiz. - (c)
appendβO(1)amortized. Odatda bo'sh joyga yozish (O(1)); ba'zan sig'im to'lib, ikkilantirish + nusxalash (O(n)), lekin u shu qadar kam-ki, o'rtachaO(1). - (d) Boshiga qo'shish β
O(n). Mavjudnta elementning hammasi bittadan o'ngga siljishi kerak.
3-mashq yechimi¶
2-pozitsiyaga qo'yganimizda, 2, 3, 4, 5, 6 indeksdagi elementlar (jami 5 ta) o'ngga siljiydi. Umumiy formula: n elementli massivga i-pozitsiyaga qo'shsak, i dan keyingi (i ham kiradi) hamma element siljiydi, ya'ni n - i ta.
Bu yerda n - i = 7 - 2 = 5. Eng yomon holat β boshiga qo'shish (i = 0), n ta siljish, O(n).
4-mashq yechimi¶
Row-major formulasi: chiziqli = q * U + u, bu yerda U β ustunlar soni (5).
grid[2][3] β 2 * 5 + 3 = 13.
Demak element chiziqli blokda 13-o'rinda (0-dan boshlab) yotadi.
5-mashq yechimi¶
Har bir a.insert(0, x) β O(n), chunki mavjud elementlarning hammasi o'ngga siljiydi. Buni n marta takrorlasak, jami 0 + 1 + 2 + β¦ + (n-1) β nΒ²/2 = O(nΒ²).
Yechim: ikki uchi ham O(1) bo'lgan collections.deque ishlatish va appendleft qilish β butun jarayon O(n). Yoki elementlarni oxiriga append qilib (O(n) jami), so'ng bir marta teskari o'girish.
from collections import deque
d = deque()
for x in range(5):
d.appendleft(x) # har biri O(1)
print(list(d)) # -> [4, 3, 2, 1, 0]
6-mashq yechimi¶
class DinamikMassiv:
def __init__(self):
self._cap = 1
self._n = 0
self._data = [None] * self._cap
self.kochirishlar = 0
def __len__(self):
return self._n
def _resize(self, yangi_cap):
yangi = [None] * yangi_cap
for i in range(self._n):
yangi[i] = self._data[i]
self.kochirishlar += 1
self._data = yangi
self._cap = yangi_cap
def append(self, x):
if self._n == self._cap:
self._resize(2 * self._cap)
self._data[self._n] = x
self._n += 1
m = DinamikMassiv()
for i in range(16):
m.append(i)
print("size =", len(m)) # -> size = 16
print("kochirishlar =", m.kochirishlar) # -> kochirishlar = 15
Sig'im 1 β 2 β 4 β 8 β 16 o'sadi, har o'tishda mos ravishda 1, 2, 4, 8 element nusxalanadi β jami 15. Bu 2n = 32 dan kichik, geometrik yig'indi chegarasini tasdiqlaydi. Demak append amortized O(1).
7-mashq yechimi¶
def jami_kochirish(faktor, n):
cap, size, kochirildi = 1, 0, 0
for _ in range(n):
if size == cap:
yangi = max(cap + 1, int(cap * faktor))
kochirildi += size # size ta element nusxalanadi
cap = yangi
size += 1
return kochirildi, cap
n = 1000
for f in (2.0, 1.5):
k, c = jami_kochirish(f, n)
print(f"faktor={f}: jami_kochirish={k}, isrof={c-n} katak")
# faktor=2.0: jami_kochirish=1023, isrof=24 katak
# faktor=1.5: jami_kochirish=2137, isrof=66 katak
Natija: 2x kamroq nusxalaydi (1023 vs 2137), chunki ikkilantirishlar siyrakroq. Lekin 2x ko'proq xotira "isrof" qilishi mumkin (oxirgi sig'im 1024, kerakli 1000 β bu holatda 24 katak), 1.5x esa odatda zichroq joylashadi (lekin ko'proq nusxalaydi). Ikkalasi ham amortized O(1); tanlov β tezlik (kamroq nusxalash) yoki xotira tejash o'rtasidagi kelishuv. Aynan shu sababdan turli kutubxonalar turli faktor tanlaydi.
8-mashq yechimi¶
Ikki ko'rsatkich (chap, ong) qarama-qarshi tomondan markazga yurib, har qadamda almashtiradi. Qo'shimcha massiv ishlatilmaydi β O(1) xotira; har element bir marta tegiladi β O(n) vaqt.
def teskari(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([1, 2, 3, 4, 5])) # -> [5, 4, 3, 2, 1]
print(teskari([1, 2, 3, 4])) # -> [4, 3, 2, 1]
Toq uzunlikda o'rtadagi element joyida qoladi (chap == ong da sikl to'xtaydi); juft uzunlikda hamma juftlar almashadi. Murakkablik: vaqt O(n), qo'shimcha xotira O(1).
β¬ οΈ Oldingi: 11 β Amortizatsiyalangan tahlil Β· π README Β· Keyingi: 13 β Bog'langan ro'yxatlar (linked list) β‘οΈ