Card 10/ 11

ExerciseDifficulty: Advanced4 min

Practice: Planning the Cheapest Ferry Crossings

Six harbours, crossings with fares, and one crossing that pays a rebate: trace the cheapest fares, then choose again.

Finding the cheapest fares through a network of crossings is a shortest-path problem with the fares as weights. This exercise traces Dijkstra's algorithm on a ferry network, then adds a crossing that pays the traveller and asks which algorithm the network now needs.

A ferry company runs crossings between six harbours: Ardle, Brack, Colm, Dunn, Esk and Fell. Each crossing goes one way and has a fare in pounds. A traveller starts at Ardle.

The one-way crossings and their fares in pounds
FromToFare
ArdleBrack3
ArdleColm6
BrackDunn4
ColmDunn2
ColmEsk7
DunnEsk2
DunnFell6
EskFell3
  1. Run Dijkstra's algorithm from Ardle. For each harbour give the cheapest fare, the round in which it is settled, and the harbour just before it on the cheapest route.
  2. To fill its emptiest boat, the company adds a crossing from Colm to Brack that pays the traveller £4, a fare of −4. Which algorithm should now plan the fares, and which cheapest fares change?
Answer both parts, then compare with the run.
Dijkstra's algorithm from Ardle on the original crossings
HarbourFareSettled in roundHarbour before it
Ardle01
Brack32Ardle
Colm63Ardle
Dunn74Brack
Esk95Dunn
Fell126Esk
  1. Brack settles before Colm because 3 is less than 6. Dunn is 7 through Brack, which beats 8 through Colm, and Esk is 9 through Dunn, which beats 13 by the direct crossing from Colm. Fell is 12 through Esk, one less than 13 through Dunn.
  2. A negative fare rules out Dijkstra's algorithm, so use Bellman-Ford. Dijkstra's algorithm settles Brack at 3 before it reaches Colm, so when Colm offers Brack 6 − 4 = 2 it is too late, and it reports the same fares as before. Bellman-Ford finds Brack 2, Dunn 6, Esk 8 and Fell 11, one pound less for every harbour reached through Brack, and reports no negative cycle: no crossing leads back to Colm, so the rebate can be taken only once.
ferry.pypy
import heapq
import math

H = ["Ardle", "Brack", "Colm", "Dunn", "Esk", "Fell"]
FARES = [("Ardle", "Brack", 3), ("Ardle", "Colm", 6), ("Brack", "Dunn", 4), ("Colm", "Dunn", 2),
         ("Colm", "Esk", 7), ("Dunn", "Esk", 2), ("Dunn", "Fell", 6), ("Esk", "Fell", 3)]


def dijkstra(edges):
    graph = {h: [] for h in H}
    for u, v, w in edges:
        graph[u].append((v, w))
    dist = {h: math.inf for h in H}
    prev = {h: "-" for h in H}
    rnd = {}
    dist["Ardle"] = 0
    settled, queue = set(), [(0, "Ardle")]
    while queue:
        _, u = heapq.heappop(queue)
        if u in settled:
            continue
        settled.add(u)
        rnd[u] = len(settled)
        for v, w in graph[u]:
            if v not in settled and dist[u] + w < dist[v]:
                dist[v] = dist[u] + w
                prev[v] = u
                heapq.heappush(queue, (dist[v], v))
    return dist, prev, rnd


def bellman_ford(edges):
    dist = {h: math.inf for h in H}
    prev = {h: "-" for h in H}
    dist["Ardle"] = 0
    for _ in range(len(H) - 1):
        for u, v, w in edges:
            if dist[u] + w < dist[v]:
                dist[v] = dist[u] + w
                prev[v] = u
    cycle = any(dist[u] + w < dist[v] for u, v, w in edges)
    return dist, prev, cycle


d, p, r = dijkstra(FARES)
for h in H:
    print(f"{h} | {d[h]} | round {r[h]} | {p[h]}")
rebate = FARES + [("Colm", "Brack", -4)]
d2, _, _ = dijkstra(rebate)
b, bp, cycle = bellman_ford(rebate)
print("with Colm->Brack -4, dijkstra:     ", ", ".join(f"{h} {d2[h]}" for h in H))
print("with Colm->Brack -4, bellman-ford: ", ", ".join(f"{h} {b[h]}" for h in H), "| negative cycle:", cycle)
print("bellman-ford, harbour before each:", ", ".join(f"{h} {bp[h]}" for h in H))
OutputRan with python:3.14.8-slim-trixie (Python 3.14.8)
Ardle | 0 | round 1 | -
Brack | 3 | round 2 | Ardle
Colm | 6 | round 3 | Ardle
Dunn | 7 | round 4 | Brack
Esk | 9 | round 5 | Dunn
Fell | 12 | round 6 | Esk
with Colm->Brack -4, dijkstra:      Ardle 0, Brack 3, Colm 6, Dunn 7, Esk 9, Fell 12
with Colm->Brack -4, bellman-ford:  Ardle 0, Brack 2, Colm 6, Dunn 6, Esk 8, Fell 11 | negative cycle: False
bellman-ford, harbour before each: Ardle -, Brack Colm, Colm Ardle, Dunn Brack, Esk Dunn, Fell Esk