graph_advanced/
tsp.rs

1/// Solves the **Travelling Salesman Problem** exactly using the Held-Karp
2/// bitmask dynamic programming algorithm.
3///
4/// Given a complete weighted directed graph represented as a distance matrix,
5/// finds the minimum-cost Hamiltonian circuit starting and ending at node `0`.
6///
7/// # Algorithm
8///
9/// Let `dp[mask][v]` be the minimum cost to reach node `v` having visited
10/// exactly the nodes encoded in `mask` (bit `i` set ⟹ node `i` visited),
11/// starting from node `0`.
12///
13/// **Recurrence:**  
14/// `dp[mask | (1 << u)][u] = min(dp[mask][v] + dist[v][u])`  
15/// for all `v` in `mask` and `u` not in `mask`.
16///
17/// **Answer:**  
18/// `min over v of (dp[all_visited][v] + dist[v][0])`
19///
20/// # Arguments
21///
22/// - `dist` — `n × n` distance matrix. `dist[i][j]` is the cost of going
23///   from node `i` to node `j`. Use `f64::INFINITY` for missing edges.
24///
25/// # Returns
26///
27/// `Some((cost, path))` — the minimum tour cost and the node visit order
28/// (starts and ends at node `0`).  
29/// `None` — no Hamiltonian circuit exists (some required edge is `INFINITY`),
30/// or `dist` is empty.
31///
32/// # Complexity
33///
34/// O(2^n · n²) time, O(2^n · n) space. Practical for n ≤ 20.
35///
36/// # Panics
37///
38/// Panics if `dist` is not square (all rows must have the same length as the
39/// number of rows).
40///
41/// # Examples
42///
43/// ```
44/// use graph_advanced::tsp_held_karp;
45///
46/// // 4-node complete graph with known optimal tour cost 35.
47/// //        0   1   2   3
48/// let dist = vec![
49///     vec![0.0, 10.0, 15.0, 20.0], // from 0
50///     vec![10.0, 0.0, 35.0, 25.0], // from 1
51///     vec![15.0, 35.0, 0.0, 30.0], // from 2
52///     vec![20.0, 25.0, 30.0, 0.0], // from 3
53/// ];
54///
55/// let (cost, path) = tsp_held_karp(&dist).unwrap();
56/// assert_eq!(cost, 80.0); // 0→1(10)+1→3(25)+3→2(30)+2→0(15)
57/// assert_eq!(path.first(), Some(&0));
58/// assert_eq!(path.last(), Some(&0));
59/// assert_eq!(path.len(), 5); // 4 nodes + return to 0
60/// ```
61pub fn tsp_held_karp(dist: &[Vec<f64>]) -> Option<(f64, Vec<usize>)> {
62    let n = dist.len();
63    if n == 0 {
64        return None;
65    }
66    assert!(
67        dist.iter().all(|row| row.len() == n),
68        "distance matrix must be square"
69    );
70    if n == 1 {
71        return Some((0.0, vec![0, 0]));
72    }
73
74    let full_mask = (1usize << n) - 1;
75
76    // dp[mask][v] = min cost to reach v having visited exactly nodes in mask.
77    // parent[mask][v] = the node we came from to achieve dp[mask][v].
78    let mut dp = vec![vec![f64::INFINITY; n]; 1 << n];
79    let mut parent: Vec<Vec<usize>> = vec![vec![usize::MAX; n]; 1 << n];
80
81    // Start at node 0.
82    dp[1][0] = 0.0;
83
84    for mask in 1..=(full_mask) {
85        // Only process masks that include node 0 (bit 0 always set).
86        if mask & 1 == 0 {
87            continue;
88        }
89        for v in 0..n {
90            if mask & (1 << v) == 0 {
91                continue; // v not in mask
92            }
93            if dp[mask][v] == f64::INFINITY {
94                continue;
95            }
96            // Try extending to each unvisited node u.
97            for u in 0..n {
98                if mask & (1 << u) != 0 {
99                    continue; // u already visited
100                }
101                let cost = dp[mask][v] + dist[v][u];
102                let next_mask = mask | (1 << u);
103                if cost < dp[next_mask][u] {
104                    dp[next_mask][u] = cost;
105                    parent[next_mask][u] = v;
106                }
107            }
108        }
109    }
110
111    // Find the minimum cost to complete the circuit: return to node 0.
112    let mut best_cost = f64::INFINITY;
113    let mut last_node = usize::MAX;
114
115    for v in 1..n {
116        if dp[full_mask][v] == f64::INFINITY {
117            continue;
118        }
119        let total = dp[full_mask][v] + dist[v][0];
120        if total < best_cost {
121            best_cost = total;
122            last_node = v;
123        }
124    }
125
126    if best_cost == f64::INFINITY {
127        return None; // No Hamiltonian circuit exists.
128    }
129
130    // Reconstruct path by following parent pointers.
131    let mut path = Vec::with_capacity(n + 1);
132    let mut mask = full_mask;
133    let mut curr = last_node;
134
135    while curr != usize::MAX && mask != 0 {
136        path.push(curr);
137        let prev = parent[mask][curr];
138        mask ^= 1 << curr;
139        curr = prev;
140    }
141
142    path.reverse();
143    path.push(0); // Return to start.
144
145    Some((best_cost, path))
146}