Vydělávej až 160.000 Kč měsíčně! Akreditované rekvalifikační kurzy s garancí práce od 0 Kč. Více informací.
Hledáme nové posily do ITnetwork týmu. Podívej se na volné pozice a přidej se do nejagilnější firmy na trhu - Více informací.

3. diel - Hľadanie najkratšej cesty v grafe

V minulej lekcii, Náhodný priechod grafom, priechod do hĺbky a do šírky , sme sa naučili nejako prechádzať graf. V reálnej situácii to je ale trochu zložitejšia. Graf obsahuje hromadu kružníc a ohodnotené hrany. Na druhú stranu sme schopní pomocou grafov riešiť reálne problémy. Jeden z prvých problémov je hľadanie najkratšej cesty. Prečo nepoužiť jednoduché BFS?

BFS

Z článku o vlne poznáme algoritmus prehľadávanie do šírky. Ten algoritmus je skvelý, nemožno ho ale použiť pre graf, kde sú hrany ohodnotené. Prečo? Predstavte si, že by sme mali graf, ktorý vyzerá ako tu na obrázku, alebo sme Lojzko z Nemanice, ktorý chce domov do Palice:

Hľadanie najkratšej cesty v grafe - Grafové algoritmy

Pri použití BFS by sme si museli všetky cesty nasekať a prechádzať každý stav sám o sebe. Aneb cestu 500 km dlhou by sme museli rozdeliť na 500 dielov a to je veľmi neefektívne, viď obrázok:

Použitie šírenie do šírky na hľadanie najkratšej cesty v grafe - Grafové algoritmy

Sami si môžeme všimnúť, že jednoduchšie by bolo preskúmať cestu rovno do Palice, alebo to skúsiť vziať cez Kvasnice.

Motivácia zo života

Lojzo robí pre Jesenicko mliekáreň, ktorá rozváža mlieko po celej Českej republike a ktorej vedenie sa rozhodlo pre modernizáciu. Preto poverí Lojza, aby zefektívnil dopravu. Prvou úlohou je nájsť najkratšiu cestu z Jeseníka do každého významného mesta. Lojzo teda vzal ceruzku, papier a písal. Keďže nechcem nikoho uraziť, budem označovať jednotlivé mestá písmenami. Len pre istotu :)

BFS - Grafové algoritmy

Pri reálnej situácii bude graf ešte zložitými na. Niekoľko poznámok k tomuto grafu. Pozor na to, že nie je ani veľmi dôležité, ako je graf nakreslený. Dlhé hrany môžu byť ohodnotené malým číslom a krátke veľkým. Jediné, čo graf vizuálne hovorí, je, že sú dva vrcholy spojené. Vieme, odkiaľ kam vrchol vedie a akú má váhu.

A čo je to váha? Môže to byť vzdialenosť, cena, čas dojazdu medzi dvoma mestami ... To záleží od účelu. Pre potreby firmy sa asi oplatí ohodnotiť si hrany cenou, tj. Koľko ich bude stáť jazda medzi A a B. Niekedy totiž môže dlhšiu cesta vyjsť lacnejšie, napr. Preto, že nie je cez spoplatnenú komunikáciu. V našom prípade to sú zatiaľ len vzdialenosti.

Dijkstrov algoritmus

Dijkstrov algoritmus je najpopulárnejší a najpoužívanejší algoritmus na hľadanie najkratšej cesty v grafe. Myšlienka je jednoduchá. Chceme najkratšiu cestu z jedného uzla do všetkých ostatných (tým pádom nájdeme aj cestu z A do B). Zadáme počiatočný uzol a ideme. Implementáciu ako takú nájdete v článku Implementácia Dijkstrova algoritmu.

Teraz si popíšeme iba algoritmus.

Vstup

Vstupom je ohodnotený graf G a počiatočné uzol s (štart).

Výstup

Výstupy je pole vzdialenosťou, udávajúce najkratšej vzdialenosti medzi uzlom s a všetkými ostatnými a polia predchodcov.

Opis algoritmu

Pro každý vrchol v:
    // máme 3 stavy vrcholů - nenalezený, otevřený, uzavřený
    stav(v) = nenalezený
    ohodnocení(v) = nekonečno (v našem případě třeba 999999 nebo INTEGER.MAX či tak...)
    Předchůdci_vrcholu(v) = null      // chceme si pamatovat cestu, kudy jdeme, zde je prázdná
stav(s) = otevřený
ohodnoceni(s) = 0
Dokud existují nějaké otevřené vrcholy:
    Vezměme otevřený vrchol v, jehož ohodnocení(v) je minimální
    Pro všechny následníky w vrcholu v:
          Pokud (ohodnocení(w) > ohodnocení(v) + vzdálenost(v, w)):
            ohodnocení(w) = ohodnocení(v) + vzdálenost(v, w)
            stav(w) = otevřený
            Předchůdci_vrcholu(w).přidej(v)        // přidáme vrchol do předchůdců
        stav(v) = uzavřený   // všechny jeho sousedy jsme už prošli

