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.
Ushbu bo‘lim mundarijasi
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 #
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.
| Qadam | Amal |
|---|---|
| 1 | Barcha masofalarni cheksiz, boshlang'ichni 0 deb belgila |
| 2 | Eng kichik masofali qayta ishlanmagan vershinani tanla |
| 3 | Uning qo'shnilari uchun masofani yangila (agar qisqaroq bo'lsa) |
| 4 | Vershinani "tayyor" deb belgila |
| 5 | 2-qadamga qayt, hamma vershina tugaguncha |
Sodda amalga oshirish #
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 #
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}")
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:
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")
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 #
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")
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 #
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.
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 #
| Algoritm | Murakkablik | Manfiy vazn | Qachon |
|---|---|---|---|
| BFS | O(V + E) | - | Vaznsiz graf |
| Dijkstra | O((V+E) log V) | Yo'q | Bitta manbadan, musbat vaznlar |
| Bellman-Ford | O(V · E) | Ha | Manfiy vaznlar, valyuta arbitraji |
| Floyd-Warshall | O(V³) | Ha | Barcha juftliklar orasida |
| A\* | O(E) gacha | Yo'q | Evristika 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.
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}")
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.
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.
shaharlargrafiga yangi shaharlar qo'shing va Dijkstra natijasini tekshiring.- Vaznlar vaqt (soat) bo'lsin, masofa emas. Eng tez marshrutni toping.
- Bellman-Ford algoritmini yozing va manfiy sikl aniqlash qismini qo'shing.
- 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.
qayerdanjadvali 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.
O‘qish tarixini saqlamoqchimisiz?
Tizimga kirsangiz, tugatgan bo‘limlaringiz saqlanadi va qoldirgan joyingizdan davom etasiz.
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.