4-bo‘lim

Bog'langan ro'yxatlar

Tugun tushunchasi, bir va ikki tomonlama bog'langan ro'yxatlar, ularni amalga oshirish va massiv bilan taqqoslash.

🕑 8 daqiqa o‘qish 📄 495 so‘z 👁 6 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. Asosiy g'oya
  2. Tugun (Node)
  3. To'liq amalga oshirish
  4. Teskari o'girish qanday ishlaydi?
  5. Ikki tomonlama bog'langan ro'yxat
  6. Massiv va bog'langan ro'yxat: qaysi biri?
  7. Klassik masala: siklni aniqlash
  8. Xulosa

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.

Bir tomonlama bog'langan ro'yxat bosh 85 92 78 None qiymat keyingi Xotirada esa ular tarqoq turadi: 92 4820 boshqa 78 1204 boshqa 85 9032
Har bir tugun qiymat va keyingi tugunning manzilini saqlaydi

Tugun (Node) #

Bog'langan ro'yxatning qurilish g'ishti - tugun:

Python
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")
Natija
85 -> 92 -> 78 -> None

To'liq amalga oshirish #

Python
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:

Python
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}")
Natija
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:

Boshlanish A B C None 1-qadam oldingi joriy A None B C 2-qadam A B C Natija C B A None
Har qadamda bitta strelka teskari buriladi, ko'rsatkichlar bir qadam oldinga suriladi
keyingi ni saqlashni unutmang
Python
joriy.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.

Python
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)
Natija
Teskari tartibda: [95, 78, 92, 85]
Oxiridan olindi: 95
Qolgan uzunlik: 3

Massiv va bog'langan ro'yxat: qaysi biri? #

AmalMassivBog'langan ro'yxat
Indeks bo'yicha murojaatO(1)O(n)
Boshiga qo'shishO(n)O(1)
Oxiriga qo'shishO(1)*O(n) yoki O(1)**
O'rtaga qo'shishO(n)O(1)***
QidiruvO(n)O(n)
Xotira sarfiKamKo'p (havolalar uchun)
Kesh samaradorligiYuqoriPast

* amortizatsiyalangan   ** ikki tomonlamada   *** tugun allaqachon topilgan bo'lsa

Amalda qaysi biri ko'proq ishlatiladi?

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:

Python
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))
Natija
Sikl bormi: False
Sikl bormi: True
Nima uchun bu ishlaydi?

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.

Amaliy topshiriq
  1. BoglanganRoyxat klassiga ortadan_qosh(indeks, qiymat) metodini qo'shing.
  2. ortasini_top() metodini yozing - ro'yxatni bir marta aylanib o'rtadagi elementni toping (maslahat: sekin va tez ko'rsatkichdan foydalaning).
  3. Ikkita tartiblangan bog'langan ro'yxatni bitta tartiblangan ro'yxatga birlashtiring.
  4. 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 keyingi tugunni 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.

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.