19-bo‘lim

Eng qisqa yo'l - Dijkstra algoritmi

Vaznli grafda eng qisqa yo'lni topish, Dijkstra algoritmi qadamlari, uyum bilan optimallashtirish va A* haqida.

🕑 8 daqiqa o‘qish 📄 565 so‘z 👁 6 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. Muammo: BFS yetarli emas
  2. Algoritm g'oyasi
  3. Sodda amalga oshirish
  4. Sinov grafi: shaharlar orasidagi masofa
  5. Uyum bilan optimallashtirish
  6. Algoritm qadamlarini kuzatish
  7. Dijkstra cheklovlari
  8. Boshqa eng qisqa yo'l algoritmlari
  9. A* algoritmi
  10. Xulosa

12-bo'limda BFS bilan eng qisqa yo'lni topgan edik. Lekin u faqat vaznsiz grafda ishlaydi. Real hayotda yo'llar turli uzunlikda bo'ladi - shu yerda Dijkstra algoritmi kerak bo'ladi.

Muammo: BFS yetarli emas #

100 10 15 A C B BFS: A → C (1 qadam) masofa = 100 Dijkstra: A → B → C (2 qadam) masofa = 25
BFS eng kam qadamli yo'lni topadi, Dijkstra esa eng kam vaznlisini

BFS uchun "eng qisqa" = eng kam qirra. Dijkstra uchun "eng qisqa" = eng kam umumiy vazn.

Algoritm g'oyasi #

Dijkstra - ochko'z algoritm. Uning qoidasi:

Har qadamda hozircha eng yaqin va hali qayta ishlanmagan vershinani tanla, undan boradigan barcha yo'llarni tekshirib, masofalarni yangila.

QadamAmal
1Barcha masofalarni cheksiz, boshlang'ichni 0 deb belgila
2Eng kichik masofali qayta ishlanmagan vershinani tanla
3Uning qo'shnilari uchun masofani yangila (agar qisqaroq bo'lsa)
4Vershinani "tayyor" deb belgila
52-qadamga qayt, hamma vershina tugaguncha

Sodda amalga oshirish #

Python
def dijkstra_sodda(graf, boshlanish):
    """Dijkstra algoritmi. O(V^2) - kichik graflar uchun."""
    masofalar = {vershina: float("inf") for vershina in graf}
    masofalar[boshlanish] = 0
    qayerdan = {boshlanish: None}
    tayyor = set()

    while len(tayyor) < len(graf):
        # Eng yaqin qayta ishlanmagan vershinani topamiz
        joriy = None
        eng_kichik = float("inf")
        for vershina in graf:
            if vershina not in tayyor and masofalar[vershina] < eng_kichik:
                eng_kichik = masofalar[vershina]
                joriy = vershina

        if joriy is None:            # qolganlari erishib bo'lmaydigan
            break

        tayyor.add(joriy)

        # Qo'shnilar uchun masofani yangilaymiz
        for qoshni, vazn in graf[joriy].items():
            yangi_masofa = masofalar[joriy] + vazn
            if yangi_masofa < masofalar[qoshni]:
                masofalar[qoshni] = yangi_masofa
                qayerdan[qoshni] = joriy

    return masofalar, qayerdan

Sinov grafi: shaharlar orasidagi masofa #

Python
shaharlar = {
    "Toshkent":  {"Samarqand": 280, "Namangan": 290, "Guliston": 110},
    "Guliston":  {"Toshkent": 110, "Jizzax": 90},
    "Jizzax":    {"Guliston": 90, "Samarqand": 160},
    "Samarqand": {"Toshkent": 280, "Jizzax": 160, "Buxoro": 270, "Qarshi": 140},
    "Qarshi":    {"Samarqand": 140, "Buxoro": 200, "Termiz": 260},
    "Buxoro":    {"Samarqand": 270, "Qarshi": 200, "Xiva": 440},
    "Xiva":      {"Buxoro": 440, "Nukus": 180},
    "Nukus":     {"Xiva": 180},
    "Namangan":  {"Toshkent": 290, "Andijon": 60},
    "Andijon":   {"Namangan": 60},
    "Termiz":    {"Qarshi": 260},
}


