18-bo‘lim

Ochko'z algoritmlar

Ochko'z yondashuv, u qachon ishlaydi va qachon ishlamaydi, mashg'ulotlarni rejalashtirish va Huffman kodlash.

🕑 9 daqiqa o‘qish 📄 709 so‘z 👁 6 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. Ochko'z yondashuv mohiyati
  2. Ishlaydigan misol: tangalarni maydalash
  3. Ishlamaydigan misol
  4. Ochko'z ishlashi uchun ikki shart
  5. Ishlaydigan klassik masala: mashg'ulotlarni rejalashtirish
  6. Ishlaydigan masala: fraksion ryukzak
  7. Huffman kodlash
  8. Ochko'z algoritmlar ro'yxati
  9. Xulosa

Ochko'z algoritm juda sodda qoidaga amal qiladi: har bir qadamda shu paytdagi eng yaxshi variantni tanla va orqaga qaytma.

Ba'zan bu optimal javob beradi, ba'zan esa - yo'q. Farqni bilish muhim.

Ochko'z yondashuv mohiyati #

YondashuvStrategiyaNarxi
To'liq izlashBarcha variantni sinashO(2ⁿ) - juda qimmat
Dinamik dasturlashBarcha qism masalalarni yechishO(n·W) - o'rtacha
Ochko'zHar qadamda eng yaxshisini olishO(n log n) - arzon

Ochko'z algoritm eng tez, lekin to'g'ri javobni kafolatlamaydi.

Ishlaydigan misol: tangalarni maydalash #

O'zbekiston tangalari uchun ochko'z yondashuv to'g'ri ishlaydi:

Python
def ochkoz_maydala(nominallar, summa):
    """Har safar eng katta tangani oladi. O(n log n)."""
    natija = []
    qolgan = summa

    for tanga in sorted(nominallar, reverse=True):
        soni = qolgan // tanga
        if soni:
            natija.extend([tanga] * soni)
            qolgan -= tanga * soni

    return natija if qolgan == 0 else None


nominallar = [1, 5, 10, 25, 50, 100]
for summa in [63, 87, 130]:
    tangalar = ochkoz_maydala(nominallar, summa)
    print(f"{summa:>4} so'm -> {len(tangalar)} ta tanga: {tangalar}")
Natija
  63 so'm -> 6 ta tanga: [50, 10, 1, 1, 1]
  87 so'm -> 6 ta tanga: [50, 25, 10, 1, 1]
 130 so'm -> 3 ta tanga: [100, 25, 5]

Ishlamaydigan misol #

Python
def taqqosla(nominallar, summa):
    """Ochko'z va DP yechimlarini taqqoslaydi."""
    ochkoz = ochkoz_maydala(nominallar, summa)

    CHEKSIZ = float("inf")
    jadval = [0] + [CHEKSIZ] * summa
    for joriy in range(1, summa + 1):
        for tanga in nominallar:
            if tanga <= joriy:
                jadval[joriy] = min(jadval[joriy], jadval[joriy - tanga] + 1)

    optimal = jadval[summa]
    ochkoz_soni = len(ochkoz) if ochkoz else CHEKSIZ

    holat = "to'g'ri" if ochkoz_soni == optimal else "XATO"
    print(f"  Nominallar {nominallar}, summa {summa}")
    print(f"    Ochko'z: {ochkoz_soni} ta {ochkoz}")
    print(f"    Optimal: {optimal} ta")
    print(f"    Natija:  {holat}\n")


taqqosla([1, 5, 10, 25], 30)
taqqosla([1, 3, 4], 6)
taqqosla([1, 15, 25], 30)
Natija
  Nominallar [1, 5, 10, 25], summa 30
    Ochko'z: 2 ta [25, 5]
    Optimal: 2 ta
    Natija:  to'g'ri

  Nominallar [1, 3, 4], summa 6
    Ochko'z: 3 ta [4, 1, 1]
    Optimal: 2 ta
    Natija:  XATO

  Nominallar [1, 15, 25], summa 30
    Ochko'z: 6 ta [25, 1, 1, 1, 1, 1]
    Optimal: 2 ta
    Natija:  XATO
