9-bo‘lim

Ikkilik qidiruv daraxti

BST qoidasi, qo'shish, qidirish va o'chirish amallari, muvozanatsizlik muammosi va o'z-o'zini muvozanatlovchi daraxtlar.

🕑 8 daqiqa o‘qish 📄 749 so‘z 👁 7 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. BST qoidasi
  2. BST ni amalga oshirish
  3. Qidiruv qanday ishlaydi?
  4. O'chirishning uchta holati
  5. Muvozanatsizlik muammosi
  6. Yechim: o'z-o'zini muvozanatlovchi daraxtlar
  7. Murakkablik jadvali
  8. BST va xesh-jadval: qaysi biri?
  9. Amaliy misol: avtomatik to'ldirish
  10. Xulosa

Oddiy daraxtda qidiruv O(n). Lekin tugunlarni tartib bilan joylashtirsak, qidiruvni O(log n) ga tushirish mumkin.

BST qoidasi #

Ikkilik qidiruv daraxti (Binary Search Tree) bitta qoidaga bo'ysunadi:

Har bir tugun uchun: chap qism daraxtdagi barcha qiymatlar undan kichik, o'ng qism daraxtdagi barcha qiymatlar undan katta.

50 30 70 20 40 80 chapda hammasi < 50 o'ngda hammasi > 50 Inorder aylanish: 20, 30, 40, 50, 70, 80 → tartiblangan!
BST da chapdagi hamma narsa kichik, o'ngdagi hamma narsa katta

Bu qoidadan juda muhim xossa kelib chiqadi: inorder aylanish har doim tartiblangan ketma-ketlik beradi.

BST ni amalga oshirish #

Python
class BSTTugun:
    def __init__(self, qiymat):
        self.qiymat = qiymat
        self.chap = None
        self.ong = None


class IkkilikQidiruvDaraxti:
    """Ikkilik qidiruv daraxti."""

    def __init__(self):
        self.ildiz = None
        self._soni = 0

    # ---------- qo'shish ----------

    def qosh(self, qiymat):
        """Yangi qiymat qo'shadi. O'rtacha O(log n)."""
        self.ildiz = self._qosh(self.ildiz, qiymat)

    def _qosh(self, tugun, qiymat):
        if tugun is None:
            self._soni += 1
            return BSTTugun(qiymat)

        if qiymat < tugun.qiymat:
            tugun.chap = self._qosh(tugun.chap, qiymat)
        elif qiymat > tugun.qiymat:
            tugun.ong = self._qosh(tugun.ong, qiymat)
        # teng bo'lsa - hech narsa qilmaymiz (takror qabul qilinmaydi)

        return tugun

    # ---------- qidirish ----------

    def bormi(self, qiymat):
        """Qiymat mavjudligini tekshiradi. O'rtacha O(log n)."""
        joriy = self.ildiz
        while joriy is not None:
            if qiymat == joriy.qiymat:
                return True
            joriy = joriy.chap if qiymat < joriy.qiymat else joriy.ong
        return False

    # ---------- eng kichik / eng katta ----------

    def eng_kichik(self):
        """Eng chapdagi tugun. O(log n)."""
        if self.ildiz is None:
            return None
        joriy = self.ildiz
        while joriy.chap is not None:
            joriy = joriy.chap
        return joriy.qiymat

    def eng_katta(self):
        """Eng o'ngdagi tugun. O(log n)."""
        if self.ildiz is None:
            return None
        joriy = self.ildiz
        while joriy.ong is not None:
            joriy = joriy.ong
        return joriy.qiymat

    # ---------- o'chirish ----------

    def ochir(self, qiymat):
        """Qiymatni o'chiradi. O'rtacha O(log n)."""
        self.ildiz, ochirildi = self._ochir(self.ildiz, qiymat)
        if ochirildi:
            self._soni -= 1
        return ochirildi

    def _ochir(self, tugun, qiymat):
        if tugun is None:
            return None, False

        if qiymat < tugun.qiymat:
            tugun.chap, ochirildi = self._ochir(tugun.chap, qiymat)
            return tugun, ochirildi

        if qiymat > tugun.qiymat:
            tugun.ong, ochirildi = self._ochir(tugun.ong, qiymat)
            return tugun, ochirildi

        # topildi - uchta holat
        if tugun.chap is None:                    # 1) bolasi yo'q yoki faqat o'ng
            return tugun.ong, True
        if tugun.ong is None:                     # 2) faqat chap bola
            return tugun.chap, True

        # 3) ikkala bolasi bor: o'ng qism daraxtdagi eng kichigini olamiz
        vorislovchi = tugun.ong
        while vorislovchi.chap is not None:
            vorislovchi = vorislovchi.chap

        tugun.qiymat = vorislovchi.qiymat
        tugun.ong, _ = self._ochir(tugun.ong, vorislovchi.qiymat)
        return tugun, True

    # ---------- yordamchi ----------

    def tartiblangan(self):
        """Inorder aylanish - tartiblangan ro'yxat."""
        natija = []

        def yur(tugun):
            if tugun is None:
                return
            yur(tugun.chap)
            natija.append(tugun.qiymat)
            yur(tugun.ong)

        yur(self.ildiz)
        return natija

    def balandlik(self):
        def olcha(tugun):
            if tugun is None:
                return -1
            return 1 + max(olcha(tugun.chap), olcha(tugun.ong))
        return olcha(self.ildiz)

    def __len__(self):
        return self._soni

    def __contains__(self, qiymat):
        return self.bormi(qiymat)

