4-bo‘lim
Bog'langan ro'yxatlar
Tugun tushunchasi, bir va ikki tomonlama bog'langan ro'yxatlar, ularni amalga oshirish va massiv bilan taqqoslash.
Ushbu bo‘lim mundarijasi
Massivning asosiy muammosi - boshiga element qo'shish O(n). Bog'langan ro'yxat aynan shu muammoni hal qiladi.
Asosiy g'oya #
Massivda elementlar yonma-yon turadi. Bog'langan ro'yxatda esa ular xotiraning istalgan joyida bo'lishi mumkin - har bir element keyingisining manzilini saqlab turadi.
Tugun (Node) #
Bog'langan ro'yxatning qurilish g'ishti - tugun:
class Tugun:
"""Bog'langan ro'yxatning bitta bo'g'ini."""
def __init__(self, qiymat):
self.qiymat = qiymat
self.keyingi = None
def __repr__(self):
return f"Tugun({self.qiymat})"
# Qo'lda uch tugunni bog'laymiz
birinchi = Tugun(85)
ikkinchi = Tugun(92)
uchinchi = Tugun(78)
birinchi.keyingi = ikkinchi
ikkinchi.keyingi = uchinchi
# Boshidan oxirigacha yuramiz
joriy = birinchi
while joriy is not None:
print(joriy.qiymat, end=" -> ")
joriy = joriy.keyingi
print("None")
85 -> 92 -> 78 -> None
To'liq amalga oshirish #
class BoglanganRoyxat:
"""Bir tomonlama bog'langan ro'yxat."""
def __init__(self):
self.bosh = None
self.uzunlik = 0
def boshiga_qosh(self, qiymat):
"""Boshiga element qo'shadi. O(1)."""
yangi = Tugun(qiymat)
yangi.keyingi = self.bosh
self.bosh = yangi
self.uzunlik += 1
def oxiriga_qosh(self, qiymat):
"""Oxiriga element qo'shadi. O(n)."""
yangi = Tugun(qiymat)
self.uzunlik += 1
if self.bosh is None:
self.bosh = yangi
return
joriy = self.bosh
while joriy.keyingi is not None:
joriy = joriy.keyingi
joriy.keyingi = yangi
def ochir(self, qiymat):
"""Birinchi uchragan qiymatni o'chiradi. O(n)."""
if self.bosh is None:
return False
if self.bosh.qiymat == qiymat:
self.bosh = self.bosh.keyingi
self.uzunlik -= 1
return True
joriy = self.bosh
while joriy.keyingi is not None:
if joriy.keyingi.qiymat == qiymat:
joriy.keyingi = joriy.keyingi.keyingi # o'tkazib yuboramiz
self.uzunlik -= 1
return True
joriy = joriy.keyingi
return False
def qidir(self, qiymat):
"""Qiymat bor-yo'qligini tekshiradi. O(n)."""
joriy = self.bosh
while joriy is not None:
if joriy.qiymat == qiymat:
return True
joriy = joriy.keyingi
return False
def teskari_ogir(self):
"""Ro'yxatni teskari o'giradi. O(n) vaqt, O(1) xotira."""
oldingi = None
joriy = self.bosh
while joriy is not None:
keyingi = joriy.keyingi # keyingisini eslab qolamiz
joriy.keyingi = oldingi # strelkani teskari buramiz
oldingi = joriy # bir qadam oldinga
joriy = keyingi
self.bosh = oldingi
def __len__(self):
return self.uzunlik
def __iter__(self):
joriy = self.bosh
while joriy is not None:
yield joriy.qiymat
joriy = joriy.keyingi
def __str__(self):
return " -> ".join(str(q) for q in self) + " -> None"
Sinab ko'ramiz:
royxat = BoglanganRoyxat()
royxat.oxiriga_qosh(85)
royxat.oxiriga_qosh(92)
royxat.oxiriga_qosh(78)
royxat.boshiga_qosh(100)
print(royxat)
print(f"Uzunligi: {len(royxat)}")
print(f"92 bormi: {royxat.qidir(92)}")
royxat.ochir(92)
print(f"O'chirgandan keyin: {royxat}")
royxat.teskari_ogir()
print(f"Teskari o'girilgan: {royxat}")
100 -> 85 -> 92 -> 78 -> None
Uzunligi: 4
92 bormi: True
O'chirgandan keyin: 100 -> 85 -> 78 -> None
Teskari o'girilgan: 78 -> 85 -> 100 -> None
Teskari o'girish qanday ishlaydi? #
Bu klassik intervyu savoli. Uchta ko'rsatkich bilan strelkalar birma-bir teskari buriladi:
keyingi ni saqlashni unutmangjoriy.keyingi = oldingi # strelkani burdik
joriy = joriy.keyingi # ENDI XATO! joriy.keyingi allaqachon o'zgargan
Strelkani burishdan oldin keyingi tugunni vaqtinchalik o'zgaruvchida saqlash shart - aks holda ro'yxatning qolgan qismiga yo'l yo'qoladi.
Ikki tomonlama bog'langan ro'yxat #
Har bir tugun oldingi tugunni ham biladi. Bu orqaga yurish imkonini beradi.
class IkkiTomonlamaTugun:
def __init__(self, qiymat):
self.qiymat = qiymat
self.keyingi = None
self.oldingi = None
class IkkiTomonlamaRoyxat:
"""Ikki tomonlama bog'langan ro'yxat - oxiriga qo'shish ham O(1)."""
def __init__(self):
self.bosh = None
self.oxir = None
self.uzunlik = 0
def oxiriga_qosh(self, qiymat):
"""O(1) - chunki oxirni bilamiz."""
yangi = IkkiTomonlamaTugun(qiymat)
self.uzunlik += 1
if self.oxir is None:
self.bosh = self.oxir = yangi
return
yangi.oldingi = self.oxir
self.oxir.keyingi = yangi
self.oxir = yangi
def oxiridan_ol(self):
"""Oxirgi elementni olib tashlaydi. O(1)."""
if self.oxir is None:
return None
qiymat = self.oxir.qiymat
self.oxir = self.oxir.oldingi
if self.oxir is None:
self.bosh = None
else:
self.oxir.keyingi = None
self.uzunlik -= 1
return qiymat
def teskari_yur(self):
"""Oxiridan boshiga qarab yuradi."""
joriy = self.oxir
while joriy is not None:
yield joriy.qiymat
joriy = joriy.oldingi
royxat = IkkiTomonlamaRoyxat()
for baho in [85, 92, 78, 95]:
royxat.oxiriga_qosh(baho)
print("Teskari tartibda:", list(royxat.teskari_yur()))
print("Oxiridan olindi:", royxat.oxiridan_ol())
print("Qolgan uzunlik:", royxat.uzunlik)
Teskari tartibda: [95, 78, 92, 85]
Oxiridan olindi: 95
Qolgan uzunlik: 3
Massiv va bog'langan ro'yxat: qaysi biri? #
| Amal | Massiv | Bog'langan ro'yxat |
|---|---|---|
| Indeks bo'yicha murojaat | O(1) | O(n) |
| Boshiga qo'shish | O(n) | O(1) |
| Oxiriga qo'shish | O(1)* | O(n) yoki O(1)** |
| O'rtaga qo'shish | O(n) | O(1)*** |
| Qidiruv | O(n) | O(n) |
| Xotira sarfi | Kam | Ko'p (havolalar uchun) |
| Kesh samaradorligi | Yuqori | Past |
* amortizatsiyalangan ** ikki tomonlamada *** tugun allaqachon topilgan bo'lsa
Ochig'i - massiv. Zamonaviy protsessorlarda kesh shu qadar muhimki, massiv bo'ylab yurish bog'langan ro'yxatdan bir necha barobar tez bo'ladi, garchi Big-O bir xil bo'lsa ham. Bog'langan ro'yxat kerak bo'ladigan joylar: stek va navbat amalga oshirilishi, LRU-kesh, matn muharrirlaridagi "orqaga qaytarish" tarixi va operatsion tizim ichki tuzilmalari.
Klassik masala: siklni aniqlash #
Bog'langan ro'yxat o'z-o'ziga tugallanib qolishi mumkin. Buni "toshbaqa va quyon" algoritmi topadi:
def sikl_bormi(bosh):
"""Floyd algoritmi: sekin va tez ko'rsatkich. O(n) vaqt, O(1) xotira."""
sekin = bosh
tez = bosh
while tez is not None and tez.keyingi is not None:
sekin = sekin.keyingi # 1 qadam
tez = tez.keyingi.keyingi # 2 qadam
if sekin is tez: # uchrashdilar - demak sikl bor
return True
return False
a, b, c = Tugun(1), Tugun(2), Tugun(3)
a.keyingi, b.keyingi = b, c
print("Sikl bormi:", sikl_bormi(a))
c.keyingi = a # siklni yasaymiz
print("Sikl bormi:", sikl_bormi(a))
Sikl bormi: False
Sikl bormi: True
Aylana yo'lda tez yuguruvchi sekin yuruvchini albatta bir marta quvib yetadi. To'g'ri yo'lda esa
hech qachon uchrashmaydi - tez yuguruvchi oxiriga yetib, None ga uriladi.
BoglanganRoyxatklassigaortadan_qosh(indeks, qiymat)metodini qo'shing.ortasini_top()metodini yozing - ro'yxatni bir marta aylanib o'rtadagi elementni toping (maslahat: sekin va tez ko'rsatkichdan foydalaning).- Ikkita tartiblangan bog'langan ro'yxatni bitta tartiblangan ro'yxatga birlashtiring.
- Ro'yxatdagi takrorlanuvchi qiymatlarni o'chiring va murakkabligini izohda yozing.
Xulosa #
- Bog'langan ro'yxat elementlari xotirada tarqoq joylashadi, har biri keyingisiga havola saqlaydi.
- Boshiga qo'shish va o'chirish O(1), lekin indeks bo'yicha murojaat O(n).
- Teskari o'girishda
keyingitugunni strelkani burishdan oldin saqlab qolish shart. - Ikki tomonlama ro'yxat orqaga yurish va oxiridan O(1) o'chirish imkonini beradi.
- Amalda massiv ko'proq ishlatiladi (kesh tufayli), lekin stek, navbat va LRU-kesh uchun bog'langan ro'yxat qulay.
- Floyd algoritmi sikllarni O(1) xotira bilan aniqlaydi.
Keyingi bo'limda bog'langan ro'yxat ustiga quriladigan birinchi tuzilma - stek bilan tanishamiz.
O‘qish tarixini saqlamoqchimisiz?
Tizimga kirsangiz, tugatgan bo‘limlaringiz saqlanadi va qoldirgan joyingizdan davom etasiz.
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.