IT rekvalifikace s garancí práce. Seniorní programátoři vydělávají až 160 000 Kč/měsíc a rekvalifikace je prvním krokem. Zjisti, jak na to!
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í.

Lekce 2 - Náhodný průchod grafem, průchod do hloubky a do šířky

V minulé lekci, Úvod do teorie grafů, jsme si udělali úvod do teorie grafů.

Dnes se podíváme na 3 nejzákladnější průchody grafem, jsou to:

  • Náhodný průchod
  • Průchod do hloubky
  • Průchod do šířky

Tento článek počítá s povědomím o:

(případně se podívejte na dané články)

Příklad

Představme si, že máme např. mapu Evropy. Vrcholy grafu budou jednotlivé státy a hrana mezi dvěma vrcholy povede právě tehdy, když budou státy spolu sousedit. Takový graf bude docela řídký, ale to vůbec nevadí. Řekněme, že plánujeme rodinnou dovolenou a manžel programátor si napíše program na průchod tímto grafem. Chce se dostat např. z Čech do Itálie. My sice víme, že musíme jet přes Rakousko a jsme tam, ale jak to říci počítači?

Mapa Evropy - Grafové algoritmy

Náhodný průchod

Prvnímu průchodu, který si zmíníme, říkejme náhodný, i když nejspíše nemá ani žádný název. Jednoduše pojedeme tam, jak nám to přijde pod ruku. Tak se může stát, že rodinka pojede do Německa, Polska, Slovenska, přes Ukrajinu a Rusko se podívá zpět na pobaltské státy, pak opět do Polska a domů. Může se stát, že se omylem tato cesta zacyklí a budou jezdit stále dokola přes tyto státy a doufat, že najdou Itálii. V lepším případě to třeba vezmou dolů do Rakouska, až nakonec Itálii najdou. Jen o 10 mezizastávek navíc. Takto tedy ne... Ideálně totiž chceme "cestu", tj. posloupnost hran, které se neopakují. Nechceme jednu hranici státu použít vícekrát.

Tento průchod bych nazval splašeným býkem. Vybereme směr a prostě pojedeme. Zní to skoro jako minulá metoda, až na to, že tentokrát si pamatujeme, kde jsme byli a nezacyklíme se.

Algoritmus DFS

V tomto případě bychom mohli mít hromadu špendlíků dvou barev, třeba zelené a červené. Vždy, když do nějakého státu přijedeme, píchneme do něj zelený špendlík na znamení, že jsme tu již byli. Pokud je ten stát Itálie, máme vyhráno. Pokud ne a máme kolem sebe ještě nějaký stát, který nemá na sobě žádný špendlík, pokračujeme do tohoto státu.

Může se stát, že tímto algoritmem dojdeme třeba na Island, kde hranice Evropy končí a musíme zpět. Když již nemůžeme jít nikam, nahradíme zelený špendlík za červený a jdeme zpátky, dokud nemůžeme někam píchnout zelený špendlík, nebo dokud neprojdeme celou Evropu a nezjistíme, že Itálie neexistuje. (Třeba se přejmenovala, kdo ví...)

Ukázka průchodu

Využijeme nyní znalosti nabyté. Průchod do hloubky se implementuje pomocí zásobníku. Představme si, že si uspořádáme mapu Evropy do stromu takto:

Mapa Evropy ve stromu - Grafové algoritmy

Česká republika sousedí s Polskem, Slovenskem, Německem a Rakouskem, Polsko s Ukrajinou atd. Česká republika bude kořenový element, protože z Čech vyjíždíme. V tomto grafu si vezmeme zelené připínáčky a vybereme si Česko, Polsko, dále Ukrajinu, dále např. Moldavsko, Rumunsko, Bulharsko, Srbsko, Chorvatsko, Slovinsko, Maďarsko, Rakousko, Slovensko a konec. Zelenou již použít nemůžeme. Česká republika, Polsko, Ukrajina, Rakousko i Maďarsko mají všichni zelenou. Musíme vzít červenou a označit Slovensko za konečnou větev. Vedle Slovenska Itálie prostě není. Vracíme se do Rakouska, jedeme do Švýcarska a nakonec narazíme na Itálii.

Jak se pozná pořadí?

Pořadí států je závislé na tom, jak je vložíme do programu. Můžeme mít štěstí a jet do Itálie rovnou, nebo smůlu a... máme roundtrip po Evropě.

Alternativní příklad

Pokud je vám to stále nejasné, tak si představte, že bydlíte v paneláku v 1. patře a chcete najít cestu k sousedům, kteří bydlí o patro výše. Jenže vy nejdříve zkusíte přízemí, pak vedlejší budovu, pak vedlejší město, pak vedlejší stát... asi to není nejvhodnější. Z těchto příkladů je patrná jedna věc:

NIKDY tento algoritmus nepoužívejte, když hledáte nejkratší cestu odněkud někam.

DFS - Grafové algoritmy

Časová složitost

Musíme prostě projít každým vrcholem a každou hranou, tzn. O(|V| + |E|), kde |V| je počet vrcholů a |E| je počet hran.

Zdrojový kód

Zdrojový kód algoritmu DFS by vypadal následovně:

public void DFS(State state)
{
    bool found = false;
    Stack<State> stack = new Stack<State>();
    state.wasVisited = true;
    stack.Push(state);

    while (stack.Count > 0)
    {
        state = stack.Pop();
        if (state.Name == "Italy")
        {
            Console.WriteLine("Wohooooo");
            found = true;
            break;
        }
        foreach (State NewState in state.Neighbours)
        {
            if (!NewState.wasVisited)
            {
                NewState.wasVisited = true;
                stack.Push(NewState);
            }
        }
    }

    if (!found)
        Console.WriteLine("Italy not found");
}

Vezměme to tedy jinak. Nebudeme strom procházet do hloubky, neboli jak splašený býk, ale použijeme něco, čemu se říká algoritmus vlny, viz. článek Algoritmus šíření do šířky (Vlna). Vlna proto, protože všechny vrcholy, které jsou na jedné hladině, projdu najednou. Využijeme k tomu frontu.

Algoritmus

Vždy, když do nějakého státu přijedeme, podíváme se, jestli je to Itálie. Když ne, koukneme na manželku a oznámíme jí všechny státy, které navštívíme dále.

Ukázka průchodu

Takže začínáme v Čechách, do fronty vložíme ČR. Odebereme z fronty ČR a dáme do fronty Polsko, Rakousko, Slovensko a Německo. (Trochu jsem to pořadí přeházel, ať s tím nezabijeme celý den :) ) Česko dáme do nějakého pole již prošlých států. Koukneme se na Polsko, odebereme ho z fronty a do ní přidáme Rusko, Litvu, Bělorusko, Ukrajinu, Slovensko, (ČR už ne) Německo. Navštívíme Rakousko, odebereme ho z fronty a přidáme Itálii, Slovinsko, Maďarsko, Lichtenštejnsko, Švýcarsko. Navštívíme Slovensko a ... . Navštívíme Německo a... Nyní jsme navštívil všechny přímé potomky ČR a víme, že Česko nesousedí s Itálií. Jdeme nyní na potomky Polska, které však také selžou, tzn. Polsko nesousedí s Itálií. Jdu na potomky Rakouska a hele, Itálie... Našli jsme ji + známe nejkratší cestu. Přes Rakousko.

Pozn. Ano, je to lehce neefektivní v reálném světě, ale uvědomme si, že bychom vůbec neměli mapu a žili bychom na planetě, kde má Evropa 2000 států. Pak je toto řešení přeci jen nejefektivnější i v reálném světě. (A počítač nemusí řešit přejíždění z místa na místo, skočí si tam).

BFS - Grafové algoritmy

Časová složitost

Musíme prostě projít každým vrcholem a každou hranou, tzn. O(|V| + |E|), kde |V| je počet vrcholů a |E| je počet hran.

Pokud se vám zdá, že jsem u časové složitosti udělal Ctrl + C a Ctrl + V z minulého algoritmu, ... ano, udělal :) Tyto algoritmy totiž dělají to samé. Nakonec projdou celý graf. Liší se jen použitím na konkrétní aplikaci, viz dále.

Zdrojový kód

Algoritmus v C#:

void BFS(State  state)
{
    bool found = false;
    Queue<State> queue = new Queue<State>();
    // přidáme Českou republiku
    state.wasVisited = true;
    queue.Enqueue(state);

    while (queue.Count > 0)
    {
        state = queue.Dequeue();
        if (state.Name == "Italy")
        {
            Console.WriteLine("Wohoooooo");
            found = true;
            break;

        }

        foreach (State newState in state.Neighbours)
        {
            if (!newState.wasVisited)
            {
                newState.wasVisited = true;
                queue.Enqueue(newState);
            }
        }

    }

    if (!found)
        Console.WriteLine("Italy not found");
}

Kdy používat BFS a kdy DFS?

Kdy používat šíření do šířky a kdy do hloubky? Jako vždy ani zde není jednoznačná odpověď a programátor sám musí zvážit, jakou povahu má daná úloha (např. zda chceme nejkratší cestu).

Klucina - Grafové algoritmy

Výhody DFS

  • Nevyžaduje tak velkou paměť jako BFS. Pokud víme, že jsme levou větev již navštívili a nic tam nebylo, tak si ji již nemusíme pamatovat. Stačí si pamatovat jen kořen té větve. Pokud víme, že Itálie není vedle Slovenska a ani vedle Ukrajiny a ani vedle Polska, stačí nám si pamatovat, že není vedle Slovenska. A máme o paměť více.
  • Pokud víme, že je řešení ve velké hloubce, tak máme šanci najít řešení mnohem rychleji než BFS. Můžeme ho však najít i pomaleji, rychlost není zaručena.

Výhody BFS

  • Najde nejkratší cestu - za předpokladu, že je graf neohodnocený, tzn. vzdálenost mezi hranami je všude stejná
  • Umí hledat řešení i v nekonečných či opravdu velkých grafech
  • Je-li více řešení a některá jsou blíž, některá dál – najde nejbližší.

Obě metody jsou naprosto esenciální znalostí každého programátora. Proto na závěr dávám ještě jednou porovnání, ať nevznikne žádná nejasnost.

porovnani - Grafové algoritmy

V další lekci, Hledání nejkratší cesty v grafu, budeme hledat nejkratší cestu v orientovaném i neorientovaném grafu pomocí algoritmů Dijkstra a Floyd-Warshall.


 

Předchozí článek
Úvod do teorie grafů
Všechny články v sekci
Grafové algoritmy
Přeskočit článek
(nedoporučujeme)
Hledání nejkratší cesty v grafu
Článek pro vás napsal Ondřej Michálek
Avatar
Uživatelské hodnocení:
13 hlasů
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