Sinab ko'ramiz:

Python
daraxt = IkkilikQidiruvDaraxti()

for son in [50, 30, 70, 20, 40, 80, 60]:
    daraxt.qosh(son)

print("Tartiblangan:", daraxt.tartiblangan())
print("Balandlik:   ", daraxt.balandlik())
print("Eng kichik:  ", daraxt.eng_kichik())
print("Eng katta:   ", daraxt.eng_katta())
print("40 bormi:    ", 40 in daraxt)
print("45 bormi:    ", 45 in daraxt)

daraxt.ochir(30)
print("30 o'chirildi:", daraxt.tartiblangan())
Natija
Tartiblangan: [20, 30, 40, 50, 60, 70, 80]
Balandlik:    2
Eng kichik:   20
Eng katta:    80
40 bormi:     True
45 bormi:     False
30 o'chirildi: [20, 40, 50, 60, 70, 80]

Qidiruv qanday ishlaydi? #

Har bir solishtirishda daraxtning yarmi tashlab yuboriladi:

50 40 < 50 → chapga 30 40 > 30 → o'ngga 70 20 40 topildi! 80 40 ni qidiramiz: 3 ta solishtirish 7 ta tugundan
Kulrang tugunlar umuman tekshirilmaydi - har qadamda yarmi tashlanadi

O'chirishning uchta holati #

O'chirish - BST dagi eng murakkab amal. Uchta vaziyat bor:

HolatNima qilinadi
Barg (bolasi yo'q)Shunchaki o'chiriladi
Bitta bolasi borBola otasining o'rniga ko'tariladi
Ikkita bolasi borO'ng qism daraxtdagi eng kichik qiymat o'rniga qo'yiladi
Nima uchun aynan o'ng qism daraxtdagi eng kichik?

U inorder vorisi deb ataladi - tartiblangan ketma-ketlikda o'chirilayotgan qiymatdan keyingi qiymat. Uni o'rniga qo'yganda BST qoidasi buzilmaydi: u chapdagi hamma narsadan katta va o'ngdagi qolgan hamma narsadan kichik. Chap qism daraxtdagi eng kattasi ham xuddi shunday ishlaydi.

Muvozanatsizlik muammosi #

BST ning eng katta zaifligi shu. Tartiblangan ma'lumot qo'shsangiz, daraxt bog'langan ro'yxatga aylanadi:

Python
yaxshi = IkkilikQidiruvDaraxti()
for son in [50, 30, 70, 20, 40, 60, 80]:
    yaxshi.qosh(son)

yomon = IkkilikQidiruvDaraxti()
for son in [20, 30, 40, 50, 60, 70, 80]:      # tartiblangan!
    yomon.qosh(son)

print(f"Aralash tartibda: balandlik = {yaxshi.balandlik()}")
print(f"Tartiblangan:     balandlik = {yomon.balandlik()}")
Natija
Aralash tartibda: balandlik = 2
Tartiblangan:     balandlik = 6
Muvozanatli - O(log n) 50 30 70 20 40 60 80 balandlik 2, 3 ta solishtirish Muvozanatsiz - O(n) 20 30 40 50 60 bog'langan ro'yxatga aylandi
Bir xil ma'lumot, turli qo'shish tartibi - butunlay boshqa samaradorlik
Amaliy xavf

Ma'lumotlar bazasidan ORDER BY id bilan olingan yozuvlarni BST ga ketma-ket qo'shsangiz, daraxtingiz kafolatli buzilgan bo'ladi. Bu real loyihalarda tez-tez uchraydigan xato.

Yechim: o'z-o'zini muvozanatlovchi daraxtlar #

Daraxt turiG'oyasiQayerda ishlatiladi
AVLBalandlik farqi ≤ 1, qat'iy muvozanatKo'p o'qiladigan ma'lumot
Qizil-qora (Red-Black)Yumshoqroq muvozanat, tez qo'shishJava TreeMap, C++ std::map, Linux yadrosi
B-daraxtBir tugunda ko'p kalitMa'lumotlar bazasi indekslari
TreapTasodifiy prioritetlarSodda amalga oshirish

Ular qo'shish yoki o'chirishdan keyin aylantirish (rotation) amali bilan muvozanatni tiklaydi:

Muvozanatsiz 10 20 30 chapga aylantirish Muvozanatli 20 10 30
Aylantirish BST qoidasini buzmasdan balandlikni kamaytiradi

Murakkablik jadvali #

AmalO'rtachaEng yomon (muvozanatsiz)Muvozanatlangan
QidiruvO(log n)O(n)O(log n)
Qo'shishO(log n)O(n)O(log n)
O'chirishO(log n)O(n)O(log n)
Eng kichik/kattaO(log n)O(n)O(log n)
Tartiblangan chiqarishO(n)O(n)O(n)

BST va xesh-jadval: qaysi biri? #

XususiyatXesh-jadvalBST
QidiruvO(1)O(log n)
Tartib saqlanadimiYo'qHa
Eng kichik/kattaO(n)O(log n)
Oraliq so'rov (10-50)O(n)O(log n + k)
Eng yomon holatO(n)O(log n)*
XotiraKo'proqKamroq

* muvozanatlangan daraxtda

Tanlov qoidasi
  • Faqat "bormi / yo'qmi" kerakmi → xesh-jadval (dict, set).
  • Tartib, oraliq so'rov yoki "eng yaqin qiymat" kerakmi → daraxt.

Aynan shuning uchun ma'lumotlar bazalari indeks uchun xesh emas, B-daraxt ishlatadi: WHERE narx BETWEEN 10000 AND 50000 kabi so'rovlar xeshda ishlamaydi.

Amaliy misol: avtomatik to'ldirish #

BST oraliq so'rovlarda kuchli. Prefiks bo'yicha so'zlarni topamiz:

Python
def prefiks_boyicha(daraxt, prefiks):
    """Berilgan prefiks bilan boshlanadigan barcha so'zlarni topadi."""
    natija = []

    def yur(tugun):
        if tugun is None:
            return

        # Chapga borish mantiqiymi?
        if tugun.qiymat >= prefiks:
            yur(tugun.chap)

        if tugun.qiymat.startswith(prefiks):
            natija.append(tugun.qiymat)

        # O'ngga borish mantiqiymi?
        if tugun.qiymat <= prefiks + "￿":
            yur(tugun.ong)

    yur(daraxt.ildiz)
    return natija


lugat = IkkilikQidiruvDaraxti()
for soz in ["dastur", "dasturchi", "dasturlash", "daftar", "daraxt", "kitob", "kalit"]:
    lugat.qosh(soz)

print("'dastur' bilan boshlanuvchilar:")
for soz in prefiks_boyicha(lugat, "dastur"):
    print("  -", soz)
Natija
'dastur' bilan boshlanuvchilar:
  - dastur
  - dasturchi
  - dasturlash
Amaliy topshiriq
  1. bst_mi(tugun) funksiyasini yozing - berilgan daraxt BST qoidasiga bo'ysunishini tekshiring. (Diqqat: faqat bevosita bolalarni tekshirish yetarli emas.)
  2. k_chi_eng_kichik(daraxt, k) - daraxtdagi k-chi eng kichik qiymatni toping.
  3. Tartiblangan massivdan muvozanatli BST quring (maslahat: o'rtadagi elementni ildiz qiling).
  4. Ikkita tugunning eng yaqin umumiy ajdodini (LCA) toping.

Xulosa #

  • BST qoidasi: chapda kichiklar, o'ngda kattalar.
  • Inorder aylanish har doim tartiblangan ketma-ketlik beradi.
  • Qidiruv, qo'shish va o'chirish o'rtacha O(log n) - har qadamda yarmi tashlanadi.
  • O'chirishda ikkita bolasi bor tugun inorder vorisi bilan almashtiriladi.
  • Tartiblangan ma'lumot BST ni buzadi - u bog'langan ro'yxatga aylanadi.
  • Amalda AVL, qizil-qora yoki B-daraxt kabi o'z-o'zini muvozanatlovchi turlar ishlatiladi.

Keyingi bo'limda maxsus daraxt - uyum va uning ustiga qurilgan prioritetli navbatni ko'ramiz.

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.