-
je množina vrcholů; ,
-
je množina hran; ,
-
hrana je dvojice vrcholů .
Grafy a grafové algoritmy
Editovat
Note
|
Reprezentace grafů. Souvislost grafu, rovinné grafy. Prohledávání grafu do šířky a do hloubky, nejkratší vzdálenosti, kostry, toky v sítích. Algoritmy: Bellman-Ford, Dijkstra, Ford-Fulkerson, Push-Relabel, maximální párování v bipartitních grafech. IB000, IB002, IV003 |
Tip
|
Tahle otázka má solidní překryv s bakalářskými otázkami Grafy a Grafové problémy. |
Terminologie
- Graf
-
Dvojice kde:
- Váha grafu
-
Váha grafu je součet vah hran grafu .
- Bipartitní graf
-
Graf jehož vrcholy lze rozdělit do dvou disjunktních množin tak, že všechny hrany vedou z jedné množiny do druhé.
Obrázek 1. Example of bipartite graph without cycles by Watchduck - (Silná) souvislost grafu / (strongly) connected graph
-
Graf je souvislý, pokud pro každé dva vrcholy existuje cesta z do .
- Slabá souvislost grafu / weakly connected graph
-
Graf je slabě souvislý, pokud je souvislý jeho podgraf vzniklý odebráním orientace hran.
Je souvislý alespoň, pokud zapomeneme, že hrany mají směr?
- Silně souvislá komponenta / strongly connected component
-
Silně souvislá komponenta grafu je jeho maximální podgraf takový, že je silně souvislý. Jinými slovy pro každé dva vrcholy existuje cesta z do .
- Planární / rovinný graf
-
Graf je planární, pokud se dá nakreslit do roviny tak, že se žádné dvě hrany nekříží.
Platí v nich Eulerova formule:
Kde je počet stěn — oblastí ohraničených hranami.
Vrcholy planárního grafu lze vždy obarvit 4 barvami tak, že žádné dva sousední vrcholy nebudou mít stejnou barvu.
- (Hranový) řez / (edge) cut
-
Množina hran taková, že po odebrání hran se graf rozpadne na více komponent — není souvislý.
Analogicky se definuje i vrcholový řez / vertex cut.
Reprezentace grafů
- Seznam následníků / adjacency list
-
Pro každý vrchol máme seznam (např. dynamic array nebo linked list) jeho následníků.
Zabírá paměti.
- Matice sousednosti / adjacency matrix
-
Máme matici velikosti kde pokud existuje hrana mezi a , jinak .
Dá se pěkně použít k uložení vah.
- Matice incidence / incidence matrix
-
Máme matici velikosti kde pokud je vrcholem hrany , jinak .
Dá se z ní pěkně určit stupeň vrcholu.
Prohledávání grafu
Prohlédávání do šířky / breadth-first search (BFS)
Od zadaného vrcholu navštíví nejprve vrcholy vzdálené 1 hranou, poté vrcholy vzdálené 2 hranami, atd.
-
Prohledávání po "vrstvách".
-
Je implementovaný pomocí fronty (queue / FIFO).
-
Časová složitost je .
def dfs(graph: List[List[bool]], stamps: List[int], vertex: int) -> None:
if stamps[vertex] == -1:
stamps[vertex] = 0
stamp = stamps[vertex]
for i in range(0, len(graph)):
if graph[vertex][i] and stamps[i] != -1:
stamps[i] = stamp + 1
dfs(graph, stamps, i)
Prohlédávání do hloubky / depth-first search (DFS)
Od zadaného vrcholu rekurzivně navštěvuje jeho nenavštívené následníky.
-
Prohledání po "slepých uličkách".
-
Vynořuje se teprve ve chvíli, kdy nemá kam dál (backtrackuje).
-
Je implementovaný pomocí zásobníku (stack / LIFO).
-
Časová složitost je .
def bfs(graph: List[List[bool]], stamps: List[int], vertex: int) -> None:
stamp = 0
queue = deque()
queue.append(vertex)
while len(queue) > 0:
current = queue.popleft()
stamps[current] = stamp
stamp += 1
for i in range(0, len(graph)):
if graph[current][i] and stamps[i] == -1:
queue.append(i)
Nejkratší vzdálenosti
Problém nalezení buď nejkratší cesty mezi dvěma vrcholy nebo nejkratší cesty z jednoho vrcholu do všech ostatních.
- Relaxace hrany
-
Zkrácení vzdálenosti k vrcholu průchodem přes vrchol . Musí platit . Hrana je v takovém případě napjatá.
Bellman-Fordův algoritmus
Hledá nejkratší cesty z jednoho vrcholu do všech ostatních.
-
Využívá relaxaci hran.
-
Funguje i na grafech se zápornými hranami.
-
Má časovou složitost .
def bellmanford(graph: List[List[Tuple[int, int]]], s: int) \
-> Tuple[bool, List[int], List[int]]:
# graph is an adjacency list of tuples (dst, weight)
distance = [float('inf') for i in range(0, len(graph))]
distance[s] = 0
parent = [-1 for i in range(0, len(graph))]
# relax all edges |V| - 1 times
for _ in range(1, len(graph)):
for u in range(0, len(graph)):
for edge in graph[u]:
(v, w) = edge
if distance[u] + w < distance[v]:
distance[v] = distance[u] + w
parent[v] = u
# check for negative cycles
for u in range(0, len(graph)):
for edge in graph[u]:
(v, w) = edge
if distance[u] + w < distance[v]:
return (False, None, None)
return (True, distance, parent)
Dijkstrův algoritmus
Hledá nejkratší cesty z jednoho vrcholu do všech ostatních.
-
Je podobný BFS, ale používá prioritní frontu.
-
Funguje pouze na grafech bez záporných hran.
Tip
|
Složitost závisí na implementaci prioritní fronty. Je to insertů, hledání nejmenšího prvku, snížení priority. |
Note
|
Implementace níže používá pole (resp. Pythoní list ), tedy složitost je , jelikož hledání minima je lineární.
|
def dijkstra(graph: List[List[Tuple[int, int]]], s: int) \
-> Tuple[List[int], List[int]]:
# graph is an adjacency list of tuples (dst, weight)
distance = [float('inf') for i in range(0, len(graph))]
distance[s] = 0
parent = [-1 for i in range(0, len(graph))]
queue = list(range(0, len(graph)))
while len(queue) > 0:
u = min(queue, lambda v: distance[v])
queue.remove(u)
for edge in graph[current]:
(v, w) = edge
if distance[u] + w < distance[v]:
distance[v] = distance[u] + w
parent[v] = u
return (distance, parent)
V binární haldě by to bylo a ve Fibonacciho haldě .
Dijkstrův algoritmus lze optimalizovat, pokud nás zajímá jen nejkratší cesta mezi dvěma konkrétními vrcholy:
-
Funkce vrátí výsledek, jakmile je cílový vrchol vytažen z fronty.
-
Můžeme hledat zároveň ze začátku a konce pomocí dvou front a skončit, jakmile se někde potkají.
-
Můžeme přidat potenciál — dodatečnou heuristickou váhu.
ImportantTéhle variantě se říká A* (A star). Věnuje se mu část otázky Umělá inteligence v počítačových hrách.
Kostry
- Spanning tree / kostra
-
Kostra grafu je podgraf takový, že je je strom.
- Minimum spanning tree (MST) / minimální kostra
-
Je kostra grafu s nejmenší možnou váhou. Tedy pro každou kostru grafu :
- Fundamental cycle
-
Fundamental cycle je cyklus v grafu takový, že odebráním libovolné hrany získáme kostru.
- Fundamental cutset / řez
-
Fundamental cutset je množina hran v grafu taková, že přidáním libovolné hrany získáme kostru.
- Red rule
-
Najdi cyklus bez červených hran, vyber v něm neobarvenou hranu s nejvyšší cenou a obarvi ji červeně.
- Blue rule
-
Najdi řez bez modrých hran, vyber v něm neobarvenou hranu s nejmenší cenou a obarvi ji modře.
- Greedy algoritmus
-
Nedeterministicky aplikuj red rule a blue rule, dokud to jde (stačí iterací). Modré hrany tvoří MST.
- Jarníkův / Primův algoritmus
-
Speciální případ greedy algoritmu, kdy aplikujeme pouze blue rule. Princip:
-
Vyber libovolný vrchol a přidej ho do kostry .
-
Opakuj krát:
-
Vyber hranu s nejmenší cenou, která má právě jeden vrchol v .
-
Přidej druhý vrchol do .
-
Složitost: použijeme binární haldu
-
Inicializace ( jako cena hrany mezi prázdnou kostrou a každým vrcholem):
-
Odstranění minima z binární haldy pro každý vrchol ve :
-
Procházení každé hrany z a snižování ceny:
-
Celková složitost:
-
S Fibonacciho haldou jde zlepšit na:
-
- Kruskalův algoritmus
-
Princip: Seřaď hrany podle ceny vzestupně. Postupně přidávej hrany do kostry, vynechej ty, které by vytvořily cyklus.
-
Seřad hrany podle ceny vzestupně.
-
Použij union-find na udržování komponent grafu.
-
Procházej hrany postupně. Pokud oba konce hrany patří do různých množin, přidej ji.
Je to speciální případ greedy algoritmu.
Složitost:
-
Inicializace union-findu:
-
Seřazení hran:
-
Pro každou hranu provádíme dvakrát
find
() a eventuálněunion
(): -
Celková složitost:
-
- Borůvkův algoritmus
-
Je "paralelní". Buduje modré stromy ze všech vrcholů naráz.
-
Pro každý vrchol inicializuj modrý strom.
-
Dokud nemáš jen jeden modrý strom, opakuj fázi:
-
Pro každý modrý strom najdi nejlevnější odchozí hranu a přidej ji (propojíš tak dva stromy).
-
Je to speciální případ greedy algoritmu, který spamuje jen blue rule.
Složitost:
-
Počet komponent v první fázi: .
-
V každé fázi se zmenší počet komponent na polovin. Tím pádem bude fází.
-
Každá fáze zabere času, protože procházíme všechny hrany.
-
Celková složitost:
TipKruskal sice taky buduje stromy na více místech najednou, ale není "paralelní", protože minimalita kostry spoléhá na to, že hrany jsou seřazené. Borůvka takový požadavek nemá, a proto je paralelizovatelnější. -
Algoritmus | Časová složitost | Prostorová složitost |
---|---|---|
Jarník (Prim) s prioritní frontou |
||
Jarník (Prim) s Fibonacciho haldou |
||
Kruskal |
||
Borůvka |
Toky v sítích
- Síť toků / flow network
-
Je čtveřice , kde:
-
je orientovaný graf,
-
je zdroj / source,
-
je cíl / stok / sink; ,
-
je funkce udávající kapacitu hran.
-
- Network flow / tok
-
Je funkce , která splňuje:
-
podmínku kapacity:
-
tok hranou je nezáporný a nepřevyšuje povolennou kapacitu
-
-
podmínku kontinuity:
-
tok do vrcholu je stejný jako tok z vrcholu
-
-
- Hodnota toku
-
Ford-Fulkerson
- Residual network
-
Síť, která vzniká, když je už část kapacity hrany využívána tokem . Umožnuje algoritmům změnit přechozí rozhodnutí a získat využitou kapacitu zpět.
Je to pětice , kde
-
,
-
pokud , ,
-
-
- Augmenting path
-
Jednoduchá cesta v residuální síti .
NoteT.j. cesta která může jít i proti směru toku . Bottleneck kapacita je nejmenší kapacita hran v augmenting path .
To krásné na augmenting cestách je, že pro flow a augmenting path v grafu , existuje tok takový, že . Nový tok lze získat takto:
Augment(f, c, P) { delta = bottleneck(P) foreach(e in P) { if(e in E) { f[e] = f[e] + delta } else { f[reverse(e)] = f[reverse(e)] - delta } } return f }
- Algoritmus Ford-Fulkerson
-
Hledá maximální tok. Augmentuje cestu v residuální síti dokud to jde.
-
pro každou .
-
Najdi cestu v reziduální síti .
-
Augmentuj tok podél .
-
Opakuj dokud se nezasekneš.
Ford-Fulkerson(G) { foreach (e in E) { f(e) = 0 } G_f = reziduální síť vzniklá z G vzhledem k toku f while (existuje s ~> t cesta v G_f) { f = Augment(f, c, P) Updatuj G_f } return f }
-
Push-Relabel
- Pre-flow
-
Ne-tak-docela tok.
Funkce taková, že
-
platí kapacitní podmínka: ,
-
platí relaxováné zachování toku: .
-
- Overflowing vertex
-
Takový vertex , do kterého více přitéká než odtéká.
- Excess flow
-
To, co je v overflowing vertexu navíc.
- Height function
-
Funkce . Řekneme, že je kompatibilní s preflow , právě když
-
source: ,
-
sink: ,
-
height difference: .
NotePokud mezi dvěma vrcholy v reziduální síti vede hrana, pak je nejvýše o jednu úroveň výš než .
-
- Push operace
-
Pro (reziduálně-grafovou) hranu se pokusí přesunout excess flow z do , aniž by porušil (reziduální) kapacitu .
// Assumptions: e_f[v] > 0, c_f( (v, w) > 0) > 0, h[v] > h[w] Push(f, h, v, w) { delta_f = min(e_f[v], c_f(v, w)) if( (v, w) in E) f[(v, w)] += delta_f else f[(w, v)] -= delta_f e_f[v] -= delta_f e_f[w] += delta_f }
- Relabel operace
-
Zvýší výšku natolik, aby neporušil kompatibilitu s .
// Assumptions: // - v is overflowing: e_f[v] > 0 // - all residual neighbors of v the same height or higher: // forall (v, w) in E_f: h[v] <= h[w] Relabel(f, h, v) { h[v] = 1 + min(h[w] | (v, w) in E_f) }
- Algoritmus Push-Relabel (Goldberg-Tarjan)
-
Hledá maximální tok.
Princip: Pokud je nějaký vrchol overflowing, tak ho pushni nebo relabeluj. Pokud ne, tak jsi našel maximální tok.
Push-Relabel(V, E, s, t, c) { // initialize preflow — default values for(v in V) { h[v] = 0 // height function e_f[v] = 0 // excess flow } n = |V| h[s] = n for(e in E) { f[e] = 0 // (pre)flow } // initialize preflow — saturate connections from s for( (s, v) in E) { f[(s, v)] = c(s, v) // preflow maxes out all capacity e_f[v] = c(s, v) // presume all of it excess e_f[s] -= c(s, v) // yes, it will be negative } // the juicy part while(any vertex is overflowing) { v = an overflowing vertex (has e_f[v] > 0) if(v has a neighbor w in G_f such that h(v) > h(w)) { Push(f, h, v, w) } else { Relabel(f, h, v) } } return f }
Korektnost: V průběhu výpočtu platí:
-
Výška vrcholu nikdy neklesá.
-
Pre-flow a výšková funkce jsou kompatibilní.
Složitost:
-
Nejvýše Relabelů.
-
saturujících Push.
-
nesaturujících Push.
-
Relabel i Push jsou v .
-
Celkem: .
-
Ford-Fulkerson | Push-Relabel (Goldberg) |
---|---|
global character |
local character |
update flow along an augmenting path |
update flow on edges |
flow conservation |
preflow |
Maximální párování v bipartitních grafech
- Párování / matching
-
Množina taková, že žádné dvě hrany v nemají společný vrchol. [4]
Prázdná množina je párováním na každém grafu. Graf bez hran má pouze prázdné párování.
Obrázek 2. Příklad párování, které je zároveň maximální by RRPPGG - Maximální párování
-
Takové párování, které má nejvyšší počet hran. Graf může mít několik maximálních párování.
- Perfektní párování
-
Takové párování, které páruje všechny vrcholy grafu. Každé perfektní párování je zároveň maximální.
- Maximum cardinality matching (MCM) v bipartitním grafu
-
Problém nalezení maximálního párování v grafu. Ve speciálním případě, kdy graf je bipartitní, se tento problém dá převést na problém nalezení maximálního toku v síti: [5]
-
Mejmě bipartitní graf .
-
Přidej zdroj a hranu pro každý vrchol z .
-
Přidej stok a ranu pro každý vrchol z .
-
Každé hraně dej kapacitu 1.
-
Spusť algoritmus Ford-Fulkerson.
-