Məzmuna keç
Educora
Universitet25 dəq50 / 59

Qraflar və qraf alqoritmləri

Qrafların təsvir üsullarını, eninə (BFS) və dərininə (DFS) axtarışı, Deykstra alqoritmini, topoloji çeşidləməni və minimal örtücü ağacı (Kruskal) addım-addım öyrən.

Özünü yoxla
Bu dərsdə öyrənəcəksən
  • Qrafı qonşuluq matrisi və ya qonşuluq siyahısı ilə təsvir etmək və seçimi əsaslandırmaq
  • BFS, DFS və Deykstra alqoritmlərini əl ilə izləmək
  • Topoloji sıra və minimal örtücü ağac tapmaq

Naviqator Bakıdan Şəkiyə ən qısa yolu saniyədə tapır, sosial şəbəkə «tanıya biləcəyin insanları» təklif edir, universitet isə fənlərin ardıcıllığını elə qurur ki, heç kim «Alqoritmlər»ə «Proqramlaşdırma»dan əvvəl yazılmasın. Üç fərqli məsələ, bir model: qraf. Qraf obyektləri (təpələri) və onlar arasındakı əlaqələri (tilləri) təsvir edir və informatikanın ən çox işlədilən modellərindəndir.

Qraflar və onların təsviri

Tərif
Qraf G = (V, E)

V təpələr çoxluğu və E tillər (təpə cütləri) çoxluğu. Tillər istiqamətsiz (dostluq) və ya istiqamətlənmiş (A → B, «izləyir») ola bilər; hər tilin çəkisi (məsafə, qiymət, vaxt) varsa, qraf çəkili adlanır. Təpənin dərəcəsi ona bitişik tillərin sayıdır.

∑ deg(v) = 2|E|, |E| ≤ |V| · (|V| − 1) / 2∑ deg(v) = 2|E|, |E| ≤ |V| · (|V| − 1) / 2
burada:
  • deg(v)v təpəsinin dərəcəsi
  • |V|, |E|təpələrin və tillərin sayı

«Əl sıxma lemması»: hər istiqamətsiz til iki ucunda iki dərəcə verir. İkinci bərabərsizlik sadə istiqamətsiz qrafda tillərin maksimal sayıdır.

XassəQonşuluq matrisiQonşuluq siyahıları
yaddaşO(V²)O(V + E)
(u, v) tili varmı?O(1)O(deg u)
u-nun bütün qonşularını gəzməkO(V)O(deg u)
nə vaxt seçilirsıx qraf (E ≈ V²)seyrək qraf (yollar, sosial şəbəkələr)

Dərs boyu bir nümunə qrafdan istifadə edəcəyik. Altı təpə A–F və çəkili istiqamətsiz tillər: A–B 4, A–C 2, B–C 1, B–D 5, C–D 8, C–E 10, D–E 2, D–F 6, E–F 3. Çəkiləri kilometr kimi təsəvvür et.

Eninə (BFS) və dərininə (DFS) axtarış

BFS başlanğıc təpədən «dalğa» kimi yayılır: əvvəl bütün qonşular, sonra qonşuların qonşuları. Bunun üçün növbə işlədilir və nəticədə çəkisiz qrafda ən az til sayı ilə ən qısa yollar alınır. DFS isə bir yol ilə mümkün qədər dərinə gedir, dalana çatanda geri qayıdır; o, stek (və ya rekursiya) ilə işləyir və əlaqəlilik, dövrlərin tapılması və topoloji çeşidləmə üçün istifadə olunur. Hər ikisi hər təpəyə və hər tilə bir dəfə baxır: O(V + E).

Nümunə 1: F-dən BFS və DFS

Çəkiləri nəzərə almadan nümunə qrafda F-dən BFS və DFS apar. Qonşuları əlifba sırası ilə götür: A: B, C; B: A, C, D; C: A, B, D, E; D: B, C, E, F; E: C, D, F; F: D, E.

Həllini göstər
BFS (növbə): [F] → F-i çıxar, D və E əlavə et (məsafə 1) → D-ni çıxar, B və C əlavə et (məsafə 2) → E-nin yeni qonşusu yoxdur → B-ni çıxar, A əlavə et (məsafə 3).
Səviyyələr: F | D, E | B, C | A.
DFS (rekursiya): F → D (F-in ilk qonşusu) → B (D-nin ilk ziyarət olunmamış qonşusu) → A → C (A-nın qonşusu) → E (C-nin ziyarət olunmamış qonşusu).
Sıra: F, D, B, A, C, E — DFS A-ya 4 addımla çatdı, BFS isə ən qısa yolu (3 til) tapdı.
Python
from collections import deque

graph = {'A': ['B', 'C'], 'B': ['A', 'C', 'D'], 'C': ['A', 'B', 'D', 'E'],
         'D': ['B', 'C', 'E', 'F'], 'E': ['C', 'D', 'F'], 'F': ['D', 'E']}