Nominallar [1, 15, 25], summa = 30 Ochko'z yo'l: 25 + 1 1 1 1 1 = 6 ta tanga Optimal yo'l: 15 + 15 = 2 ta tanga Eng katta tangani olish 25 ni tanladi va 15+15 variantini butunlay o'tkazib yubordi
Ochko'z algoritm birinchi qadamdagi tanlovni hech qachon qayta ko'rib chiqmaydi
Ochko'z algoritmni isbotsiz ishlatmang

Ochko'z yechim to'g'ri ekanini matematik isbotlash kerak. Aks holda u ba'zi kirish ma'lumotlarida jim ravishda noto'g'ri javob beradi - va bu xatoni topish juda qiyin.

Agar isbot topa olmasangiz, dinamik dasturlash ishlating: sekinroq, lekin to'g'ri.

Ochko'z ishlashi uchun ikki shart #

ShartMa'nosi
Ochko'z tanlov xossasiLokal optimal tanlov global optimalga olib boradi
Optimal qism tuzilmaMasalaning yechimi qism masalalar yechimidan iborat

Ishlaydigan klassik masala: mashg'ulotlarni rejalashtirish #

Bitta auditoriya bor. Har bir mashg'ulotning boshlanish va tugash vaqti ma'lum. Eng ko'p mashg'ulotni qanday joylashtirish mumkin?

Ochko'z qoidasi: eng erta tugaydiganni tanla.

Python
def mashgulotlarni_rejalashtir(mashgulotlar):
    """Eng ko'p mashg'ulotni joylashtiradi. O(n log n)."""
    # Tugash vaqti bo'yicha saralaymiz - ochko'z tanlovning kaliti
    tartiblangan = sorted(mashgulotlar, key=lambda m: m[2])

    tanlangan = []
    oxirgi_tugash = 0

    for nomi, boshlanish, tugash in tartiblangan:
        if boshlanish >= oxirgi_tugash:
            tanlangan.append((nomi, boshlanish, tugash))
            oxirgi_tugash = tugash

    return tanlangan


mashgulotlar = [
    ("Python darsi",      9, 11),
    ("Algoritmlar",      10, 12),
    ("Ma'lumotlar bazasi", 11, 13),
    ("Veb dasturlash",   12, 14),
    ("Ingliz tili",      13, 15),
    ("Matematika",        9, 14),
]

tanlangan = mashgulotlarni_rejalashtir(mashgulotlar)

print(f"{'Mashg‘ulot':<22}{'Vaqt':>12}")
print("-" * 34)
for nomi, boshlanish, tugash in tanlangan:
    print(f"{nomi:<22}{boshlanish:>6}:00 - {tugash}:00")
print("-" * 34)
print(f"Jami {len(tanlangan)} ta mashg'ulot joylashtirildi "
      f"({len(mashgulotlar)} tadan)")
Natija
Mashg'ulot                    Vaqt
----------------------------------
Python darsi               9:00 - 11:00
Ma'lumotlar bazasi        11:00 - 13:00
Ingliz tili               13:00 - 15:00
----------------------------------
Jami 3 ta mashg'ulot joylashtirildi (6 tadan)
Nima uchun "eng erta tugaydigan" to'g'ri qoida?

Mashg'ulot qanchalik erta tugasa, qolganlar uchun shuncha ko'p vaqt qoladi. Bu intuitiv, lekin matematik isboti ham bor: har qanday optimal yechimdagi birinchi mashg'ulotni eng erta tugaydiganiga almashtirsak, yechim yomonlashmaydi.

E'tibor bering: "eng qisqa" yoki "eng erta boshlanadigan" qoidalari ishlamaydi. Yuqoridagi misolda "Matematika" (9-14) eng erta boshlanadi, lekin uni tanlash faqat 1 ta mashg'ulot berardi.

Ishlaydigan masala: fraksion ryukzak #

O'tgan bo'limdagi ryukzak masalasi DP talab qilgan edi. Lekin agar buyumlarni bo'lish mumkin bo'lsa (qum, un, benzin), ochko'z yondashuv optimal ishlaydi:

