10-bo‘lim

Uyum (Heap)

Uyum xossasi, massivda daraxtni saqlash, heapify, prioritetli navbat va heap sort algoritmi.

🕑 10 daqiqa o‘qish 📄 614 so‘z 👁 8 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. Uyum xossasi
  2. Massivda daraxt
  3. Min-uyumni amalga oshirish
  4. Ko'tarish va tushirish
  5. Python heapq moduli
  6. Max-uyum qanday yasaladi?
  7. Prioritetli navbat
  8. Heap Sort
  9. Murakkablik jadvali
  10. Amaliy misol: oqimdagi mediana
  11. Uyum qayerda ishlatiladi?
  12. Xulosa

Uyum - "eng katta" yoki "eng kichik" elementni doimiy ravishda tez olish kerak bo'lganda ishlatiladigan tuzilma.

Uyum xossasi #

Uyum - butun (complete) ikkilik daraxt bo'lib, quyidagi qoidaga bo'ysunadi:

TuriQoida
Min-uyumHar bir ota o'z bolalaridan kichik yoki teng
Max-uyumHar bir ota o'z bolalaridan katta yoki teng
Uyum - bu BST emas

BST da chapdagi hamma narsa kichik, o'ngdagi katta. Uyumda esa faqat ota-bola munosabati muhim - aka-uka tugunlar orasida hech qanday tartib yo'q. Shu sababli uyumda qidir(x) amali O(n); u faqat ildizga (eng kichik yoki eng katta) tez murojaat uchun mo'ljallangan.

Massivda daraxt #

Uyumning eng chiroyli tomoni: u daraxt bo'lsa ham, oddiy massivda saqlanadi. Havolalar kerak emas.

3 [0] 5 [1] 8 [2] 10 [3] 7 [4] 9 [5] Massiv: 3 5 8 10 7 9 0 1 2 3 4 5 chap = 2i + 1 o'ng = 2i + 2 ota = (i - 1) // 2
Daraja bo'yicha yozilgan massiv - indeks formulalari daraxt aloqalarini beradi

Uchta oddiy formula butun daraxtni almashtiradi:

Python
def chap_bola(i):
    return 2 * i + 1

def ong_bola(i):
    return 2 * i + 2

def ota(i):
    return (i - 1) // 2


# Tekshirib ko'ramiz: uyum = [3, 5, 8, 10, 7, 9]
print(f"Ildiz [0] ning bolalari: [{chap_bola(0)}] va [{ong_bola(0)}]")
print(f"[1] ning bolalari:       [{chap_bola(1)}] va [{ong_bola(1)}]")
print(f"[4] ning otasi:          [{ota(4)}]")
Natija
Ildiz [0] ning bolalari: [1] va [2]
[1] ning bolalari:       [3] va [4]
[4] ning otasi:          [1]

Min-uyumni amalga oshirish #

Python
class MinUyum:
    """Eng kichik element har doim ildizda turadi."""

    def __init__(self):
        self._elementlar = []

    # ---------- yordamchi indekslar ----------

    @staticmethod
    def _ota(i):
        return (i - 1) // 2

    @staticmethod
    def _chap(i):
        return 2 * i + 1

    @staticmethod
    def _ong(i):
        return 2 * i + 2

    def _almashtir(self, i, j):
        self._elementlar[i], self._elementlar[j] = self._elementlar[j], self._elementlar[i]

    # ---------- asosiy amallar ----------

    def qosh(self, qiymat):
        """Element qo'shadi. O(log n)."""
        self._elementlar.append(qiymat)
        self._yuqoriga_kotar(len(self._elementlar) - 1)

    def eng_kichigini_ol(self):
        """Eng kichik elementni olib tashlaydi va qaytaradi. O(log n)."""
        if not self._elementlar:
            raise IndexError("Uyum bo'sh")

        eng_kichik = self._elementlar[0]
        oxirgi = self._elementlar.pop()

        if self._elementlar:
            self._elementlar[0] = oxirgi
            self._pastga_tushir(0)

        return eng_kichik

    def eng_kichigini_kor(self):
        """Olmasdan qaraydi. O(1)."""
        if not self._elementlar:
            raise IndexError("Uyum bo'sh")
        return self._elementlar[0]

    # ---------- muvozanatni tiklash ----------

    def _yuqoriga_kotar(self, i):
        """Yangi elementni o'z o'rniga ko'taradi (sift up)."""
        while i > 0:
            ota_indeks = self._ota(i)
            if self._elementlar[i] >= self._elementlar[ota_indeks]:
                break
            self._almashtir(i, ota_indeks)
            i = ota_indeks

    def _pastga_tushir(self, i):
        """Ildizdagi elementni o'z o'rniga tushiradi (sift down)."""
        soni = len(self._elementlar)

        while True:
            eng_kichik = i
            chap, ong = self._chap(i), self._ong(i)

            if chap < soni and self._elementlar[chap] < self._elementlar[eng_kichik]:
                eng_kichik = chap
            if ong < soni and self._elementlar[ong] < self._elementlar[eng_kichik]:
                eng_kichik = ong

            if eng_kichik == i:
                break

            self._almashtir(i, eng_kichik)
            i = eng_kichik

    def __len__(self):
        return len(self._elementlar)

    def __str__(self):
        return str(self._elementlar)
