DFS and BFS

This commit is contained in:
Antti H S Laaksonen 2017-01-07 18:30:14 +02:00
parent 38c6cda3ed
commit a0abff3f0a
1 changed files with 118 additions and 130 deletions

View File

@ -1,38 +1,38 @@
\chapter{Graph search} \chapter{Graph search}
Tässä luvussa tutustumme This chapter introduces two fundamental
syvyyshakuun ja leveyshakuun, jotka graph algorithms:
ovat keskeisiä menetelmiä verkon läpikäyntiin. depth-first search and breadth-first search.
Molemmat algoritmit lähtevät liikkeelle Both algorithms are given a starting
tietystä alkusolmusta ja node in the graph,
käyvät läpi kaikki solmut, and they visit all nodes that can be reached
joihin alkusolmusta pääsee. from the starting node.
Algoritmien erona on, The difference in the algorithms is the order
missä järjestyksessä ne kulkevat verkossa. in which they visit the nodes.
\section{Syvyyshaku} \section{Depth-first search}
\index{syvyyshaku@syvyyshaku} \index{depth-first search}
\key{Syvyyshaku} \key{Depth-first search} (DFS)
on suoraviivainen menetelmä verkon läpikäyntiin. is a straightforward graph search technique.
Algoritmi lähtee liikkeelle tietystä The algorithm begins at a starting node,
verkon solmusta ja etenee siitä and proceeds to all other nodes that are
kaikkiin solmuihin, jotka ovat reachable from the starting node using
saavutettavissa kaaria kulkemalla. the edges in the graph.
Syvyyshaku etenee verkossa syvyyssuuntaisesti Depth-first search always follows a single
eli kulkee eteenpäin verkossa niin kauan path in the graph as long as it finds
kuin vastaan tulee uusia solmuja. new nodes.
Tämän jälkeen haku perääntyy kokeilemaan After this, it returns back to previous
muita suuntia. nodes and begins to explore other parts of the graph.
Algoritmi pitää kirjaa vierailemistaan solmuista, The algorithm keeps track of visited nodes,
jotta se käsittelee kunkin solmun vain kerran. so that it processes each node only once.
\subsubsection*{Esimerkki} \subsubsection*{Example}
Tarkastellaan syvyyshaun toimintaa Let's consider how depth-first search processes
seuraavassa verkossa: the following graph:
\begin{center} \begin{center}
\begin{tikzpicture} \begin{tikzpicture}
\node[draw, circle] (1) at (1,5) {$1$}; \node[draw, circle] (1) at (1,5) {$1$};
@ -48,13 +48,11 @@ seuraavassa verkossa:
\path[draw,thick,-] (2) -- (5); \path[draw,thick,-] (2) -- (5);
\end{tikzpicture} \end{tikzpicture}
\end{center} \end{center}
Syvyyshaku voi lähteä liikkeelle The algorithm can begin at any node in the graph,
mistä tahansa solmusta, but we will now assume that it begins
mutta oletetaan nyt, at node 1.
että haku lähtee liikkeelle solmusta 1.
Solmun 1 naapurit ovat solmut 2 ja 4, The search first proceeds to node 2:
joista haku etenee ensin solmuun 2:
\begin{center} \begin{center}
\begin{tikzpicture} \begin{tikzpicture}
\node[draw, circle,fill=lightgray] (1) at (1,5) {$1$}; \node[draw, circle,fill=lightgray] (1) at (1,5) {$1$};
@ -72,8 +70,7 @@ joista haku etenee ensin solmuun 2:
\path[draw=red,thick,->,line width=2pt] (1) -- (2); \path[draw=red,thick,->,line width=2pt] (1) -- (2);
\end{tikzpicture} \end{tikzpicture}
\end{center} \end{center}
Tämän jälkeen haku etenee vastaavasti After this, nodes 3 and 5 will be visited:
solmuihin 3 ja 5:
\begin{center} \begin{center}
\begin{tikzpicture} \begin{tikzpicture}
\node[draw, circle,fill=lightgray] (1) at (1,5) {$1$}; \node[draw, circle,fill=lightgray] (1) at (1,5) {$1$};
@ -93,13 +90,12 @@ solmuihin 3 ja 5:
\path[draw=red,thick,->,line width=2pt] (3) -- (5); \path[draw=red,thick,->,line width=2pt] (3) -- (5);
\end{tikzpicture} \end{tikzpicture}
\end{center} \end{center}
Solmun 5 naapurit ovat 2 ja 3, The neighbors of node 5 are 2 and 3,
mutta haku on käynyt jo molemmissa, but the search has already visited both of them,
joten on aika peruuttaa taaksepäin. so it's time to return back.
Myös solmujen 3 ja 2 naapurit on käyty, Also the neighbors of nodes 3 and 2
joten haku peruuttaa solmuun 1 asti. have been visited, so we'll next proceed
Siitä lähtee kaari, josta pääsee from node 1 to node 4:
solmuun 4:
\begin{center} \begin{center}
\begin{tikzpicture} \begin{tikzpicture}
\node[draw, circle,fill=lightgray] (1) at (1,5) {$1$}; \node[draw, circle,fill=lightgray] (1) at (1,5) {$1$};
@ -117,74 +113,70 @@ solmuun 4:
\path[draw=red,thick,->,line width=2pt] (1) -- (4); \path[draw=red,thick,->,line width=2pt] (1) -- (4);
\end{tikzpicture} \end{tikzpicture}
\end{center} \end{center}
Tämän jälkeen haku päättyy, After this, the search terminates because it has visited
koska se on käynyt kaikissa solmuissa. all nodes.
Syvyyshaun aikavaativuus on $O(n+m)$, The time complexity of depth-first search is $O(n+m)$
missä $n$ on solmujen määrä ja $m$ on kaarten määrä, where $n$ is the number of nodes and $m$ is the
koska haku käsittelee kerran jokaisen solmun ja kaaren. number of edges,
because the algorithm processes each node and edge once.
\subsubsection*{Toteutus} \subsubsection*{Implementation}
Syvyyshaku on yleensä mukavinta toteuttaa Depth-first search can be conveniently
rekursiolla. implemented using recursion.
Seuraava funktio \texttt{haku} The following function \texttt{dfs} begins
suorittaa syvyyshaun sille parametrina a depth-first search at a given node.
annetusta solmusta lähtien. The function assumes that the graph is
Funktio olettaa, että stored as adjacency lists in array
verkko on tallennettu vieruslistoina
taulukkoon
\begin{lstlisting} \begin{lstlisting}
vector<int> v[N]; vector<int> v[N];
\end{lstlisting} \end{lstlisting}
ja pitää lisäksi yllä taulukkoa and also maintains an array
\begin{lstlisting} \begin{lstlisting}
int z[N]; int z[N];
\end{lstlisting} \end{lstlisting}
joka kertoo, missä solmuissa haku on käynyt. that keeps track of the visited nodes.
Alussa taulukon jokainen arvo on 0, Initially, each array value is 0,
ja kun haku saapuu solmuun $s$, and when the search arrives at node $s$,
kohtaan \texttt{z}[$s$] merkitään 1. the value of \texttt{z}[$s$] becomes 1.
Funktion toteutus on seuraavanlainen: The function can be implemented as follows:
\begin{lstlisting} \begin{lstlisting}
void haku(int s) { void dfs(int s) {
if (z[s]) return; if (z[s]) return;
z[s] = 1; z[s] = 1;
// solmun s käsittely tähän // process node s
for (auto u: v[s]) { for (auto u: v[s]) {
haku(u); dfs(u);
} }
} }
\end{lstlisting} \end{lstlisting}
\section{Leveyshaku} \section{Breadth-first search}
\index{leveyshaku@leveyshaku} \index{breadth-first search}
\key{Leveyshaku} \key{Breadth-first search} (BFS) visits the nodes
käy solmut läpi järjestyksessä sen mukaan, in increasing order of their distance
kuinka kaukana ne ovat alkusolmusta. from the starting node.
Niinpä leveyshaun avulla pystyy laskemaan Thus, we can calculate the distance
etäisyyden alkusolmusta kaikkiin from the starting node to all other
muihin solmuihin. nodes using breadth-first search.
Leveyshaku on kuitenkin vaikeampi However, breadth-first search is more difficult
toteuttaa kuin syvyyshaku. to implement than depth-first search.
Leveyshakua voi ajatella niin, Breadth-first search goes through the nodes
että se käy solmuja läpi kerros kerrallaan. one level after another.
Ensin haku käy läpi solmut, First the search explores the nodes whose
joihin pääsee yhdellä kaarella distance from the starting node is 1,
alkusolmusta. then the nodes whose distance is 2, and so on.
Tämän jälkeen vuorossa ovat This process continues until all nodes
solmut, joihin pääsee kahdella have been visited.
kaarella alkusolmusta, jne.
Sama jatkuu, kunnes uusia käsiteltäviä
solmuja ei enää ole.
\subsubsection*{Esimerkki} \subsubsection*{Example}
Tarkastellaan leveyshaun toimintaa Let's consider how the algorithm processes
seuraavassa verkossa: the following graph:
\begin{center} \begin{center}
\begin{tikzpicture} \begin{tikzpicture}
@ -204,11 +196,9 @@ seuraavassa verkossa:
\path[draw,thick,-] (5) -- (6); \path[draw,thick,-] (5) -- (6);
\end{tikzpicture} \end{tikzpicture}
\end{center} \end{center}
Oletetaan jälleen, Assume again that the search begins at node 1.
että haku alkaa solmusta 1. First, we process all nodes that can be reached
Haku etenee ensin kaikkiin solmuihin, from node 1 using a single edge:
joihin pääsee alkusolmusta:
\\
\begin{center} \begin{center}
\begin{tikzpicture} \begin{tikzpicture}
\node[draw, circle,fill=lightgray] (1) at (1,5) {$1$}; \node[draw, circle,fill=lightgray] (1) at (1,5) {$1$};
@ -234,8 +224,7 @@ joihin pääsee alkusolmusta:
\path[draw=red,thick,->,line width=2pt] (1) -- (4); \path[draw=red,thick,->,line width=2pt] (1) -- (4);
\end{tikzpicture} \end{tikzpicture}
\end{center} \end{center}
Seuraavaksi haku etenee solmuihin 3 ja 5: After this, we procees to nodes 3 and 5:
\\
\begin{center} \begin{center}
\begin{tikzpicture} \begin{tikzpicture}
\node[draw, circle,fill=lightgray] (1) at (1,5) {$1$}; \node[draw, circle,fill=lightgray] (1) at (1,5) {$1$};
@ -261,8 +250,7 @@ Seuraavaksi haku etenee solmuihin 3 ja 5:
\path[draw=red,thick,->,line width=2pt] (2) -- (5); \path[draw=red,thick,->,line width=2pt] (2) -- (5);
\end{tikzpicture} \end{tikzpicture}
\end{center} \end{center}
Viimeisenä haku etenee solmuun 6: Finally, we visit node 6:
\\
\begin{center} \begin{center}
\begin{tikzpicture} \begin{tikzpicture}
\node[draw, circle,fill=lightgray] (1) at (1,5) {$1$}; \node[draw, circle,fill=lightgray] (1) at (1,5) {$1$};
@ -288,14 +276,13 @@ Viimeisenä haku etenee solmuun 6:
\path[draw=red,thick,->,line width=2pt] (5) -- (6); \path[draw=red,thick,->,line width=2pt] (5) -- (6);
\end{tikzpicture} \end{tikzpicture}
\end{center} \end{center}
Leveyshaun tuloksena selviää etäisyys Now we have calculated the distances
kuhunkin verkon solmuun alkusolmusta. from the starting node to all nodes in the graph.
Etäisyys on sama kuin kerros, The distances are as follows:
jossa solmu käsiteltiin haun aikana:
\begin{tabular}{ll} \begin{tabular}{ll}
\\ \\
solmu & etäisyys \\ node & distance \\
\hline \hline
1 & 0 \\ 1 & 0 \\
2 & 1 \\ 2 & 1 \\
@ -306,49 +293,50 @@ solmu & etäisyys \\
\\ \\
\end{tabular} \end{tabular}
Leveyshaun aikavaativuus on syvyyshaun tavoin $O(n+m)$, Like in depth-first search,
missä $n$ on solmujen määrä ja $m$ on kaarten määrä. the time complexity of breadth-first search
is $O(n+m)$ where $n$ is the number of nodes
and $m$ is the number of edges.
\subsubsection*{Toteutus} \subsubsection*{Implementation}
Leveyshaku on syvyyshakua hankalampi toteuttaa, Breadth-first search is more difficult
koska haku käy läpi solmuja verkon eri to implement than depth-first search
puolilta niiden etäisyyden mukaan. because the algorithm visits nodes
Tyypillinen toteutus on pitää yllä jonoa in different parts in the graph.
käsiteltävistä solmuista. A typical implementation is to maintain
Joka askeleella otetaan käsittelyyn seuraava a queue of nodes to be processed.
solmu jonosta ja uudet solmut lisätään At each step, the next node in the queue
jonon perälle. will be processed.
Seuraava koodi toteuttaa leveyshaun The following code begins a breadth-first
solmusta $x$ lähtien. search at node $x$.
Koodi olettaa, että verkko on tallennettu The code assumes that the graph is stored
vieruslistoina, ja pitää yllä jonoa as adjacency lists and maintains a queue
\begin{lstlisting} \begin{lstlisting}
queue<int> q; queue<int> q;
\end{lstlisting} \end{lstlisting}
joka sisältää solmut käsittelyjärjestyksessä. that contains the nodes in increasing order
Koodi lisää aina uudet vastaan tulevat solmut of their distance.
jonon perään ja ottaa seuraavaksi käsiteltävän New nodes are always added to the end
solmun jonon alusta, of the queue, and the node at the beginning
minkä ansiosta solmut käsitellään of the queue is the next node to be processed.
kerroksittain alkusolmusta lähtien.
Lisäksi koodi käyttää taulukoita In addition, the code uses arrays
\begin{lstlisting} \begin{lstlisting}
int z[N], e[N]; int z[N], e[N];
\end{lstlisting} \end{lstlisting}
niin, että taulukko \texttt{z} sisältää tiedon, so that array \texttt{z} indicates
missä solmuissa haku on käynyt, which nodes the search already has visited
ja taulukkoon \texttt{e} lasketaan lyhin and array \texttt{e} will contain the
etäisyys alkusolmusta kaikkiin verkon solmuihin. minimum distance to all nodes in the graph.
Toteutuksesta tulee seuraavanlainen: The search can be implemented as follows:
\begin{lstlisting} \begin{lstlisting}
z[s] = 1; e[x] = 0; z[s] = 1; e[x] = 0;
q.push(x); q.push(x);
while (!q.empty()) { while (!q.empty()) {
int s = q.front(); q.pop(); int s = q.front(); q.pop();
// solmun s käsittely tähän // process node s
for (auto u : v[s]) { for (auto u : v[s]) {
if (z[u]) continue; if (z[u]) continue;
z[u] = 1; e[u] = e[s]+1; z[u] = 1; e[u] = e[s]+1;