Skip to content

Commit 7a51814

Browse files
committed
Added in depth explanation of dijkstras time complexity
1 parent 36a5126 commit 7a51814

File tree

2 files changed

+19
-5
lines changed

2 files changed

+19
-5
lines changed

docs/graphs.md

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -845,12 +845,21 @@ Scenarios:
845845

846846
Pronounced *Dikestra*.
847847
Finds the shortest **greedy** path via a **[priority queue](computer-science.md#priority-queue)**.
848-
- Not [Dynamic Programming](algorithms.md#dynamic-programming)
848+
849+
- Although dijkstras does store information as it is building a solution, it is not [Dynamic Programming](algorithms.md#dynamic-programming) because it does not explicitly or fully solve any discrete sub-problems of the original input. This is debated, but for the sake of VCE just remember it as [greedy](algorithms.md#greedy)
849850

850851
<img src="images/Dijkstra_Animation.gif" alt="Dijkstra_Animation">
851852

852-
Worst case performance if using a [priority queue](computer-science.md#priority-queue):
853-
$$\displaystyle O(|E|+|V|\log |V|)$$
853+
Worst case performance if using a [priority queue](computer-science.md#priority-queue) (Fibonacci heap version):
854+
$$
855+
\displaystyle O(|E|+|V|\log |V|) \newline
856+
\text{or equivalently,} \newline
857+
\displaystyle O(|V|\log(|V|+|E|))
858+
$$
859+
860+
This is because you're initialising each node into `unexplored`. Which is both $O(|V|)$ and setting the size of `unexplored` to $|V|$ which means the `while` loop will run at most $O(|V|)$ times. And the pop operation for a vertex with with minimum distance from the priority queue (Fibonacci heap) is $O(\log |V|)$. Which means you are running $O(|V| \log |V|)$ operations there.
861+
862+
Then considering the inner loop of `for each neighbour N of V do`, you can't immediately think of it as having that automatic coeficcient of $|V|$ because it's in that while loop. Instead, think of it on a higher level and what it's doing overall. That loop is the mechanism for relaxing edges if you find a shorter path. And when you run the algorithm you will see that relaxation is done **once per edge**[^dijkstra-edge]. So that contributes the extra term of $O(|E|)$ for a final time complexity of $O(|V|\log(|V|+|E|)) \equiv O(|E|+|V|\log |V|)$
854863

855864
Worst case performance when using an array:
856865
$$\displaystyle O(|V^2|)$$
@@ -874,11 +883,11 @@ End do
874883
dist[source] :=0 // Distance from source to source
875884

876885
while unexplored is not empty do
877-
V := vertex in unexplored with minimum dist[V] // Greedy Priority Queue
886+
V := vertex in unexplored with minimum dist[V] // Greedy Priority Queue (1)
878887
remove V from unexplored
879888
for each neighbour N of V do
880889
thisDist := dist[V] + weight(V, N)
881-
if (thisDist < dist[N] ) then
890+
if (thisDist < dist[N]) then
882891
// A shorter path to N has been found
883892
dist[N] := thisDist // Update shortest path
884893
pred[N] := V // Update path predecessor
@@ -887,10 +896,14 @@ while unexplored is not empty do
887896
End do // shortest path information in dist[], pred[]
888897
```
889898

899+
1. With Fibonacci heap, this discrete operation is `O(log |V|)`
900+
890901
**Limitations**
891902

892903
- Can't use negative weights
893904

905+
[^dijkstra-edge]: On an undirected graph, you can argue that it does some edges twice which would make it $2|E|$. But in that case you use the convention that an undirected graph has one edge in each direction, so $2|E|$ is now $|E|$. Either way, it's still asymptotically $|E|$
906+
894907
#### Expanding a node
895908

896909
A node is **expanded** when if has been visited:

mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ theme:
99
logo: assets/logo.png
1010
features:
1111
- content.action.edit
12+
- content.code.annotate
1213
palette:
1314
- scheme: default
1415
primary: "blue"

0 commit comments

Comments
 (0)