11-bo‘lim

Graflar

Graf atamalari, yo'naltirilgan va vaznli graflar, qo'shnilik matritsasi va ro'yxati, ularni tanlash mezonlari.

🕑 9 daqiqa o‘qish 📄 652 so‘z 👁 6 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. Graf nima?
  2. Asosiy atamalar
  3. Grafni saqlashning ikki usuli
  4. 1. Qo'shnilik matritsasi
  5. 2. Qo'shnilik ro'yxati
  6. Qaysi usulni tanlash kerak?
  7. Lug'at bilan sodda graf
  8. Vershina darajasi
  9. Grafning maxsus turlari
  10. Xulosa

Graf - eng universal ma'lumotlar tuzilmasi. Massiv, ro'yxat va daraxtlarning barchasi - grafning maxsus holatlari.

Graf nima? #

Graf - tugunlar (vershina) va ularni bog'lovchi qirralardan iborat tuzilma.

Atrofimizdagi deyarli hamma narsa graf:

SohaTugunQirra
Ijtimoiy tarmoqOdamDo'stlik
XaritaShaharYo'l
InternetSahifaHavola
MetroBekatYo'nalish
KompilyatorModulBog'liqlik
AviatsiyaAeroportReys
Yo'naltirilmagan A B C do'stlik: ikki tomonlama Yo'naltirilgan A B C obuna: bir tomonlama Vaznli 12 7 4 A B C masofa, narx, vaqt
Grafning uch asosiy turi - ular birga ham ishlatilishi mumkin

Asosiy atamalar #