Python
def fraksion_ryukzak(buyumlar, quvvat):
    """Buyumlarni bo'lish mumkin bo'lgan ryukzak. O(n log n)."""
    # Birlik og'irlik uchun qiymat bo'yicha saralaymiz
    tartiblangan = sorted(buyumlar, key=lambda b: b[2] / b[1], reverse=True)

    jami_qiymat = 0.0
    tanlangan = []
    qolgan = quvvat

    for nomi, ogirlik, qiymat in tartiblangan:
        if qolgan <= 0:
            break

        if ogirlik <= qolgan:
            tanlangan.append((nomi, ogirlik, qiymat, 100))
            jami_qiymat += qiymat
            qolgan -= ogirlik
        else:
            ulush = qolgan / ogirlik
            tanlangan.append((nomi, qolgan, qiymat * ulush, ulush * 100))
            jami_qiymat += qiymat * ulush
            qolgan = 0

    return jami_qiymat, tanlangan


mahsulotlar = [
    ("Zafaron",   2, 12000),
    ("Asal",      5,  8000),
    ("Yong'oq",   8,  6000),
    ("Un",       10,  2000),
]
QUVVAT = 12

qiymat, tanlangan = fraksion_ryukzak(mahsulotlar, QUVVAT)

print(f"{'Mahsulot':<12}{'Og‘irlik':>10}{'Ulush':>9}{'Qiymat':>11}")
print("-" * 42)
for nomi, ogirlik, narx, ulush in tanlangan:
    print(f"{nomi:<12}{ogirlik:>8} kg{ulush:>8.0f}%{narx:>11,.0f}")
print("-" * 42)
print(f"{'JAMI':<12}{QUVVAT:>8} kg{'':>9}{qiymat:>11,.0f}")
Natija
Mahsulot      Og'irlik    Ulush     Qiymat
------------------------------------------
Zafaron              2 kg     100%     12,000
Asal                 5 kg     100%      8,000
Yong'oq              5 kg      62%      3,750
------------------------------------------
JAMI                12 kg              23,750
0/1 va fraksion ryukzak farqi
0/1 ryukzakFraksion ryukzak
Buyumni bo'lishMumkin emasMumkin
To'g'ri yondashuvDPOchko'z
MurakkablikO(n·W)O(n log n)

Farq bitta so'zda, lekin yondashuv butunlay boshqa.

Huffman kodlash #

Ochko'z algoritmning eng chiroyli qo'llanishi - ma'lumotni siqish. G'oya: tez-tez uchraydigan belgiga qisqa kod ber.

Python
import heapq
from collections import Counter


class HuffmanTugun:
    def __init__(self, belgi, chastota, chap=None, ong=None):
        self.belgi = belgi
        self.chastota = chastota
        self.chap = chap
        self.ong = ong

    def __lt__(self, boshqa):
        return self.chastota < boshqa.chastota


def huffman_kodlari(matn):
    """Har bir belgi uchun optimal ikkilik kod hosil qiladi."""
    chastotalar = Counter(matn)

    if len(chastotalar) == 1:                    # maxsus holat
        return {next(iter(chastotalar)): "0"}

    uyum = [HuffmanTugun(belgi, soni) for belgi, soni in chastotalar.items()]
    heapq.heapify(uyum)

    # Har safar eng kam uchraydigan ikkitasini birlashtiramiz
    while len(uyum) > 1:
        birinchi = heapq.heappop(uyum)
        ikkinchi = heapq.heappop(uyum)
        yangi = HuffmanTugun(None, birinchi.chastota + ikkinchi.chastota,
                             birinchi, ikkinchi)
        heapq.heappush(uyum, yangi)

    # Daraxt bo'ylab yurib kodlarni yig'amiz
    kodlar = {}

    def yur(tugun, kod=""):
        if tugun is None:
            return
        if tugun.belgi is not None:
            kodlar[tugun.belgi] = kod or "0"
            return
        yur(tugun.chap, kod + "0")
        yur(tugun.ong, kod + "1")

    yur(uyum[0])
    return kodlar