def yolni_tikla(qayerdan, manzil):
    """qayerdan jadvalidan to'liq yo'lni tiklaydi."""
    yol = []
    joriy = manzil
    while joriy is not None:
        yol.append(joriy)
        joriy = qayerdan.get(joriy)
    return yol[::-1]


masofalar, qayerdan = dijkstra_sodda(shaharlar, "Toshkent")

print(f"{'Shahar':<12}{'Masofa':>10}   Yo'l")
print("-" * 62)
for shahar, masofa in sorted(masofalar.items(), key=lambda j: j[1]):
    yol = " -> ".join(yolni_tikla(qayerdan, shahar))
    print(f"{shahar:<12}{masofa:>8} km   {yol}")
Natija
Shahar          Masofa   Yo'l
--------------------------------------------------------------
Toshkent             0 km   Toshkent
Guliston           110 km   Toshkent -> Guliston
Jizzax             200 km   Toshkent -> Guliston -> Jizzax
Samarqand          280 km   Toshkent -> Samarqand
Namangan           290 km   Toshkent -> Namangan
Andijon            350 km   Toshkent -> Namangan -> Andijon
Qarshi             420 km   Toshkent -> Samarqand -> Qarshi
Buxoro             550 km   Toshkent -> Samarqand -> Buxoro
Termiz             680 km   Toshkent -> Samarqand -> Qarshi -> Termiz
Xiva               990 km   Toshkent -> Samarqand -> Buxoro -> Xiva
Nukus             1170 km   Toshkent -> Samarqand -> Buxoro -> Xiva -> Nukus

E'tibor bering: Jizzaxga borish uchun Guliston orqali yurish (200 km) Samarqand orqali yurishdan (280+160=440 km) qisqaroq. Algoritm buni o'zi topdi.

Uyum bilan optimallashtirish #

Har qadamda eng yaqin vershinani chiziqli qidirish O(V) vaqt oladi. Prioritetli navbat (uyum) buni O(log V) ga tushiradi:

Python
import heapq


def dijkstra(graf, boshlanish):
    """Uyum bilan Dijkstra. O((V + E) log V)."""
    masofalar = {vershina: float("inf") for vershina in graf}
    masofalar[boshlanish] = 0
    qayerdan = {boshlanish: None}
    tayyor = set()

    uyum = [(0, boshlanish)]

    while uyum:
        joriy_masofa, joriy = heapq.heappop(uyum)

        if joriy in tayyor:            # eskirgan yozuv - o'tkazib yuboramiz
            continue
        tayyor.add(joriy)

        for qoshni, vazn in graf[joriy].items():
            if qoshni in tayyor:
                continue

            yangi_masofa = joriy_masofa + vazn
            if yangi_masofa < masofalar[qoshni]:
                masofalar[qoshni] = yangi_masofa
                qayerdan[qoshni] = joriy
                heapq.heappush(uyum, (yangi_masofa, qoshni))

    return masofalar, qayerdan


def eng_qisqa_yol(graf, boshlanish, manzil):
    """Ikki nuqta orasidagi eng qisqa yo'l va uning uzunligi."""
    masofalar, qayerdan = dijkstra(graf, boshlanish)

    if masofalar[manzil] == float("inf"):
        return None, None

    yol = []
    joriy = manzil
    while joriy is not None:
        yol.append(joriy)
        joriy = qayerdan.get(joriy)

    return yol[::-1], masofalar[manzil]


yol, masofa = eng_qisqa_yol(shaharlar, "Andijon", "Nukus")

print("Andijondan Nukusgacha eng qisqa yo'l:\n")
jami = 0
for i in range(len(yol) - 1):
    bosqich = shaharlar[yol[i]][yol[i + 1]]
    jami += bosqich
    print(f"  {yol[i]:<12} -> {yol[i + 1]:<12}{bosqich:>6} km  (jami {jami} km)")