Ukážka

Pokiaľ vám pseudokód nič nehovorí, ukážeme si postup na jednoduchom grafe, ako to vyzerá v praxi. Laicky povedané, chceme do jednotlivých vrcholov dostať informáciu, ako sa do nich najkratšou cestou dostaneme.

Pritom si treba uvedomiť, že ak poznáme cestu napr. Z Olomouca do Chebu, nepoznáme automaticky cestu z Chebu do Olomouca ... Na to pozor!

Krok 0, inicializácia:

Dijkstrov algoritmus – Krok 0 - Grafové algoritmy

Krok 1, prejdeme všetky mestá, ktoré sú dosiahnuteľná z vrcholu J. Prechádzame od najmenšej vzdialenosti:

Dijkstrov algoritmus – Krok 1 - Grafové algoritmy

Krok 2, teraz prejdeme jednotlivé vrcholy podľa poradia, v akom sme ich prechádzali. Vidíme, že sa nám zmenilo mesto A, našli sme kratšiu cestu:

Dijkstrov algoritmus – Krok 2 - Grafové algoritmy

Krok 3, ďalej pokračujeme intuitívne tak, ako sme pokračovali doteraz:

Dijkstrov algoritmus – Krok 3 - Grafové algoritmy

Krok 4, sme v cieli. Máme najkratšiu cestu do všetkých miest v zadanom grafe:

Dijkstrov algoritmus – Krok 4 - Grafové algoritmy

Pozor, graf nesmie mať záporný cyklus. Čiže, keď prejdeme cyklom, nesmie byť výsledná cesta záporná. Algoritmus sa totiž zacyklí, lebo zakaždým nájde kratšie a kratšie cestu ... Bohužiaľ, čo sa týka samotných záporných hrán, nie je jasné, ako sa Dijkstra zachová. Záleží na implementáciu. Kvôli týmto problémom existuje algoritmus Bellman-Ford.

Bellman-Ford

Čo keď cez ČR tiahnu piráti, ktorí sa usídlili na niektorých dopravných tepnách. Je všeobecne známe, že piráti milujú mlieko a veľmi radi nakupujú od poctivé mliekarne. Preto, keď pôjdeme cez cestu s pirátmi, bude tá cesta zárobková, pretože sa zbavíme časti zásob rovno na ceste. Máme teda v grafe naraz zápornej ohodnotenie hrany, pretože cena cesty je záporná, pretože za ňou dostaneme zaplatené. Lojzo teda s nadšením sadne k PC a prepíše Dijkstra tak, aby riešil aj pirátmi:

Vstup

Vstupom je ohodnotený graf G a počiatočné uzol s (štart).

Výstup

Výstupy je pole vzdialenosťou, udávajúce najkratšej vzdialenosti medzi uzlom s a všetkými ostatnými a polia predchodcov. Ďalším výstupom je detekcia záporného cyklu.

Opis algoritmu

Pro každý vrchol v :
        ohodnocení(v) = nekonečno
    předchůdci_vrcholu[v] = null // chceme si pamatovat cestu, kudy jdeme, zde je prázdná
ohodnoceni(s) = 0

pro každý další vrchol u
    pro každý vrchol v
        pokud ohodnocení (u) + vzdálenost(u,v) < ohodnocení(v)
            ohodnoceni(v) = ohodnocení (u) + vzdálenost(u,v)
            předchůdce_vrcholu(v).add(u)

Pro každý vrchol u
    pro každý vrchol v
        pokud ohodnocení (u) + vzdálenost(u,v) < ohodnocení(v)
                      vypis "ERROR, zaporny cyklus"

Znie to možno ako mágia, ale skúsme si rozobrať hlavnú myšlienku. Každá záporná hrana nám môže pohnúť s naším doterajším výpočtom, preto musíme iterovat toľkokrát, koľko máme vrcholov - 1 (prvý vrchol má vzdialenosť 0).

Ukážka

V našom upravenom prípade by to mohlo vyzerať nejako takto.

Krok 0, inicializácia:

Algoritmus Bellman-Ford – Krok 0 - Grafové algoritmy

Krok 1, teraz pozor - robíme niečo trochu iné ako u Dijkstra. Pre každý vrchol skúsime, či máme zlepšujúce cestu. Tu by sa mohlo zdať, že záleží na poradí. Ak skúsime zlé poradí, cesta sa o moc nezlepší. Je však dôležité, že po V (počet vrcholov) -1 opakovaniach algoritmus vždy nájde správne riešenie, nehľadiac na poradie:

Algoritmus Bellman-Ford – Krok 1 - Grafové algoritmy

Krok 2, opätovný priechod grafom:

Algoritmus Bellman-Ford – Krok 2 - Grafové algoritmy

Krok 3, to isté:

Algoritmus Bellman-Ford – Krok 3 - Grafové algoritmy

Krok 4, vzhľadom na to, že musíme urobiť V - 1 krokov, tak krok 4 vyzerá rovnako ako krok 3, len skontrolujeme, ze je všetko OK:

Algoritmus Bellman-Ford – Krok 4 - Grafové algoritmy

Tento algoritmus nám dokonca oznámi chybu, pokiaľ je v grafe záporný cyklus. Oproti Dijkstra vieme, že Bellman-Ford urobí vždy V * E krokov (pre V = počet vrcholov a E = počet hrán). Ak aj potom existuje kratšia cesta, je v grafe záporný cyklus.

Floyd-Warshallův algoritmus

Zadanie bolo úspešné a Lojzo firme vyniesol veľké peniaze. Piráti nakupovali mlieko ako šialení. Preto sa vedenie rozhodlo, že rozšíri pôsobnosť a v každom meste si založili vlastnú pobočku. Lojzo dostal ďalšiu úlohu. Firma po ňom chce, aby našiel najkratšej cesty medzi všetkými mestami navzájom. Na to sa hodí Floyd-Warshallův algoritmus. Tento algoritmus je veľmi jednoduchý, elegantný a bohužiaľ aj časovo náročný. Sú to vlastne tri for -cykly v sebe. Keď si predstavíte maticu, tak pre každú dvojicu vrcholov zisťuje, či nie je bližšie nejaká cesta cez tretiu vrchol.

Vstup

Hodnotený graf G

Výstup

matica vzdialeností

Opis algoritmu

pro každé k od 1 do n
     pro každé i od 1 do n
      pro každé j od 1 do n
        pokud G[i][j] > G[i][k] + G[k][j] potom
            G[i][j] = G[i][k] + G[k][j]

Tento algoritmus nie je možné ukázať pomocou grafov ako predchádzajúci algoritmy. Namiesto toho použijeme maticu vzdialeností = matice susednosti, kde je 0 a 1 pre označenie susednosti medzi mestami.

Nákupný algoritmov

Teraz prichádza tá najdôležitejšia časť. Aký algoritmus kedy použiť? K tomu slúži nasledujúca tabuľka:

test Dijkstra Bellman-Ford Floyd-Warshall
zložitosť O (E * log (V)) O (V * E) O (V 3)
Najkratšia cesta z A do B áno áno áno
Najkratšia cesta medzi všetkými vrcholmi nie nie áno
Berie aj záporné hrany nie * áno áno
Môže mať záporný cyklus nie nie nie
  • Nie je jasné, ako to algoritmus rieši, záleží na implementáciu.

Kedy aký algoritmus teda použiť? Ide zas o to, čo vlastne chceme robiť. Najčastejšie je Dijkstrov algoritmus, kedy chceme len z jedného uzla niekam. Ak chceme zo všetkých vrcholov do všetkých, implementujeme Floyd-Warshalla.

Malé cvičeníčko na záver - Čo sa stane, keď týmto algoritmom dáme záporné cykly? Aká je najkratšia cesta cez záporný cyklus?

Odpoveď leží v článku :)

Grafové algoritmy

V ďalšej lekcii, Tok v sieti a Dinicův algoritmus na hľadanie maximálneho toku , si ukážeme definície toku v sieti, súvisiacich pojmov a implementácia jedného z algoritmov na hľadanie maximálneho toku zvaného Dinicův algoritmus.


 

Mal si s čímkoľvek problém? Stiahni si vzorovú aplikáciu nižšie a porovnaj ju so svojím projektom, chybu tak ľahko nájdeš.

Stiahnuť

Stiahnutím nasledujúceho súboru súhlasíš s licenčnými podmienkami

Stiahnuté 36x (4.71 kB)
Aplikácia je vrátane zdrojových kódov

 

Predchádzajúci článok
Náhodný priechod grafom, priechod do hĺbky a do šírky
Všetky články v sekcii
Grafové algoritmy
Preskočiť článok
(neodporúčame)
Tok v sieti a Dinicův algoritmus na hľadanie maximálneho toku
Článok pre vás napísal Ondřej Michálek
Avatar
Užívateľské hodnotenie:
Ešte nikto nehodnotil, buď prvý!
Autor se věnuje teoretické informatice. Ve svých volných chvílích nepohrdne šálkem dobrého čaje, kaligrafickým brkem a foukací harmonice.
Aktivity