Dijkstra's Algorithm

For rust, I’m updating the documentation for the standard library and specifically with the collections. For the priority queue I had the idea to use Dijkstra’s algorithm as a fun example. That idea was well received and that example is now live.

At first I wanted to use A* to solve the eight puzzle, which I’ve done before, but that example became far too big.

Here’s the example code currently up in docs:

Using rustc 0.12.0-pre (ef352faea84fa16616b773bd9aa5020d7c76bff0 2014-07-18 21:46:32 +0000)

use std::collections::PriorityQueue;
use std::uint;

#[deriving(Eq, PartialEq)]
struct State {
    cost: uint,
    position: uint
}

// The priority queue depends on `Ord`.
// Explicitly implement the trait so the queue becomes a min-heap
// instead of a max-heap.
impl Ord for State {
    fn cmp(&self, other: &State) -> Ordering {
        // Notice that the we flip the ordering here
        other.cost.cmp(&self.cost)
    }
}

// `PartialOrd` needs to be implemented as well.
impl PartialOrd for State {
    fn partial_cmp(&self, other: &State) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

// Each node is represented as an `uint`, for a shorter implementation.
struct Edge {
    node: uint,
    cost: uint
}

// Dijkstra's shortest path algorithm.

// Start at `start` and use `dist` to track the current shortest distance
// to each node. This implementation isn't memory efficient as it may leave duplicate
// nodes in the queue. It also uses `uint::MAX` as a sentinel value,
// for a simpler implementation.
fn shortest_path(adj_list: &Vec<Vec<Edge>>, start: uint, goal: uint) -> uint {
    // dist[node] = current shortest distance from `start` to `node`
    let mut dist = Vec::from_elem(adj_list.len(), uint::MAX);

    let mut pq = PriorityQueue::new();

    // We're at `start`, with a zero cost
    *dist.get_mut(start) = 0u;
    pq.push(State { cost: 0u, position: start });

    // Examine the frontier with lower cost nodes first (min-heap)
    loop {
        let State { cost, position } = match pq.pop() {
            None => break, // empty
            Some(s) => s
        };

        // Alternatively we could have continued to find all shortest paths
        if position == goal { return cost }

        // Important as we may have already found a better way
        if cost > dist[position] { continue }

        // For each node we can reach, see if we can find a way with
        // a lower cost going through this node
        for edge in adj_list[position].iter() {
            let next = State { cost: cost + edge.cost, position: edge.node };

            // If so, add it to the frontier and continue
            if next.cost < dist[next.position] {
                pq.push(next);
                // Relaxation, we have now found a better way
                *dist.get_mut(next.position) = next.cost;
            }
        }
    }

    // Goal not reachable
    uint::MAX
}

fn main() {
    // This is the directed graph we're going to use.
    // The node numbers correspond to the different states,
    // and the edge weights symbolises the cost of moving
    // from one node to another.
    // Note that the edges are one-way.
    //
    //                  7
    //          +-----------------+
    //          |                 |
    //          v   1        2    |
    //          0 -----> 1 -----> 3 ---> 4
    //          |        ^        ^      ^
    //          |        | 1      |      |
    //          |        |        | 3    | 1
    //          +------> 2 -------+      |
    //           10      |               |
    //                   +---------------+
    //
    // The graph is represented as an adjecency list where each index,
    // corresponding to a node value, has a list of outgoing edges.
    // Chosen for it's efficiency.
    let graph = vec![
        // Node 0
        vec![Edge { node: 2, cost: 10 },
             Edge { node: 1, cost: 1 }],
        // Node 1
        vec![Edge { node: 3, cost: 2 }],
        // Node 2
        vec![Edge { node: 1, cost: 1 },
             Edge { node: 3, cost: 3 },
             Edge { node: 4, cost: 1 }],
        // Node 3
        vec![Edge { node: 0, cost: 7 },
             Edge { node: 4, cost: 2 }],
        // Node 4
        vec![]];

    assert_eq!(shortest_path(&graph, 0, 1), 1);
    assert_eq!(shortest_path(&graph, 0, 3), 3);
    assert_eq!(shortest_path(&graph, 3, 0), 7);
    assert_eq!(shortest_path(&graph, 0, 4), 5);
    assert_eq!(shortest_path(&graph, 4, 0), uint::MAX);
}