def bfs(start):
    dist = {start: 0}
    queue = deque([start])
    while queue:
        v = queue.popleft()
        for w in graph[v]:
            if w not in dist:
                dist[w] = dist[v] + 1
                queue.append(w)
    return dist

def dfs(v, seen):
    seen.append(v)
    for w in graph[v]:
        if w not in seen:
            dfs(w, seen)
    return seen

print('BFS from F:', bfs('F'))
print('DFS from F:', dfs('F', []))
▸ Gözlənilən nəticə
BFS from F: {'F': 0, 'D': 1, 'E': 1, 'B': 2, 'C': 2, 'A': 3}
DFS from F: ['F', 'D', 'B', 'A', 'C', 'E']
Qonşuluq siyahıları lüğətdə saxlanır. BFS məsafələri təpələri tapdığı sırada verir; DFS sırası əl ilə izləməmizlə eynidir.

Deykstra alqoritmi: çəkili ən qısa yollar

Tillərin çəkisi olanda BFS yaramır: 3 qısa til bir uzun tildən ucuz ola bilər. Deykstra alqoritmi (mənfi olmayan çəkilər üçün) hər addımda hələ qəti olmayan təpələr arasından məsafəsi ən kiçik olanı seçir (prioritet növbəsi), onu qəti elan edir və qonşularının məsafələrini relaksasiya ilə yeniləyir.

dist[v] ← min(dist[v], dist[u] + w(u, v)); T = O((V + E) · log V)
burada:
  • dist[v]mənbədən v-yə indiyədək tapılmış ən qısa məsafə
  • w(u, v)u–v tilinin çəkisi (≥ 0)
  • Tikili yığınla iş vaxtı
Addım: qəti olanABCDEF
0: başlanğıc0∞∞∞∞∞
1: A (0)042∞∞∞
2: C (2)0321012∞
3: B (3)032812∞
4: D (8)03281014
5: E (10)03281013
6: F (13)03281013
A-dan Deykstra izləməsi (hər addımdan sonrakı dist qiymətləri). 2-ci addımda B 4-dən 3-ə düşür (A → C → B), 4-cü və 5-ci addımlarda E və F yaxşılaşır.
Nümunə 2: yolun bərpası

Cədvələ əsasən A-dan F-ə ən qısa yolu və onun uzunluğunu tap.

Həllini göstər
Hər yeniləmədə «haradan gəldik» (prev) yadda saxlanır: F son dəfə E-dən (10 + 3), E D-dən (8 + 2), D B-dən (3 + 5), B C-dən (2 + 1), C isə A-dan (0 + 2) yeniləndi.
Sondan geriyə: F ← E ← D ← B ← C ← A.
Yol: A → C → B → D → E → F, uzunluq 2 + 1 + 5 + 2 + 3 = 13.
Diqqət: birbaşa görünən A → B → D → F yolu 4 + 5 + 6 = 15-dir.
Python
import heapq

edges = [('A', 'B', 4), ('A', 'C', 2), ('B', 'C', 1), ('B', 'D', 5), ('C', 'D', 8),
         ('C', 'E', 10), ('D', 'E', 2), ('D', 'F', 6), ('E', 'F', 3)]
adj = {}
for u, v, w in edges:
    adj.setdefault(u, []).append((v, w))
    adj.setdefault(v, []).append((u, w))

def dijkstra(src):
    dist, prev, pq = {src: 0}, {}, [(0, src)]
    while pq:
        d, u = heapq.heappop(pq)
        if d > dist[u]:
            continue
        for v, w in adj[u]:
            if d + w < dist.get(v, float('inf')):
                dist[v], prev[v] = d + w, u
                heapq.heappush(pq, (d + w, v))
    return dist, prev

dist, prev = dijkstra('A')
path = ['F']
while path[-1] != 'A':
    path.append(prev[path[-1]])
print(dict(sorted(dist.items())))
print(' -> '.join(reversed(path)), '=', dist['F'])
▸ Gözlənilən nəticə
{'A': 0, 'B': 3, 'C': 2, 'D': 8, 'E': 10, 'F': 13}
A -> C -> B -> D -> E -> F = 13
heapq prioritet növbəsi rolunu oynayır. Təpə növbəyə bir neçə dəfə düşə bilər; köhnəlmiş qeydləri d > dist[u] şərti atır («tənbəl silmə»).

Topoloji çeşidləmə və minimal örtücü ağac

Topoloji çeşidləmə istiqamətlənmiş dövrsüz qrafın (DAG) təpələrini elə düzür ki, hər u → v tili üçün u v-dən əvvəl gəlsin: fənlərin ardıcıllığı, proqram modullarının yığılma sırası, Excel-də xanaların hesablanma sırası. Kan alqoritmi: daxil olan dərəcəsi 0 olan təpələri növbəyə qoy, birini çıxar, onun tillərini sil və dərəcəsi 0-a enən təpələri növbəyə əlavə et. Nəticədə V-dən az təpə çıxırsa, qrafda dövr var. Vaxt: O(V + E).