matn = "dasturlash dasturlash algoritm"
kodlar = huffman_kodlari(matn)
chastotalar = Counter(matn)

print(f"{'Belgi':<8}{'Chastota':>10}{'Kod':>10}{'Bit':>6}")
print("-" * 34)
for belgi, kod in sorted(kodlar.items(), key=lambda j: -chastotalar[j[0]]):
    korinish = repr(belgi) if belgi == " " else belgi
    print(f"{korinish:<8}{chastotalar[belgi]:>10}{kod:>10}{len(kod):>6}")

oddiy_bitlar = len(matn) * 8
huffman_bitlar = sum(chastotalar[b] * len(k) for b, k in kodlar.items())

print("-" * 34)
print(f"Oddiy kodlash (8 bit):  {oddiy_bitlar:>6} bit")
print(f"Huffman kodlash:        {huffman_bitlar:>6} bit")
print(f"Siqilish darajasi:      {(1 - huffman_bitlar / oddiy_bitlar):>6.1%}")
Natija
Belgi      Chastota       Kod   Bit
----------------------------------
a                 6       00     2
s                 4      010     3
t                 3      100     3
r                 3      101     3
' '               2     1100     4
d                 2     1101     4
u                 2     1110     4
l                 3      011     3
h                 2     1111     4
g                 1    11110     5
o                 1    11111     5
i                 1   111100     6
m                 1   111101     6
----------------------------------
Oddiy kodlash (8 bit):     240 bit
Huffman kodlash:           108 bit
Siqilish darajasi:       55.0%

Matn hajmi ikki barobardan ko'proq kamaydi. Bu algoritm ZIP, JPEG va MP3 formatlarida ishlatiladi.

Ochko'z algoritmlar ro'yxati #

AlgoritmMasalaTo'g'rimi
DijkstraEng qisqa yo'l (musbat vaznlar)Ha
Prim, KruskalMinimal ostki daraxtHa
HuffmanOptimal siqishHa
Mashg'ulotlarni rejalashtirishMaksimal soniHa
Fraksion ryukzakMaksimal qiymatHa
0/1 ryukzakMaksimal qiymatYo'q
Tangalarni maydalashMinimal tangalarNominalga bog'liq
Sotuvchi masalasi (TSP)Eng qisqa marshrutYo'q
Ochko'z va DP: qanday tanlash?
  1. Ochko'z yechimni o'ylab ko'ring - u sodda va tez.
  2. Uni buzadigan misol topishga urinib ko'ring.
  3. Topsangiz → DP ishlating.
  4. Topmasangiz va isbotlay olsangiz → ochko'z ishlating.

Intervyularda ko'pincha aynan shu fikrlash jarayoni baholanadi, javobning o'zi emas.

Amaliy topshiriq
  1. Benzin quyish masalasi: A dan B ga borish uchun eng kam necha marta to'xtash kerak? Ochko'z yechim yozing va nima uchun to'g'ri ishlashini tushuntiring.
  2. [1, 3, 4] nominallari uchun ochko'z algoritm xato beradigan yana ikkita summa toping.
  3. Huffman kodlash uchun dekodlash funksiyasini yozing.
  4. Mashg'ulotlarni rejalashtirishning k ta auditoriyali variantini yeching.

Xulosa #

  • Ochko'z algoritm har qadamda lokal eng yaxshi tanlovni qiladi va orqaga qaytmaydi.
  • U juda tez (odatda O(n log n)), lekin har doim ham optimal emas.
  • Ishlashi uchun ochko'z tanlov xossasi isbotlanishi kerak.
  • Mashg'ulotlarni rejalashtirishda to'g'ri qoida - eng erta tugaydiganni tanlash.
  • Fraksion ryukzak - ochko'z, 0/1 ryukzak - DP.
  • Huffman kodlash - ochko'z algoritmning amaliy va keng tarqalgan qo'llanishi.

Keyingi bo'limda eng mashhur ochko'z algoritm - Dijkstra 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.