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:

  • je množina vrcholů; ,

  • je množina hran; ,

  • hrana je dvojice vrcholů .

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é.

szp07 bipartite graph
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.

    Important
    Té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.

szp07 spanning tree
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:

  1. Vyber libovolný vrchol a přidej ho do kostry .

  2. Opakuj krát:

    1. Vyber hranu s nejmenší cenou, která má právě jeden vrchol v .

    2. 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.

  1. Seřad hrany podle ceny vzestupně.

  2. Použij union-find na udržování komponent grafu.

  3. 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.

  1. Pro každý vrchol inicializuj modrý strom.

  2. Dokud nemáš jen jeden modrý strom, opakuj fázi:

    1. 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:

Tip
Kruskal 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ší.
Tabulka 1. Složitosti algoritmů
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 .

Note
T.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.

  1. pro každou .

  2. Najdi cestu v reziduální síti .

  3. Augmentuj tok podél .

  4. 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: .

    Note
    Pokud 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: .

Tabulka 2. Srovnání algoritmů Ford-Fulkerson a Push-Relabel
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í.

szp07 matching
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]

  1. Mejmě bipartitní graf .

    szp07 mcm 01
  2. Přidej zdroj a hranu pro každý vrchol z .

  3. Přidej stok a ranu pro každý vrchol z .

    szp07 mcm 02
  4. Každé hraně dej kapacitu 1.

  5. Spusť algoritmus Ford-Fulkerson.

    szp07 mcm 03