Python
from collections import deque

courses = {'Programming': ['DataStructures', 'Databases'], 'Math': ['Algorithms'],
           'DataStructures': ['Algorithms'], 'Databases': ['AI'],
           'Algorithms': ['AI'], 'AI': []}

def topo_sort(g):
    indeg = {v: 0 for v in g}
    for v in g:
        for w in g[v]:
            indeg[w] += 1
    queue = deque(v for v in g if indeg[v] == 0)
    order = []
    while queue:
        v = queue.popleft()
        order.append(v)
        for w in g[v]:
            indeg[w] -= 1
            if indeg[w] == 0:
                queue.append(w)
    return order if len(order) == len(g) else None

print(topo_sort(courses))
print(topo_sort({'X': ['Y'], 'Y': ['Z'], 'Z': ['X']}))
▸ Gözlənilən nəticə
['Programming', 'Math', 'DataStructures', 'Databases', 'Algorithms', 'AI']
None
Fənlər qrafı üçün etibarlı sıra alınır; X → Y → Z → X dövrü olan qraf üçün isə None qaytarılır — belə tələblərlə heç bir fənnə başlamaq mümkün deyil.
Tərif
Minimal örtücü ağac (MST)

Əlaqəli çəkili qrafın bütün təpələrini birləşdirən, dövrü olmayan və tillərinin çəkiləri cəmi ən kiçik olan tillər çoxluğu. Onun həmişə düz V − 1 tili olur. Tətbiq: kəndləri ən az kabel ilə birləşdirmək, elektrik və su şəbəkələrinin layihələndirilməsi, klasterləşmə.

Nümunə 3: Kruskal alqoritmi

Nümunə qrafın minimal örtücü ağacını Kruskal alqoritmi ilə tap: tilləri çəkiyə görə artan sıra ilə götür və dövr yaratmayanları seç.

Həllini göstər
Sıra: B–C 1, A–C 2, D–E 2, E–F 3, A–B 4, B–D 5, D–F 6, C–D 8, C–E 10.
B–C 1 ✓ → {B, C}
A–C 2 ✓ → {A, B, C}
D–E 2 ✓ → {D, E}
E–F 3 ✓ → {D, E, F}
A–B 4 ✗ — A və B artıq eyni komponentdədir (A–C–B dövrü yaranardı)
B–D 5 ✓ → bütün təpələr birləşdi; 5 = V − 1 til, dayanırıq.
MST: {B–C, A–C, D–E, E–F, B–D}, ümumi çəki 1 + 2 + 2 + 3 + 5 = 13.
Python
edges = [('A', 'B', 4), ('A', 'C', 2), ('B', 'C', 1), ('B', 'D', 5), ('C', 'D', 8),
         ('C', 'E', 10), ('D', 'E', 2), ('D', 'F', 6), ('E', 'F', 3)]
parent = {v: v for v in 'ABCDEF'}

def find(v):
    while parent[v] != v:
        v = parent[v]
    return v

mst = []
for u, v, w in sorted(edges, key=lambda e: e[2]):
    ru, rv = find(u), find(v)
    if ru != rv:
        parent[ru] = rv
        mst.append((u, v, w))
print(mst)
print('total weight:', sum(w for _, _, w in mst))
▸ Gözlənilən nəticə
[('B', 'C', 1), ('A', 'C', 2), ('D', 'E', 2), ('E', 'F', 3), ('B', 'D', 5)]
total weight: 13
«Dövr yaradırmı?» sualına birləşmə–tapma (union–find) strukturu cavab verir: find komponentin «başçısını» tapır, iki təpənin başçısı eynidirsə, til dövr yaradar. Kruskalın vaxtı çeşidləmə ilə müəyyən olunur: O(E log E). Prim alqoritmi isə ağacı bir təpədən böyüdür (Deykstraya bənzər, prioritet növbəsi ilə).

Əsas fikirlər

  • Qraf G = (V, E); seyrək qraf üçün qonşuluq siyahısı (O(V + E)), sıx qraf üçün matris (O(V²)).
  • BFS növbə ilə səviyyə-səviyyə gedir və çəkisiz ən qısa yolları verir; DFS stek ilə dərinə gedir; hər ikisi O(V + E).
  • Deykstra mənfi olmayan çəkilərlə prioritet növbəsi və relaksasiya ilə O((V + E) log V) vaxtda işləyir.
  • Topoloji sıra yalnız DAG-da mövcuddur; Kan alqoritmi dövrü də aşkar edir.
  • MST-nin V − 1 tili var; Kruskal tilləri çəkiyə görə düzür və union–find ilə dövrlərdən qaçır.

Özünü yoxla

10 sual. Hər düzgün cavab XP qazandırır.

1 / 10
Yol şəbəkəsində V = 10⁴ kəsişmə və E = 3 · 10⁴ yol var. Qonşuluq matrisi neçə xana tələb edir?