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.

🕑 11 daqiqa o‘qish 📄 569 so‘z 👁 6 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. BFS va DFS - asosiy farq
  2. Sinov grafi
  3. BFS - kenglik bo'yicha qidiruv
  4. BFS bilan eng qisqa yo'l
  5. Masofalar jadvali
  6. DFS - chuqurlik bo'yicha qidiruv
  7. Rekursiv variant
  8. Stek bilan (rekursiyasiz)
  9. Qo'llanilishi 1: bog'langan komponentlar
  10. Qo'llanilishi 2: siklni aniqlash
  11. Qo'llanilishi 3: topologik saralash
  12. Qaysi birini tanlash?
  13. 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)
StrategiyaAvval barcha qo'shnilarAvval imkon qadar chuqurga
TuzilmaNavbat (deque)Stek yoki rekursiya
TopadiEng qisqa yo'l*Istalgan yo'l
XotiraO(kenglik)O(chuqurlik)
QachonEng yaqin narsani izlashBarcha variantni ko'rish

* vaznsiz grafda

BFS - daraja bo'ylab 1 2 3 4 5 6 butun darajani tugatib, pastga tushadi DFS - chuqurga 1 2 5 3 4 6 oxirigacha tushib, keyin qaytadi
Raqamlar vershinalarga tashrif tartibini ko'rsatadi

Sinov grafi #

Python
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 #

Python
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}")
Natija
 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.

Python
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")
Natija
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
Nima uchun BFS eng qisqasini topadi?

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 #

Python
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}")
Natija
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 #

Python
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}")
Natija
 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:

Python
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])
Natija
['Paxtakor', 'Xalqlar dostligi', 'Milliy bog', 'Novza', 'Chilonzor']
BFS va DFS deyarli bir xil kod

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:

Python
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)}")
Natija
1-guruh (3 kishi): Husanboy, Malika, Sardor
2-guruh (2 kishi): Bekzod, Nodira
3-guruh (1 kishi): Aziza

Qo'llanilishi 2: siklni aniqlash #

Python
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))
Natija
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.

Python
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}")
Natija
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
Bu qayerda ishlatiladi?

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? #

VazifaTanlov
Eng qisqa yo'l (vaznsiz)BFS
Eng yaqin narsani topishBFS
Ijtimoiy tarmoqda "2 qadamdagi tanishlar"BFS
Labirintdan chiqish yo'lini topishDFS
Barcha yo'llarni sanab chiqishDFS
Siklni aniqlashDFS
Topologik saralashDFS
Xotira cheklangan, graf kengDFS
Xotira cheklangan, graf chuqurBFS
Amaliy topshiriq
  1. n_qadamdagi(graf, boshlanish, n) - aynan n qadam narida joylashgan vershinalarni toping.
  2. Labirintni ikki o'lchamli massiv sifatida bering va BFS bilan chiqish yo'lini toping.
  3. Yo'naltirilgan grafda sikl aniqlovchi funksiya yozing (yo'naltirilmaganidan farq qiladi).
  4. 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() va pop() da.
  • korilgan to'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.

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.