AtamaMa'nosi
Vershina (tugun)Grafning nuqtasi
QirraIkki vershinani bog'lovchi chiziq
Qo'shniBevosita qirra bilan bog'langan vershina
Daraja (degree)Vershinaga tegishli qirralar soni
Yo'l (path)Vershinalar ketma-ketligi
SiklBoshlangan joyga qaytadigan yo'l
Bog'langan grafHar bir vershinadan har biriga yo'l bor
VaznQirraga biriktirilgan son (masofa, narx)
Maxsus holatlar
  • Daraxt = sikli yo'q bog'langan graf
  • Bog'langan ro'yxat = har bir vershinada ko'pi bilan bitta chiquvchi qirra bo'lgan graf
  • DAG (yo'naltirilgan asiklik graf) = sikli yo'q yo'naltirilgan graf; vazifalar bog'liqligi uchun ishlatiladi

Grafni saqlashning ikki usuli #

1. Qo'shnilik matritsasi #

n × n o'lchamli jadval. matritsa[i][j] = 1 bo'lsa, i va j bog'langan.

Python
class MatritsaGraf:
    """Qo'shnilik matritsasi asosidagi graf."""

    def __init__(self, vershinalar):
        self.vershinalar = list(vershinalar)
        self._indeks = {v: i for i, v in enumerate(self.vershinalar)}
        n = len(self.vershinalar)
        self.matritsa = [[0] * n for _ in range(n)]

    def qirra_qosh(self, birinchi, ikkinchi, vazn=1):
        i, j = self._indeks[birinchi], self._indeks[ikkinchi]
        self.matritsa[i][j] = vazn
        self.matritsa[j][i] = vazn        # yo'naltirilmagan

    def bogliqmi(self, birinchi, ikkinchi):
        """O(1) - matritsaning kuchli tomoni."""
        return self.matritsa[self._indeks[birinchi]][self._indeks[ikkinchi]] != 0

    def qoshnilar(self, vershina):
        """O(n) - butun qatorni ko'rish kerak."""
        i = self._indeks[vershina]
        return [self.vershinalar[j]
                for j, qiymat in enumerate(self.matritsa[i]) if qiymat != 0]

    def chiz(self):
        print("     " + "  ".join(f"{v:>2}" for v in self.vershinalar))
        for i, qator in enumerate(self.matritsa):
            print(f"  {self.vershinalar[i]:>2} " + "  ".join(f"{q:>2}" for q in qator))


graf = MatritsaGraf(["A", "B", "C", "D"])
graf.qirra_qosh("A", "B")
graf.qirra_qosh("A", "C")
graf.qirra_qosh("B", "C")
graf.qirra_qosh("C", "D")

graf.chiz()
print("\nA va B bog'liqmi:", graf.bogliqmi("A", "B"))
print("C ning qo'shnilari:", graf.qoshnilar("C"))
Natija
      A   B   C   D
   A  0   1   1   0
   B  1   0   1   0
   C  1   1   0   1
   D  0   0   1   0

A va B bog'liqmi: True
C ning qo'shnilari: ['A', 'B', 'D']

2. Qo'shnilik ro'yxati #

Har bir vershina uchun uning qo'shnilari ro'yxati saqlanadi. Amalda eng ko'p ishlatiladigan usul.

Python
from collections import defaultdict


class Graf:
    """Qo'shnilik ro'yxati asosidagi graf."""

    def __init__(self, yonaltirilgan=False):
        self._qoshnilar = defaultdict(list)
        self.yonaltirilgan = yonaltirilgan

    def vershina_qosh(self, vershina):
        _ = self._qoshnilar[vershina]        # bo'sh ro'yxat yaratadi

    def qirra_qosh(self, birinchi, ikkinchi, vazn=1):
        """O(1)."""
        self._qoshnilar[birinchi].append((ikkinchi, vazn))
        if not self.yonaltirilgan:
            self._qoshnilar[ikkinchi].append((birinchi, vazn))

    def qoshnilar(self, vershina):
        """O(1) - ro'yxatning kuchli tomoni."""
        return self._qoshnilar[vershina]

    def bogliqmi(self, birinchi, ikkinchi):
        """O(daraja) - qo'shnilarni ko'rib chiqish kerak."""
        return any(q == ikkinchi for q, _ in self._qoshnilar[birinchi])

    def vershinalar(self):
        return list(self._qoshnilar.keys())

    def qirralar_soni(self):
        jami = sum(len(q) for q in self._qoshnilar.values())
        return jami if self.yonaltirilgan else jami // 2

    def chiz(self):
        for vershina in sorted(self._qoshnilar):
            bogliqlar = ", ".join(
                f"{q}({v})" if v != 1 else str(q)
                for q, v in self._qoshnilar[vershina]
            )
            print(f"  {vershina} -> {bogliqlar}")


graf = Graf()
for a, b in [("A", "B"), ("A", "C"), ("B", "C"), ("C", "D")]:
    graf.qirra_qosh(a, b)

graf.chiz()
print(f"\nVershinalar: {len(graf.vershinalar())}, qirralar: {graf.qirralar_soni()}")
Natija
  A -> B, C
  B -> A, C
  C -> A, B, D
  D -> C

Vershinalar: 4, qirralar: 4

Qaysi usulni tanlash kerak? #

MezonMatritsaRo'yxat
XotiraO(V²)O(V + E)
Qirra bormi?O(1)O(daraja)
Qo'shnilarni olishO(V)O(1)
Qirra qo'shishO(1)O(1)
Vershina qo'shishO(V²)O(1)

V - vershinalar soni, E - qirralar soni.

Zich graf (dense) E ≈ V² → matritsa qulay Siyrak graf (sparse) E ≈ V → ro'yxat qulay
Graf zichligi saqlash usulini tanlashda hal qiluvchi omil
Amalda deyarli har doim ro'yxat

Real graflar siyrak bo'ladi. Facebook'da 3 milliard foydalanuvchi bor, lekin o'rtacha odamning 300 ta do'sti - 3 milliard emas. Matritsa uchun 9×10¹⁸ katak kerak bo'lardi. Shu sababli ijtimoiy tarmoqlar, xaritalar va tarmoqlar - hammasi qo'shnilik ro'yxati ishlatadi.

Lug'at bilan sodda graf #

Ko'p hollarda alohida klass ham kerak emas:

Python
# Toshkent metrosining soddalashtirilgan sxemasi
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"],
}