print(f"\nUmumiy masofa: {masofa} km")
Natija
Andijondan Nukusgacha eng qisqa yo'l:

  Andijon      -> Namangan          60 km  (jami 60 km)
  Namangan     -> Toshkent         290 km  (jami 350 km)
  Toshkent     -> Samarqand        280 km  (jami 630 km)
  Samarqand    -> Buxoro           270 km  (jami 900 km)
  Buxoro       -> Xiva             440 km  (jami 1340 km)
  Xiva         -> Nukus            180 km  (jami 1520 km)

Umumiy masofa: 1520 km

Algoritm qadamlarini kuzatish #

Python
import heapq


def dijkstra_izohli(graf, boshlanish, manzil=None):
    masofalar = {v: float("inf") for v in graf}
    masofalar[boshlanish] = 0
    tayyor = set()
    uyum = [(0, boshlanish)]
    qadam = 0

    while uyum:
        joriy_masofa, joriy = heapq.heappop(uyum)
        if joriy in tayyor:
            continue

        tayyor.add(joriy)
        qadam += 1
        print(f"{qadam}-qadam: {joriy} tanlandi (masofa {joriy_masofa} km)")

        for qoshni, vazn in graf[joriy].items():
            if qoshni in tayyor:
                continue
            yangi = joriy_masofa + vazn
            if yangi < masofalar[qoshni]:
                eski = masofalar[qoshni]
                eski_matn = "inf" if eski == float("inf") else str(eski)
                print(f"         {qoshni}: {eski_matn} -> {yangi} km yangilandi")
                masofalar[qoshni] = yangi
                heapq.heappush(uyum, (yangi, qoshni))

        if joriy == manzil:
            print(f"\nManzilga yetildi: {masofalar[manzil]} km")
            break

    return masofalar


dijkstra_izohli(shaharlar, "Toshkent", "Jizzax")
Natija
1-qadam: Toshkent tanlandi (masofa 0 km)
         Samarqand: inf -> 280 km yangilandi
         Namangan: inf -> 290 km yangilandi
         Guliston: inf -> 110 km yangilandi
2-qadam: Guliston tanlandi (masofa 110 km)
         Jizzax: inf -> 200 km yangilandi
3-qadam: Jizzax tanlandi (masofa 200 km)

Manzilga yetildi: 200 km

Dijkstra cheklovlari #

Manfiy vaznlar bilan ishlamaydi

Dijkstra ochko'z: vershinani "tayyor" deb belgilagach, uni qayta ko'rib chiqmaydi. Manfiy vazn bo'lsa, keyinroq qisqaroq yo'l topilishi mumkin - lekin algoritm buni sezmaydi.

Python
manfiy_graf = {
    "A": {"B": 5, "C": 2},
    "B": {"D": 1},
    "C": {"B": -4},        # manfiy vazn!
    "D": {},
}
masofalar, _ = dijkstra(manfiy_graf, "A")
print(masofalar["B"])      # noto'g'ri javob

Manfiy vaznlar uchun Bellman-Ford algoritmi kerak - u sekinroq (O(V·E)), lekin manfiy vaznlarni qo'llab-quvvatlaydi va manfiy sikllarni aniqlay oladi.

Boshqa eng qisqa yo'l algoritmlari #

AlgoritmMurakkablikManfiy vaznQachon
BFSO(V + E)-Vaznsiz graf
DijkstraO((V+E) log V)Yo'qBitta manbadan, musbat vaznlar
Bellman-FordO(V · E)HaManfiy vaznlar, valyuta arbitraji
Floyd-WarshallO(V³)HaBarcha juftliklar orasida
A\*O(E) gachaYo'qEvristika mavjud (xaritalar)

A* algoritmi #

Dijkstra barcha yo'nalishga birdek tarqaladi. A\* esa manzil qayerda ekanini taxmin qilib, o'sha tomonga yo'naladi - shuning uchun ancha tez.

Python
import heapq


