12-bo‘lim
Graf bo'ylab yurish - BFS va DFS
Kenglik va chuqurlik bo'yicha qidiruv, ularning farqi, eng qisqa yo'lni topish, bog'langan komponentlar va topologik saralash.
Ushbu bo‘lim mundarijasi
- BFS va DFS - asosiy farq
- Sinov grafi
- BFS - kenglik bo'yicha qidiruv
- BFS bilan eng qisqa yo'l
- Masofalar jadvali
- DFS - chuqurlik bo'yicha qidiruv
- Rekursiv variant
- Stek bilan (rekursiyasiz)
- Qo'llanilishi 1: bog'langan komponentlar
- Qo'llanilishi 2: siklni aniqlash
- Qo'llanilishi 3: topologik saralash
- Qaysi birini tanlash?
- Xulosa
Graf tuzilgandan keyin asosiy savol tug'iladi: uni qanday aylanib chiqish kerak? Ikkita asosiy strategiya bor va ular butunlay boshqa natija beradi.
BFS va DFS - asosiy farq #
| BFS (kenglik) | DFS (chuqurlik) | |
|---|---|---|
| Strategiya | Avval barcha qo'shnilar | Avval imkon qadar chuqurga |
| Tuzilma | Navbat (deque) | Stek yoki rekursiya |
| Topadi | Eng qisqa yo'l* | Istalgan yo'l |
| Xotira | O(kenglik) | O(chuqurlik) |
| Qachon | Eng yaqin narsani izlash | Barcha variantni ko'rish |
* vaznsiz grafda
Sinov grafi #
metro = {
"Chilonzor": ["Mirzo Ulug'bek", "Novza"],
"Novza": ["Chilonzor", "Milliy bog'"],
"Milliy bog'": ["Novza", "Xalqlar do'stligi"],
"Xalqlar do'stligi": ["Milliy bog'", "Paxtakor"],
"Paxtakor": ["Xalqlar do'stligi", "Mustaqillik maydoni", "Alisher Navoiy"],
"Mustaqillik maydoni": ["Paxtakor", "Amir Temur xiyoboni"],
"Alisher Navoiy": ["Paxtakor", "G'afur G'ulom"],
"G'afur G'ulom": ["Alisher Navoiy"],
"Amir Temur xiyoboni": ["Mustaqillik maydoni"],
"Mirzo Ulug'bek": ["Chilonzor"],
}
BFS - kenglik bo'yicha qidiruv #
from collections import deque
def bfs(graf, boshlangich):
"""Grafni daraja bo'ylab aylanadi. O(V + E)."""
korilgan = {boshlangich}
navbat = deque([boshlangich])
tartib = []
while navbat:
joriy = navbat.popleft() # navbatning BOSHIDAN
tartib.append(joriy)
for qoshni in graf[joriy]:
if qoshni not in korilgan:
korilgan.add(qoshni) # navbatga qo'shishda belgilaymiz
navbat.append(qoshni)
return tartib
for raqam, bekat in enumerate(bfs(metro, "Paxtakor"), start=1):
print(f"{raqam:>2}. {bekat}")
1. Paxtakor
2. Xalqlar do'stligi
3. Mustaqillik maydoni
4. Alisher Navoiy
5. Milliy bog'
6. Amir Temur xiyoboni
7. G'afur G'ulom
8. Novza
9. Chilonzor
10. Mirzo Ulug'bek
Diqqat: avval Paxtakorning barcha qo'shnilari, keyin ularning qo'shnilari.
korilgan ga qachon qo'shish kerak?Vershinani navbatga qo'shayotganda belgilang, navbatdan olayotganda emas. Aks holda bir vershina navbatga bir necha marta tushib, algoritm eksponensial sekinlashadi. Bu eng ko'p uchraydigan BFS xatosi.
BFS bilan eng qisqa yo'l #
BFS ning eng qimmatli xossasi: vaznsiz grafda u topgan yo'l har doim eng qisqa.
from collections import deque
def eng_qisqa_yol(graf, boshlanish, tugash):
"""Vaznsiz grafda eng qisqa yo'lni topadi. O(V + E)."""
if boshlanish == tugash:
return [boshlanish]
korilgan = {boshlanish}
navbat = deque([boshlanish])
qayerdan = {boshlanish: None} # yo'lni tiklash uchun
while navbat:
joriy = navbat.popleft()
for qoshni in graf[joriy]:
if qoshni in korilgan:
continue
korilgan.add(qoshni)
qayerdan[qoshni] = joriy
navbat.append(qoshni)
if qoshni == tugash: # topdik - yo'lni tiklaymiz
yol = []
tugun = tugash
while tugun is not None:
yol.append(tugun)
tugun = qayerdan[tugun]
return yol[::-1]
return None # yo'l yo'q
yol = eng_qisqa_yol(metro, "Mirzo Ulug'bek", "G'afur G'ulom")
print("Eng qisqa marshrut:")
for raqam, bekat in enumerate(yol, start=1):
print(f" {raqam}. {bekat}")
print(f"\nJami: {len(yol) - 1} ta bekat")
Eng qisqa marshrut:
1. Mirzo Ulug'bek
2. Chilonzor
3. Novza
4. Milliy bog'
5. Xalqlar do'stligi
6. Paxtakor
7. Alisher Navoiy
8. G'afur G'ulom
Jami: 7 ta bekat
BFS vershinalarni masofa bo'yicha aylanadi: avval 1 qadamdagilar, keyin 2 qadamdagilar va hokazo. Manzilga birinchi marta yetganda, siz unga eng kam qadamda yetgan bo'lasiz. Bu faqat vaznsiz graflarda ishlaydi - vaznli graf uchun Dijkstra kerak (19-bo'lim).
Masofalar jadvali #
from collections import deque
def masofalar(graf, boshlangich):
"""Har bir vershinagacha bo'lgan eng qisqa masofani hisoblaydi."""
masofa = {boshlangich: 0}
navbat = deque([boshlangich])
while navbat:
joriy = navbat.popleft()
for qoshni in graf[joriy]:
if qoshni not in masofa:
masofa[qoshni] = masofa[joriy] + 1
navbat.append(qoshni)
return masofa
print("Paxtakordan masofalar:")
for bekat, qadam in sorted(masofalar(metro, "Paxtakor").items(), key=lambda j: j[1]):
print(f" {qadam} bekat {'#' * qadam:<6} {bekat}")
Paxtakordan masofalar:
0 bekat Paxtakor
1 bekat # Xalqlar do'stligi
1 bekat # Mustaqillik maydoni
1 bekat # Alisher Navoiy
2 bekat ## Milliy bog'
2 bekat ## Amir Temur xiyoboni
2 bekat ## G'afur G'ulom
3 bekat ### Novza
4 bekat #### Chilonzor
5 bekat ##### Mirzo Ulug'bek
DFS - chuqurlik bo'yicha qidiruv #
Rekursiv variant #
def dfs_rekursiv(graf, joriy, korilgan=None, tartib=None):
"""Chuqurlik bo'yicha aylanish. O(V + E)."""
if korilgan is None:
korilgan, tartib = set(), []
korilgan.add(joriy)
tartib.append(joriy)
for qoshni in graf[joriy]:
if qoshni not in korilgan:
dfs_rekursiv(graf, qoshni, korilgan, tartib)
return tartib
for raqam, bekat in enumerate(dfs_rekursiv(metro, "Paxtakor"), start=1):
print(f"{raqam:>2}. {bekat}")
1. Paxtakor
2. Xalqlar do'stligi
3. Milliy bog'
4. Novza
5. Chilonzor
6. Mirzo Ulug'bek
7. Mustaqillik maydoni
8. Amir Temur xiyoboni
9. Alisher Navoiy
10. G'afur G'ulom
Bu safar algoritm birinchi yo'nalish bo'yicha oxirigacha bordi, keyin qaytdi.
Stek bilan (rekursiyasiz) #
Chuqur graflarda RecursionError dan qochish uchun:
def dfs_stek(graf, boshlangich):
"""Rekursiyasiz DFS - stek yordamida."""
korilgan = set()
stek = [boshlangich]
tartib = []
while stek:
joriy = stek.pop() # stekning OXIRIDAN - BFS dan yagona farq!
if joriy in korilgan:
continue
korilgan.add(joriy)
tartib.append(joriy)
for qoshni in reversed(graf[joriy]):
if qoshni not in korilgan:
stek.append(qoshni)
return tartib
print(dfs_stek(metro, "Paxtakor")[:5])
['Paxtakor', 'Xalqlar dostligi', 'Milliy bog', 'Novza', 'Chilonzor']
Ikkalasining farqi bitta satrda: popleft() (navbat) yoki pop() (stek).
Qolgan hamma narsa bir xil. Bu ikki algoritmni eslab qolishning eng oson usuli.
Qo'llanilishi 1: bog'langan komponentlar #
Graf bir necha alohida bo'lakka bo'lingan bo'lishi mumkin:
def komponentlar(graf):
"""Grafning bog'langan bo'laklarini topadi."""
korilgan = set()
natija = []
for vershina in graf:
if vershina in korilgan:
continue
komponent = []
stek = [vershina]
while stek:
joriy = stek.pop()
if joriy in korilgan:
continue
korilgan.add(joriy)
komponent.append(joriy)
stek.extend(q for q in graf[joriy] if q not in korilgan)
natija.append(sorted(komponent))
return natija
tarmoq = {
"Husanboy": ["Sardor"],
"Sardor": ["Husanboy", "Malika"],
"Malika": ["Sardor"],
"Bekzod": ["Nodira"],
"Nodira": ["Bekzod"],
"Aziza": [],
}
for raqam, guruh in enumerate(komponentlar(tarmoq), start=1):
print(f"{raqam}-guruh ({len(guruh)} kishi): {', '.join(guruh)}")
1-guruh (3 kishi): Husanboy, Malika, Sardor
2-guruh (2 kishi): Bekzod, Nodira
3-guruh (1 kishi): Aziza
Qo'llanilishi 2: siklni aniqlash #
def sikl_bormi(graf):
"""Yo'naltirilmagan grafda sikl borligini tekshiradi."""
korilgan = set()
def tekshir(joriy, ota):
korilgan.add(joriy)
for qoshni in graf[joriy]:
if qoshni not in korilgan:
if tekshir(qoshni, joriy):
return True
elif qoshni != ota: # ko'rilgan va otamiz emas -> sikl
return True
return False
for vershina in graf:
if vershina not in korilgan:
if tekshir(vershina, None):
return True
return False
daraxt = {"A": ["B", "C"], "B": ["A"], "C": ["A"]}
sikli_bor = {"A": ["B", "C"], "B": ["A", "C"], "C": ["A", "B"]}
print("Daraxtda sikl:", sikl_bormi(daraxt))
print("Uchburchakda sikl:", sikl_bormi(sikli_bor))
Daraxtda sikl: False
Uchburchakda sikl: True
Qo'llanilishi 3: topologik saralash #
Vazifalar bir-biriga bog'liq bo'lganda, ularni to'g'ri tartibda bajarish kerak. Bu DAG ustidagi DFS bilan hal qilinadi.
def topologik_sarala(bogliqliklar):
"""Bajarish tartibini aniqlaydi. DAG uchun. O(V + E)."""
korilgan = set()
jarayonda = set()
natija = []
def yur(vazifa):
if vazifa in jarayonda:
raise ValueError(f"Aylanma bog'liqlik: {vazifa}")
if vazifa in korilgan:
return
jarayonda.add(vazifa)
for keyingi in bogliqliklar.get(vazifa, []):
yur(keyingi)
jarayonda.discard(vazifa)
korilgan.add(vazifa)
natija.append(vazifa)
for vazifa in bogliqliklar:
yur(vazifa)
return natija[::-1]
# "A: [B, C]" = A ni bajarish uchun avval B va C kerak
loyiha = {
"Saytni ishga tushirish": ["Testlash", "Serverni sozlash"],
"Testlash": ["Backend yozish", "Frontend yozish"],
"Backend yozish": ["Ma'lumotlar bazasini loyihalash"],
"Frontend yozish": ["Dizayn tayyorlash"],
"Ma'lumotlar bazasini loyihalash": [],
"Dizayn tayyorlash": [],
"Serverni sozlash": [],
}
print("Bajarish tartibi:")
for raqam, vazifa in enumerate(topologik_sarala(loyiha), start=1):
print(f" {raqam}. {vazifa}")
Bajarish tartibi:
1. Ma'lumotlar bazasini loyihalash
2. Backend yozish
3. Dizayn tayyorlash
4. Frontend yozish
5. Testlash
6. Serverni sozlash
7. Saytni ishga tushirish
npm install, pip install va make kabi vositalar aynan shu algoritm bilan
paketlarni to'g'ri tartibda o'rnatadi. Aylanma bog'liqlik topilsa, ular
"circular dependency" xatosini beradi - yuqoridagi ValueError kabi.
Qaysi birini tanlash? #
| Vazifa | Tanlov |
|---|---|
| Eng qisqa yo'l (vaznsiz) | BFS |
| Eng yaqin narsani topish | BFS |
| Ijtimoiy tarmoqda "2 qadamdagi tanishlar" | BFS |
| Labirintdan chiqish yo'lini topish | DFS |
| Barcha yo'llarni sanab chiqish | DFS |
| Siklni aniqlash | DFS |
| Topologik saralash | DFS |
| Xotira cheklangan, graf keng | DFS |
| Xotira cheklangan, graf chuqur | BFS |
n_qadamdagi(graf, boshlanish, n)- aynannqadam narida joylashgan vershinalarni toping.- Labirintni ikki o'lchamli massiv sifatida bering va BFS bilan chiqish yo'lini toping.
- Yo'naltirilgan grafda sikl aniqlovchi funksiya yozing (yo'naltirilmaganidan farq qiladi).
- DFS yordamida ikki vershina orasidagi barcha yo'llarni toping.
Xulosa #
- BFS navbat ishlatadi va daraja bo'ylab yuradi; vaznsiz grafda eng qisqa yo'lni topadi.
- DFS stek yoki rekursiya ishlatadi va imkon qadar chuqurga tushadi.
- Ikkala algoritmning murakkabligi O(V + E).
- Ularning kodi deyarli bir xil - farq faqat
popleft()vapop()da. korilganto'plamiga vershinani navbatga qo'shayotganda qo'shing.- DFS bog'langan komponentlar, sikl aniqlash va topologik saralash uchun asos.
Keyingi bo'limda saralash algoritmlariga o'tamiz.
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.