print(f"Bekatlar soni: {len(metro)}")
print(f"Paxtakordan yo'nalishlar: {metro['Paxtakor']}")

# Eng ko'p bog'lanishga ega bekat
eng_gavjum = max(metro, key=lambda b: len(metro[b]))
print(f"Eng gavjum tugun: {eng_gavjum} ({len(metro[eng_gavjum])} ta yo'nalish)")
Natija
Bekatlar soni: 10
Paxtakordan yo'nalishlar: ['Xalqlar do'stligi', 'Mustaqillik maydoni', 'Alisher Navoiy']
Eng gavjum tugun: Paxtakor (3 ta yo'nalish)

Bu grafni 20-bo'limdagi yakuniy loyihada ishlatamiz.

Vershina darajasi #

Python
def darajalar(graf):
    """Har bir vershinaning darajasini hisoblaydi."""
    return {v: len(qoshnilar) for v, qoshnilar in graf.items()}


for bekat, daraja in sorted(darajalar(metro).items(), key=lambda j: -j[1])[:4]:
    ustun = "#" * daraja
    print(f"{bekat:<22}{daraja}  {ustun}")
Natija
Paxtakor              3  ###
Chilonzor             2  ##
Novza                 2  ##
Milliy bog'           2  ##
Qo'l siqishlar lemmasi

Yo'naltirilmagan grafda barcha darajalar yig'indisi qirralar sonining ikki barobariga teng - chunki har bir qirra ikkita vershinaning darajasiga hissa qo'shadi. Bundan qiziq xulosa kelib chiqadi: har qanday grafda toq darajali vershinalar soni juft bo'ladi.

Grafning maxsus turlari #

TuriXossasiMisol
To'liq grafHar bir juftlik bog'langanKichik jamoa aloqalari
Ikki bo'lakli (bipartite)Vershinalar ikki guruhga bo'linadiTalaba-fan, xodim-loyiha
DAGYo'naltirilgan, sikli yo'qVazifalar bog'liqligi, git tarixi
DaraxtBog'langan, sikli yo'qFayl tizimi
O'rmonBir nechta alohida daraxtBog'lanmagan komponentlar
Python
def toliq_grafmi(graf):
    """Har bir vershina qolgan hammasi bilan bog'langanmi?"""
    n = len(graf)
    return all(len(qoshnilar) == n - 1 for qoshnilar in graf.values())


kichik_jamoa = {
    "Husanboy": ["Sardor", "Malika"],
    "Sardor": ["Husanboy", "Malika"],
    "Malika": ["Husanboy", "Sardor"],
}
print("To'liq grafmi:", toliq_grafmi(kichik_jamoa))
print("Metro to'liq grafmi:", toliq_grafmi(metro))
Natija
To'liq grafmi: True
Metro to'liq grafmi: False
Amaliy topshiriq
  1. Graf klassiga qirra_ochir(a, b) va vershina_ochir(v) metodlarini qo'shing.
  2. Grafni matritsa ko'rinishidan ro'yxat ko'rinishiga o'giradigan funksiya yozing.
  3. Berilgan graf ikki bo'lakli ekanini tekshiring (maslahat: vershinalarni ikki rangga bo'yang).
  4. Ijtimoiy tarmoq grafida "umumiy do'stlar" ni topadigan funksiya yozing.

Xulosa #

  • Graf - vershinalar va qirralardan iborat eng universal tuzilma.
  • U yo'naltirilgan/yo'naltirilmagan va vaznli/vaznsiz bo'lishi mumkin.
  • Qo'shnilik matritsasi: O(V²) xotira, qirrani tekshirish O(1) - zich graflar uchun.
  • Qo'shnilik ro'yxati: O(V + E) xotira, qo'shnilarni olish O(1) - amalda deyarli har doim shu.
  • Python'da oddiy dict ko'p hollarda yetarli graf tuzilmasini beradi.

Keyingi bo'limda graf bo'ylab yurishning ikki asosiy usuli - BFS va DFS 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.