def a_yulduz(graf, koordinatalar, boshlanish, manzil):
    """A* algoritmi - to'g'ri chiziqli masofani evristika sifatida ishlatadi."""

    def evristika(shahar):
        """Manzilgacha bo'lgan taxminiy (to'g'ri chiziqli) masofa."""
        x1, y1 = koordinatalar[shahar]
        x2, y2 = koordinatalar[manzil]
        return ((x1 - x2) ** 2 + (y1 - y2) ** 2) ** 0.5


    masofalar = {v: float("inf") for v in graf}
    masofalar[boshlanish] = 0
    qayerdan = {boshlanish: None}
    tayyor = set()

    uyum = [(evristika(boshlanish), 0, boshlanish)]

    while uyum:
        _, joriy_masofa, joriy = heapq.heappop(uyum)

        if joriy in tayyor:
            continue
        tayyor.add(joriy)

        if joriy == manzil:
            break

        for qoshni, vazn in graf[joriy].items():
            yangi = joriy_masofa + vazn
            if yangi < masofalar[qoshni]:
                masofalar[qoshni] = yangi
                qayerdan[qoshni] = joriy
                heapq.heappush(uyum, (yangi + evristika(qoshni), yangi, qoshni))

    yol = []
    joriy = manzil
    while joriy is not None:
        yol.append(joriy)
        joriy = qayerdan.get(joriy)

    return yol[::-1], masofalar[manzil], len(tayyor)


# Taxminiy koordinatalar (km, shartli)
koordinatalar = {
    "Toshkent": (690, 410),  "Guliston": (680, 330),  "Jizzax": (670, 300),
    "Samarqand": (670, 250), "Qarshi": (650, 150),    "Buxoro": (640, 200),
    "Xiva": (600, 260),      "Nukus": (590, 420),     "Namangan": (710, 410),
    "Andijon": (720, 400),   "Termiz": (670, 40),
}

yol, masofa, korilgan = a_yulduz(shaharlar, koordinatalar, "Toshkent", "Termiz")
print(f"A*:       {' -> '.join(yol)}")
print(f"Masofa:   {masofa} km, ko'rilgan vershinalar: {korilgan}")

masofalar_d, qayerdan_d = dijkstra(shaharlar, "Toshkent")
print(f"\nDijkstra bir xil natija berdi: {masofalar_d['Termiz'] == masofa}")
Natija
A*:       Toshkent -> Samarqand -> Qarshi -> Termiz
Masofa:   680 km, ko'rilgan vershinalar: 7

Dijkstra bir xil natija berdi: True

A* atigi 7 ta vershinani ko'rdi, Dijkstra esa hammasini (11 ta) tekshirardi.

A* qayerda ishlatiladi?

Yandex Maps, Google Maps va barcha o'yin dvigatellari A* ishlatadi. Evristika sifatida odatda to'g'ri chiziqli masofa olinadi - u haqiqiy masofadan hech qachon katta bo'lmaydi, bu esa algoritmning to'g'riligini kafolatlaydi.

Amaliy topshiriq
  1. shaharlar grafiga yangi shaharlar qo'shing va Dijkstra natijasini tekshiring.
  2. Vaznlar vaqt (soat) bo'lsin, masofa emas. Eng tez marshrutni toping.
  3. Bellman-Ford algoritmini yozing va manfiy sikl aniqlash qismini qo'shing.
  4. Dijkstra ni o'zgartiring: u faqat eng qisqa emas, eng kam o'tish joyi bo'lgan yo'lni topsin (teng masofalarda).

Xulosa #

  • Dijkstra vaznli grafda bitta manbadan barcha vershinalargacha eng qisqa yo'lni topadi.
  • U ochko'z: har qadamda eng yaqin qayta ishlanmagan vershinani tanlaydi.
  • Prioritetli navbat (uyum) murakkablikni O(V²) dan O((V+E) log V) ga tushiradi.
  • Manfiy vaznlar bilan ishlamaydi - u holda Bellman-Ford kerak.
  • A\* evristika yordamida qidiruvni manzil tomon yo'naltiradi va ancha tez ishlaydi.
  • qayerdan jadvali orqali nafaqat masofa, balki to'liq yo'l ham tiklanadi.

Keyingi va yakuniy bo'limda o'rgangan hamma narsani birlashtirib, metro marshrut qidiruvchi dastur yaratamiz.

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.