Python
uyum = MinUyum()

for son in [10, 4, 15, 20, 1, 8]:
    uyum.qosh(son)
    print(f"{son:>3} qo'shildi -> {uyum}")

print("\nO'sish tartibida chiqaramiz:")
while len(uyum):
    print(uyum.eng_kichigini_ol(), end=" ")
print()
Natija
 10 qo'shildi -> [10]
  4 qo'shildi -> [4, 10]
 15 qo'shildi -> [4, 10, 15]
 20 qo'shildi -> [4, 10, 15, 20]
  1 qo'shildi -> [1, 4, 15, 20, 10]
  8 qo'shildi -> [1, 4, 8, 20, 10, 15]

O'sish tartibida chiqaramiz:
1 4 8 10 15 20 

Ko'tarish va tushirish #

1 ni qo'shdik - u eng oxirga tushdi, lekin eng kichik 4 10 15 1 1 < 10, almashtiramiz 4 1 15 10 1 < 4, yana almashtiramiz
Yangi element o'z o'rnini topguncha yuqoriga ko'tariladi - ko'pi bilan log n qadam

Python heapq moduli #

Amalda o'z uyumingizni yozish shart emas - standart kutubxonada tayyor:

Python
import heapq

uyum = []

heapq.heappush(uyum, 10)
heapq.heappush(uyum, 4)
heapq.heappush(uyum, 15)
heapq.heappush(uyum, 1)

print("Uyum:", uyum)
print("Eng kichik:", uyum[0])          # O(1) - olmasdan qarash
print("Olindi:", heapq.heappop(uyum))  # O(log n)

# Mavjud ro'yxatni uyumga aylantirish - O(n), sikldan tezroq!
sonlar = [10, 4, 15, 20, 1, 8]
heapq.heapify(sonlar)
print("Heapify:", sonlar)

# Eng katta / eng kichik N ta element
baholar = [85, 92, 78, 95, 88, 71, 99]
print("Eng yuqori 3 ta:", heapq.nlargest(3, baholar))
print("Eng past 3 ta:  ", heapq.nsmallest(3, baholar))
Natija
Uyum: [1, 4, 15, 10]
Eng kichik: 1
Olindi: 1
Heapify: [1, 4, 8, 20, 10, 15]
Eng yuqori 3 ta: [99, 95, 92]
Eng past 3 ta:   [71, 78, 85]
heapify nima uchun O(n)?

