Dijkstra's algorithm

This commit is contained in:
Antti H S Laaksonen 2017-01-07 20:36:06 +02:00
parent d4dd36b4f4
commit 11e33b0eba
1 changed files with 91 additions and 107 deletions

View File

@ -325,36 +325,32 @@ $O(nm)$ and it is possible to create inputs
that make the algorithm as slow as the
standard BellmanFord algorithm.
\section{Dijkstran algoritmi}
\section{Dijkstra's algorithm}
\index{Dijkstran algoritmi@Dijkstran algoritmi}
\index{Dijkstra's algorithm}
\key{Dijkstran algoritmi} etsii BellmanFordin
algoritmin tavoin lyhimmät polut
alkusolmusta kaikkiin muihin solmuihin.
Dijkstran algoritmi on tehokkaampi kuin
BellmanFordin algoritmi,
minkä ansiosta se soveltuu suurten
verkkojen käsittelyyn.
Algoritmi vaatii kuitenkin,
ettei verkossa ole negatiivisia kaaria.
\key{Dijkstra's algorithm} finds the shortest
paths from the starting node to all other nodes,
like the BellmanFord algorithm.
The benefit in Dijsktra's algorithm is that
it is more efficient and can be used for
processing large graphs.
However, the algorithm requires that there
are no negative weight edges in the graph.
Dijkstran algoritmi vastaa
BellmanFordin algoritmia siinä,
että se pitää
yllä etäisyysarvioita solmuihin
ja parantaa niitä algoritmin aikana.
Algoritmin tehokkuus perustuu
siihen, että sen riittää käydä läpi
verkon kaaret vain kerran
hyödyntäen tietoa,
ettei verkossa ole negatiivisia kaaria.
Like the BellmanFord algorithm,
Dijkstra's algorithm maintains estimated distances
for the nodes and improves them during the algorithm.
Dijkstra's algorithm is efficient because
it only processes
each edge in the graph once, using the fact
that there are no negative edges.
\subsubsection{Esimerkki}
\subsubsection{Example}
Tarkastellaan Dijkstran algoritmin toimintaa
seuraavassa verkossa, kun alkusolmuna
on solmu 1:
Let's consider how Dijkstra's algorithm
works in the following graph when the
starting node is node 1:
\begin{center}
\begin{tikzpicture}[scale=0.9]
\node[draw, circle] (1) at (1,3) {3};
@ -377,25 +373,18 @@ on solmu 1:
\path[draw,thick,-] (4) -- node[font=\small,label=below:1] {} (5);
\end{tikzpicture}
\end{center}
BellmanFordin algoritmin tavoin
alkusolmun etäisyysarvio on 0
ja kaikissa muissa solmuissa etäisyysarvio
on aluksi ääretön.
Like in the BellmanFord algorithm,
the estimated distance is 0 to the starting node
and infinite to all other nodes.
Dijkstran algoritmi
ottaa joka askeleella käsittelyyn
sellaisen solmun,
jota ei ole vielä käsitelty
ja jonka etäisyysarvio on
mahdollisimman pieni.
Alussa tällainen solmu on solmu 1,
jonka etäisyysarvio on 0.
At each step, Dijkstra's algorithm selects a node
that has not been processed yet and whose estimated distance
is as small as possible.
The first such node is node 1 with distance 0.
Kun solmu tulee käsittelyyn,
algoritmi käy läpi kaikki
siitä lähtevät kaaret ja
parantaa etäisyysarvioita
niiden avulla:
When a node is selected, the algorithm
goes through all edges that begin from the node
and improves the distances using them:
\begin{center}
\begin{tikzpicture}[scale=0.9]
\node[draw, circle] (1) at (1,3) {3};
@ -422,12 +411,10 @@ niiden avulla:
\path[draw=red,thick,->,line width=2pt] (4) -- (5);
\end{tikzpicture}
\end{center}
Solmun 1 käsittely paransi etäisyysarvioita
solmuihin 2, 4 ja 5,
joiden uudet etäisyydet ovat nyt 5, 9 ja 1.
The edges from node 1 improved distances to
nodes 2, 4 and 5 whose now distances are now 5, 9 and 1.
Seuraavaksi käsittelyyn tulee solmu 5,
jonka etäisyys on 1:
The next node to be processed is node 5 with distance 1:
\begin{center}
\begin{tikzpicture}
\node[draw, circle] (1) at (1,3) {3};
@ -452,7 +439,7 @@ jonka etäisyys on 1:
\path[draw=red,thick,->,line width=2pt] (5) -- (2);
\end{tikzpicture}
\end{center}
Tämän jälkeen vuorossa on solmu 4:
After this, the next node is node 4:
\begin{center}
\begin{tikzpicture}[scale=0.9]
\node[draw, circle] (1) at (1,3) {3};
@ -478,17 +465,14 @@ Tämän jälkeen vuorossa on solmu 4:
\end{tikzpicture}
\end{center}
Dijkstran algoritmissa on hienoutena,
että aina kun solmu tulee käsittelyyn,
sen etäisyysarvio on siitä lähtien lopullinen.
Esimerkiksi tässä vaiheessa
etäisyydet 0, 1 ja 3 ovat lopulliset
etäisyydet solmuihin 1, 5 ja 4.
A nice property in Dijkstra's algorithm is that
whenever a node is selected, its distance is final.
For example, at this point of the algorithm,
the distances 0, 1 and 3 are the final distances
to nodes 1, 5 and 4.
Algoritmi käsittelee vastaavasti
vielä kaksi viimeistä solmua,
minkä jälkeen algoritmin päätteeksi
etäisyydet ovat:
After this, the algorithm processes the two
remaining nodes, and the final distances are as follows:
\begin{center}
\begin{tikzpicture}[scale=0.9]
@ -513,13 +497,14 @@ etäisyydet ovat:
\end{tikzpicture}
\end{center}
\subsubsection{Negatiiviset kaaret}
\subsubsection{Negative edges}
Dijkstran algoritmin tehokkuus perustuu siihen,
että verkossa ei ole negatiivisia kaaria.
Jos verkossa on negatiivinen kaari,
algoritmi ei välttämättä toimi oikein.
Tarkastellaan esimerkkinä seuraavaa verkkoa:
The efficiency of Dijkstra's algorithm is
based on the fact that the graph doesn't
contain negative edges.
If there is a negative edge,
the algorithm may give incorrect results.
As an example, consider the following graph:
\begin{center}
\begin{tikzpicture}[scale=0.9]
@ -535,54 +520,53 @@ Tarkastellaan esimerkkinä seuraavaa verkkoa:
\end{tikzpicture}
\end{center}
\noindent
Lyhin polku solmusta 1 solmuun 4 on
The shortest path from node 1 to node 4 is
$1 \rightarrow 3 \rightarrow 4$,
ja sen pituus on 1.
Dijkstran algoritmi löytää
kuitenkin keveimpiä kaaria seuraten
polun $1 \rightarrow 2 \rightarrow 4$.
Algoritmi ei pysty ottamaan huomioon,
että alemmalla polulla kaaren paino $-5$
kumoaa aiemman suuren kaaren painon $6$.
and its length is 1.
However, Dijkstra's algorithm
finds the path $1 \rightarrow 2 \rightarrow 4$
by following the lightest edges.
The algorithm cannot recognize that
in the lower path, the weight $-5$
compensates the previous large weight $6$.
\subsubsection{Toteutus}
\subsubsection{Implementation}
Seuraava Dijkstran algoritmin toteutus laskee
pienimmän etäisyyden solmusta $x$ kaikkiin muihin solmuihin.
Verkko on tallennettu taulukkoon \texttt{v}
vieruslistoina, joissa on pareina kohdesolmu
ja kaaren pituus.
The following implementation of Dijkstra's algorithm
calculates the minimum distance from a node $x$
to all other nodes.
The graph is stored in an array \texttt{v}
as adjacency lists that contain target nodes
and weights for each edge.
Dijkstran algoritmin tehokas toteutus vaatii,
että verkosta pystyy löytämään
nopeasti vielä käsittelemättömän solmun,
jonka etäisyysarvio on pienin.
Sopiva tietorakenne tähän on prioriteettijono,
jossa solmut ovat järjestyksessä etäisyys\-arvioiden mukaan.
Prioriteettijonon avulla
seuraavaksi käsiteltävän solmun saa selville logaritmisessa ajassa.
An efficient implementation of Dijkstra's algorithm
requires that it is possible to quickly find the
smallest node that has not been processed.
A suitable data structure for this is a priority queue
that contains the nodes ordered by the estimated distances.
Using a priority queue, the next node to be processed
can be retrieved in logarithmic time.
Seuraavassa toteutuksessa prioriteettijono sisältää
pareja, joiden ensimmäinen kenttä on etäisyysarvio
ja toinen kenttä on solmun tunniste:
In the following implementation,
the priority queue contains pairs whose first
element is the estimated distance and second
element is the identifier of the corresponding node.
\begin{lstlisting}
priority_queue<pair<int,int>> q;
\end{lstlisting}
Pieni hankaluus on,
että Dijkstran algoritmissa täytyy saada selville
\emph{pienimmän} etäisyysarvion solmu,
kun taas C++:n prioriteettijono antaa oletuksena
\emph{suurimman} alkion.
Helppo ratkaisu on tallentaa etäisyysarviot
\emph{negatiivisina}, jolloin C++:n prioriteettijonoa
voi käyttää suoraan.
A small difficulty is that in Dijkstra's algorithm,
we should find the node with \emph{minimum} distance,
while the C++ priority queue finds the \emph{maximum}
element as default.
An easy solution is to use \emph{negative} distances,
so we can directly use the C++ priority queue.
Koodi merkitsee taulukkoon \texttt{z},
onko solmu käsitelty,
ja pitää yllä etäisyysarvioita taulukossa \texttt{e}.
Alussa alkusolmun etäisyysarvio on 0
ja jokaisen muun solmun etäisyysarviona
on ääretöntä vastaava $10^9$.
The code keeps track of processed nodes
in array \texttt{z},
and maintains estimated distances in array \texttt{e}.
Initially, the distance to the starting node is 0,
and the distance to all other nodes is $10^9$
that corresponds to infinity.
\begin{lstlisting}
for (int i = 1; i <= n; i++) e[i] = 1e9;
@ -601,10 +585,10 @@ while (!q.empty()) {
}
\end{lstlisting}
Yllä olevan toteutuksen aikavaativuus on $O(n+m \log m)$,
koska algoritmi käy läpi kaikki verkon solmut
ja lisää jokaista kaarta kohden korkeintaan
yhden etäisyysarvion prioriteettijonoon.
The time complexity of the above implementation is
$O(n+m \log m)$ because the algorithm goes through
all nodes in the graph, and adds for each edge
at most one estimated distance to the priority queue.
\section{FloydWarshallin algoritmi}