Here's the Dijkstra algorithm in TeX.
It uses the PGFFor (the .list handler) and PGFMath (the \pgfmathloop) for looping:
- The
.list handler gets used to store the weights of each edge and to do all the steps.
- The
\pgfmathloop macro (undocumented) is very similar to a LaTeX \loop but provides an additional counter \pgfmathcounter (which is not a TeX count nor a LaTeX counter).
Both could be transformed into the other with a bit more work.
The PGFMath packages also loads a small undocumented utility PGFInt which provides \pgfinteval which is almost a clone of xfp's and L3's \inteval, I'm only using it in place of \numexpr<int 1>+<int 2>\relax as an easier way to add two integers.
The PGFKeys package is used for the main interface,
- the user-interface keys
n, steps and start where
n is the number of nodes (1, …, n),
start is the number of the node where the algorithm should start and
steps is the maximum number of steps the algorithm should use;
- the key
weight matrix which stores a matrix of (possible directed) weights (use i for infinity);
- the internal keys
__init and __step which do the hard work;
- a few tests
is calculated = {<i>}{<true>}{<false>} which test whether a distance to node i at least has been calculated (doesn't mean it's the meanimum, just means that a neighbour has been visited – this is not part of the algorithm but I like it for visualization reasons),
is visited = {<i>}{<true>}{<false>} which tests whether node i has been visited,
is current = {<i>}{<true>}{<false>} which tests whether i is the current node, i.e. the source node for current step,
is edge = {<i>-<j>}{<true>}{<false>} which tests wheter edge i to j has actually a weight not equal to infinity assigned and
is pred = {<i>}{<j>}{<true>}{<false>} which tests whether j is a predecessor of i; and
- actually storing all the results (it's just a fancy interface for
\csname and \endcsname).
All the drawing is done by TikZ, meaning:
- placement of nodes
- edges and weights between nodes
- style of nodes which uses the aforementioned tests.
Technically, the dijkstra environment is only used to group the calculations (since the code overwrites itself to not do needless iterations when the algorithm finishes but there's still more steps to do according to the steps key).
Instead of a TikZ diagram, you could just put a tabular in it.
For each step, a snapshot of all these calculated distances and predecessors are stored and can be accessed later …
which is done for the tabular below the diagram.
(This doesn't need to be inside the TikZ diagram, I'm only putting it inside there for this answer.)
For the table, another bunch of commands are used that test something
specific for each node where the optional first argument is the step to be tested. (If no optional argument is used, the “global” value of the algorithm is used.)
Some of these tests end on TFX (instead of the normal boolean TF). These check whether the required value actually exist, if they don't the last argument will be used. (For example, in step 2 all the values of step 3 and above don't exist and thus no content should be written in the cell.)
In regards to the table creation, this might not be the optimal way to approach this.
I purposeful am using eight instead of the required 5 or 6 steps to show that the code won't break if more steps than necessary are asked for.
And yes, the initializing phase is step 0 where only the starting node get marked as visited and with distance 0.
Code
\documentclass[tikz]{standalone}
\usepackage{pgfkeys, pgfmath, pgffor}% for dij algo
\usepackage{tikz} % for drawing
\usepackage{booktabs, array} % for nice tabulars
%% User Interface
\newcommand*\dijset{\pgfqkeys{/dij}}
\newenvironment*{dijkstra}[1][]{%
\dijset{#1, __init}%
\ifnum\pgfkeysvalueof{/dij/steps}>0
\dijset{__step/.list/.expanded={1,...,\pgfkeysvalueof{/dij/steps}}}%
\fi}{}
\newcommand*\DijNodeDist[1]{% return the distance to #1 or infty
\ifnum\pgfkeysvalueof{/dij/node #1/dist}<2147483647\relax
\pgfkeysvalueof{/dij/node #1/dist}\else\infty\fi}
\dijset{
weight matrix/.style={
/utils/exec=\def\listrow{0},
/utils/rows/.style={
/utils/exec=\def\listcol{0}%
\edef\listrow{\the\numexpr\listrow+1\relax},
/utils/cols/.ecode={%
\edef\noexpand\listcol{\noexpand\the\numexpr\noexpand\listcol+1\relax}%
\noexpand\if i####1%
\noexpand\pgfkeyssetevalue{/dij/weight/\listrow-\noexpand\listcol}{2147483647}%
\noexpand\else
\noexpand\pgfkeyssetevalue{/dij/weight/\listrow-\noexpand\listcol}{####1}%
\noexpand\fi
},
/utils/cols/.list={##1},
},
/utils/rows/.list={#1}},
start/.initial = 1,
n/.initial = 10,
steps/.initial = 10}
\makeatletter
%% Algorithm
\newif\ifdij@allvisited
\newcommand*{\pgfkeysletlet}[2]{\pgfkeysgetvalue{#2}\pgfkeys@temp\pgfkeyslet{#1}\pgfkeys@temp}
\dijset{
__init/.code={%
\def\dij@nodecurrent{0}% for step = 0
\pgfmathloop
\pgfkeyssetvalue{/dij/node \pgfmathcounter/dist}{2147483647}%
\pgfkeyssetvalue{/dij/node \pgfmathcounter/visited}{0}%
\pgfkeyssetvalue{/dij/node \pgfmathcounter/pred}{0}%
\pgfkeyssetvalue{/dij/node \pgfmathcounter/step 0/dist}{2147483647}%
\pgfkeyssetvalue{/dij/node \pgfmathcounter/step 0/visited}{0}%
\pgfkeyssetvalue{/dij/node \pgfmathcounter/step 0/pred}{0}%
\ifnum\pgfmathcounter<\pgfkeysvalueof{/dij/n}%
\repeatpgfmathloop
\pgfkeyssetvalue{/dij/node \pgfkeysvalueof{/dij/start}/dist}{0}%
\pgfkeyssetvalue{/dij/node \pgfkeysvalueof{/dij/start}/step 0/dist}{0}%
},
__step/.code={%
%% Are all visited?
%% And if not: what's the node with the min distance?
\dij@allvisitedtrue
\def\dij@nodecurrent{0}%
\def\dij@nodedist{2147483647}%
\pgfmathloop
\ifnum\pgfkeysvalueof{/dij/node \pgfmathcounter/visited}=0\relax
\dij@allvisitedfalse
\pgfkeysgetvalue{/dij/node \pgfmathcounter/dist}\dij@temp
\ifnum\dij@temp<\dij@nodedist\relax
\let\dij@nodecurrent\pgfmathcounter
\let\dij@nodedist\dij@temp
\fi
\fi
\ifnum\pgfmathcounter<\pgfkeysvalueof{/dij/n}%
\repeatpgfmathloop
%% Try to visit all neighbours of min node.
\ifdij@allvisited\dijset{__step/.code=}% don't repeat me
\else
\pgfkeyssetvalue{/dij/node \dij@nodecurrent/visited}{1}%
\pgfkeyssetvalue{/dij/node \dij@nodecurrent/step #1/visited}{1}%
\pgfkeyssetvalue{/dij/node \dij@nodecurrent/step #1}{}% mark current node
\pgfmathloop
\ifnum\pgfkeysvalueof{/dij/node \pgfmathcounter/visited}=0\relax % not yet visited
\unless\ifnum\pgfkeysvalueof{/dij/weight/\dij@nodecurrent-\pgfmathcounter}=2147483647\relax
\edef\dij@temp{\pgfinteval{\dij@nodedist+\pgfkeysvalueof{/dij/weight/\dij@nodecurrent-\pgfmathcounter}}}%
\ifnum\dij@temp<\pgfkeysvalueof{/dij/node \pgfmathcounter/dist}\relax
\pgfkeyslet{/dij/node \pgfmathcounter/dist}\dij@temp
\pgfkeyslet{/dij/node \pgfmathcounter/pred}\dij@nodecurrent
\fi
\fi
\fi
\pgfkeysletlet{/dij/node \pgfmathcounter/step #1/dist}{/dij/node \pgfmathcounter/dist}%
\pgfkeysletlet{/dij/node \pgfmathcounter/step #1/pred}{/dij/node \pgfmathcounter/pred}%
\pgfkeysletlet{/dij/node \pgfmathcounter/step #1/visited}{/dij/node \pgfmathcounter/visited}%
\ifnum\pgfmathcounter<\pgfkeysvalueof{/dij/n}%
\repeatpgfmathloop
\fi
}
}
%% Test Keys
\dijset{
@create test 1/.style 2 args={
#1/.code n args={3}{#2\expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi
{\pgfkeysalso{##2}}{\pgfkeysalso{##3}}}},
@create test 2/.style 2 args={
#1/.code n args={4}{#2\expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi
{\pgfkeysalso{##3}}{\pgfkeysalso{##4}}}},
@create test 1={is current}{\ifnum\dij@nodecurrent=#1\relax}}
\makeatother
\dijset{
@create test 1={is visited}{\ifnum\pgfkeysvalueof{/dij/node #1/visited}=1\relax},
@create test 1={is edge}{\ifnum\pgfkeysvalueof{/dij/weight/#1}<2147483647\relax},
@create test 2={is pred}{\ifnum\pgfkeysvalueof{/dij/node #1/pred}=#2\relax},
@create test 1={is calculated}{\ifnum\pgfkeysvalueof{/dij/node #1/dist}<2147483647\relax}
}
%% Table
\dijset{table/.cd,
.style={/dij/table/content/.initial=, /dij/table/row/.list={#1}},
row/.style={%
/dij/table/content/.append={#1},
/dij/table/col/.style={%
/dij/table/content/.append={%
\DijIfNumTF{0=#1}{%
& / & $\DijIsNodeStartTF{##1}{0}{\infty}$
}{%
\DijIsNodeStartTF{##1}{&&}{%
\DijIsNodeCalculatedTFX[#1]{##1}{%
\DijIsNodeVisitedTFX[#1]{##1}{%
\DijIsNodeCurrentTF[#1]{##1}{%
& $\pgfkeysvalueof{/dij/table/node function}
{\pgfkeysvalueof{/dij/node ##1/step #1/pred}}$
& $\pgfkeysvalueof{/dij/node ##1/step #1/dist}$
}{&&}%
}{%
& $\pgfkeysvalueof{/dij/table/node function}
{\pgfkeysvalueof{/dij/node ##1/step #1/pred}}$
& $\pgfkeysvalueof{/dij/node ##1/step #1/dist}$
}{&&}%
}{& / & $\infty$}{&&}%
}%
}%
}%
},
/dij/table/col/.list/.expanded={1,...,\pgfkeysvalueof{/dij/n}},
/dij/table/content/.append=\\}}
%% Tests
\makeatletter
\def\dij@firstofthree#1#2#3{#1}
\def\dij@secondofthree#1#2#3{#2}
\def\dij@thirdofthree#1#2#3{#3}
\dijset{table/node function/.initial=\@Alph}
\def\dij@teststep#1{\ifnum#1<0 \else step #1/\fi}
\newcommand*\dij@relaxTF[3]{%
\pgfkeysgetvalue{/dij/node #2/\dij@teststep{#1}#3}\dij@temp
\ifx\dij@temp\relax\expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi}
\newcommand*\DijIsNodeVisitedTFX[2][-1]{%
\dij@relaxTF{#1}{#2}{visited}{\dij@thirdofthree}{%
\ifnum\dij@temp=1\relax
\expandafter\dij@firstofthree\else\expandafter\dij@secondofthree\fi}}
\newcommand*\DijIsNodeCalculatedTFX[2][-1]{%
\dij@relaxTF{#1}{#2}{dist}{\dij@thirdofthree}{%
\ifnum\dij@temp<2147483647\relax
\expandafter\dij@firstofthree\else\expandafter\dij@secondofthree\fi}}
\newcommand*\DijIsNodeStartTF[1]{\ifnum\pgfkeysvalueof{/dij/start}=#1\relax
\expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi}
\newcommand*\DijIfNumTF[1]{%
\ifnum#1\relax\expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi}
\newcommand*\DijIsNodeCurrentTF[2][-1]{%
\dij@relaxTF{#1}{#2}{}{\@secondoftwo}{\@firstoftwo}}
\makeatother
\begin{document}
\dijset{
n = 6, start = 1,
weight matrix={
{0,6,5,i,i,i},
{6,0,7,1,i,i},
{5,7,0,8,4,i},
{i,1,8,0,9,2},
{i,i,4,9,0,3},
{i,i,i,2,3,0}}}
\foreach \STEP in {0,...,8}{%
\begin{dijkstra}[steps = \STEP]
\begin{tikzpicture}[
thick, scale=2,
node edge 1-2/.style=swap,
node edge 4-6/.style=swap,
]
\foreach[count=\i]\p in {(0,0), (1,1), (1,-1), (3,1), (3,-1), (4,0)}
\node[draw=blue, circle, text width=width("$\infty$"), align=center,
/dij/is calculated={\i}{fill=yellow!25}{},
/dij/is visited={\i}{fill=green}{},
/dij/is current={\i}{fill=yellow}{},
] (\i) at \p
{\begin{tabular}{@{}c@{}} \pgfkeysvalueof{/dij/table/node function}{\i} \\
$\DijNodeDist{\i}$\end{tabular}}
\ifnum\i>1 % there's no possible neighbour for the first node
foreach[expand list] \j in {1,...,\pgfinteval{\i-1}}{
[/dij/is edge={\j-\i}{insert path={
(\i) edge[/dij/is pred={\i}{\j}{}{/dij/is pred={\j}{\i}{}{gray}}]
node[auto, node edge \j-\i/.try]{\pgfkeysvalueof{/dij/weight/\j-\i}}
(\j)}}{}]
}
\fi;
\node[anchor = north west] at (current bounding box.north west) {Step: \STEP};
\small
\node[below=+.5em, inner sep=+0pt] at (current bounding box.south) {%
\pgfmathsetlengthmacro\dijcolleft{width("$M$")}%
\pgfmathsetlengthmacro\dijcolright{width("$00$")}%
\begin{tabular}{r *6{>{\centering \arraybackslash}p{\dijcolleft}
>{\raggedleft\arraybackslash}p{\dijcolright}}}
\toprule
& \multicolumn{2}{c}{$A$} & \multicolumn{2}{c}{$B$} & \multicolumn{2}{c}{$C$}
& \multicolumn{2}{c}{$D$} & \multicolumn{2}{c}{$E$} & \multicolumn{2}{c}{$F$} \\
\cmidrule(lr){2-3}\cmidrule(lr){4-5} \cmidrule(lr){6-7}
\cmidrule(lr){8-9}\cmidrule(lr){10-11}\cmidrule(lr){12-13}
\dijset{table={0,...,8}}%
\pgfkeysvalueof{/dij/table/content}%
\bottomrule
\end{tabular}
};
\end{tikzpicture}
\end{dijkstra}}
\end{document}
Output