Bittalab qo'shsangiz n × log n bo'ladi. Lekin heapify pastdan yuqoriga ishlaydi: tugunlarning yarmi barglar (ular bilan ish yo'q), chorak qismi 1 qadam tushadi, sakkizdan biri 2 qadam va hokazo. Qator yig'indisi O(n) ga teng.

Max-uyum qanday yasaladi? #

heapq faqat min-uyum. Max-uyum uchun qiymatlarni manfiy qilish - keng tarqalgan hiyla:

Python
import heapq

max_uyum = []
for baho in [85, 92, 78, 95]:
    heapq.heappush(max_uyum, -baho)        # manfiy qilib saqlaymiz

print("Eng yuqori baho:", -heapq.heappop(max_uyum))
print("Keyingisi:      ", -heapq.heappop(max_uyum))
Natija
Eng yuqori baho: 95
Keyingisi:       92

Prioritetli navbat #

6-bo'limda ishlatgan prioritetli navbat aynan uyum ustiga quriladi:

Python
import heapq
from dataclasses import dataclass, field


@dataclass(order=True)
class Vazifa:
    prioritet: int
    tartib: int
    nomi: str = field(compare=False)


class VazifalarNavbati:
    def __init__(self):
        self._uyum = []
        self._hisoblagich = 0

    def qosh(self, nomi, prioritet):
        heapq.heappush(self._uyum, Vazifa(prioritet, self._hisoblagich, nomi))
        self._hisoblagich += 1

    def keyingisi(self):
        if not self._uyum:
            return None
        return heapq.heappop(self._uyum).nomi

    def __len__(self):
        return len(self._uyum)


navbat = VazifalarNavbati()
navbat.qosh("Hisobotni yuborish", 2)
navbat.qosh("Serverni tiklash", 0)
navbat.qosh("Emailga javob berish", 3)
navbat.qosh("Xatolikni tuzatish", 1)

print("Bajarish tartibi:")
while len(navbat):
    print("  -", navbat.keyingisi())
Natija
Bajarish tartibi:
  - Serverni tiklash
  - Xatolikni tuzatish
  - Hisobotni yuborish
  - Emailga javob berish

Heap Sort #

Uyum yordamida saralash - kafolatlangan O(n log n) va qo'shimcha xotira talab qilmaydi:

Python
def uyum_bilan_sarala(sonlar):
    """Heap sort. O(n log n) vaqt, O(1) qo'shimcha xotira."""
    massiv = list(sonlar)
    n = len(massiv)

    def tushir(chegara, i):
        while True:
            eng_katta = i
            chap, ong = 2 * i + 1, 2 * i + 2

            if chap < chegara and massiv[chap] > massiv[eng_katta]:
                eng_katta = chap
            if ong < chegara and massiv[ong] > massiv[eng_katta]:
                eng_katta = ong
            if eng_katta == i:
                break

            massiv[i], massiv[eng_katta] = massiv[eng_katta], massiv[i]
            i = eng_katta

    # 1-bosqich: max-uyum quramiz  O(n)
    for i in range(n // 2 - 1, -1, -1):
        tushir(n, i)

    # 2-bosqich: eng kattani oxirga surib boramiz  O(n log n)
    for oxir in range(n - 1, 0, -1):
        massiv[0], massiv[oxir] = massiv[oxir], massiv[0]
        tushir(oxir, 0)

    return massiv


print(uyum_bilan_sarala([10, 4, 15, 20, 1, 8, 3]))
Natija
[1, 3, 4, 8, 10, 15, 20]

Murakkablik jadvali #

AmalMurakkablik
Eng kichikni ko'rishO(1)
Qo'shishO(log n)
Eng kichikni olishO(log n)
Ro'yxatdan uyum qurish (heapify)O(n)
Ixtiyoriy elementni qidirishO(n)
Heap sortO(n log n)

Amaliy misol: oqimdagi mediana #

Uyumning kuchini ko'rsatadigan klassik masala. Ikkita uyum bilan oqimga kelayotgan sonlarning medianasini doimiy kuzatib boramiz:

Python
import heapq


class MedianaKuzatuvchi:
    """Oqimga kelayotgan sonlarning medianasini O(log n) da yangilaydi."""

    def __init__(self):
        self._kichik_yarim = []      # max-uyum (manfiy qiymatlar bilan)
        self._katta_yarim = []       # min-uyum

    def qosh(self, son):
        heapq.heappush(self._kichik_yarim, -son)

        # Kichik yarimning eng kattasini katta yarimga o'tkazamiz
        heapq.heappush(self._katta_yarim, -heapq.heappop(self._kichik_yarim))

        # Muvozanatni saqlaymiz
        if len(self._katta_yarim) > len(self._kichik_yarim):
            heapq.heappush(self._kichik_yarim, -heapq.heappop(self._katta_yarim))

    def mediana(self):
        if not self._kichik_yarim:
            return None
        if len(self._kichik_yarim) > len(self._katta_yarim):
            return -self._kichik_yarim[0]
        return (-self._kichik_yarim[0] + self._katta_yarim[0]) / 2


kuzatuvchi = MedianaKuzatuvchi()
for son in [5, 15, 1, 3, 8, 7, 9, 10, 6, 11, 4]:
    kuzatuvchi.qosh(son)
    print(f"{son:>3} qo'shildi -> mediana = {kuzatuvchi.mediana()}")
Natija
  5 qo'shildi -> mediana = 5
 15 qo'shildi -> mediana = 10.0
  1 qo'shildi -> mediana = 5
  3 qo'shildi -> mediana = 4.0
  8 qo'shildi -> mediana = 5
  7 qo'shildi -> mediana = 6.0
  9 qo'shildi -> mediana = 7
 10 qo'shildi -> mediana = 7.5
  6 qo'shildi -> mediana = 7
 11 qo'shildi -> mediana = 7.5
  4 qo'shildi -> mediana = 7

Har safar butun ro'yxatni saralasangiz O(n log n) bo'lardi. Ikki uyum bilan har bir qo'shish O(log n).

Uyum qayerda ishlatiladi? #

SohaQo'llanilishi
Operatsion tizimJarayonlarni prioritet bo'yicha rejalashtirish
TarmoqDijkstra algoritmi - eng qisqa yo'l
Ma'lumot tahliliEng katta/kichik N ta element
SiqishHuffman kodlash daraxti
O'yinlarA* yo'l topish algoritmi
Ma'lumotlar bazasiTashqi saralash
Amaliy topshiriq
  1. MaxUyum klassini yozing - MinUyum ni asos qilib oling.
  2. k ta tartiblangan ro'yxatni bitta tartiblangan ro'yxatga birlashtiring (uyumdan foydalaning).
  3. Katta fayldagi eng ko'p uchraydigan 10 ta so'zni toping - butun ma'lumotni saralamasdan.
  4. heapq.nlargest(k, ...) ni o'zingiz amalga oshiring va murakkabligini izohlang.

Xulosa #

  • Uyum - butun ikkilik daraxt bo'lib, ota har doim bolalaridan kichik (min) yoki katta (max).
  • U massivda saqlanadi: chap = 2i+1, o'ng = 2i+2, ota = (i-1)//2.
  • Eng kichikni ko'rish O(1), qo'shish va olish O(log n), heapify esa O(n).
  • Uyum qidiruv uchun emas - ixtiyoriy elementni topish O(n).
  • Python'da heapq moduli tayyor min-uyum beradi; max-uyum uchun qiymatlarni manfiy qiling.
  • Prioritetli navbat, heap sort va Dijkstra algoritmi uyumga tayanadi.

Keyingi bo'limda eng universal tuzilma - graflar 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.