XNetwork 1.7.5; VERSION ${PROJECT_VERSION}
Loading...
Searching...
No Matches
tsp.hpp
Go to the documentation of this file.
1#pragma once
2
25#include <algorithm>
26#include <cassert>
27#include <cstddef>
28#include <limits>
29#include <py2cpp/range.hpp>
30#include <py2cpp/set.hpp>
31#include <set>
32#include <tuple>
33#include <vector>
35
36// ---------------------------------------------------------------------------
37// Helper: calculate_total_distance
38// ---------------------------------------------------------------------------
39
49template <typename Node, typename WeightFunc>
50double calculate_total_distance(const std::vector<Node>& path, WeightFunc&& weight) {
51 double dist = 0.0;
52 for (size_t i = 0; i + 1 < path.size(); ++i) {
53 dist += std::forward<WeightFunc>(weight)(path[i], path[i + 1]);
54 }
55 return dist;
56}
57
58// ---------------------------------------------------------------------------
59// Internal helpers for the Christofides algorithm
60// ---------------------------------------------------------------------------
61
62namespace detail {
63
73 template <typename Node, typename WeightFunc> auto prim_mst(size_t n, WeightFunc&& weight)
74 -> std::vector<std::pair<Node, Node>> {
75 constexpr double INF = std::numeric_limits<double>::max();
76
77 std::vector<bool> in_mst(n, false);
78 std::vector<double> key(n, INF);
79 std::vector<Node> parent(n, Node{0});
80
81 key[0] = 0.0;
82
83 for (size_t count = 0; count < n; ++count) {
84 // Find the vertex with smallest key not yet in MST
85 double best = INF;
86 Node u = Node{0};
87 for (Node v = Node{0}; v < static_cast<Node>(n); ++v) {
88 if (!in_mst[static_cast<size_t>(v)] && key[static_cast<size_t>(v)] < best) {
89 best = key[static_cast<size_t>(v)];
90 u = v;
91 }
92 }
93 if (best == INF) break; // isolated vertex (shouldn't happen for complete graphs)
94
95 in_mst[static_cast<size_t>(u)] = true;
96
97 // Relaxation
98 for (Node v = Node{0}; v < static_cast<Node>(n); ++v) {
99 if (!in_mst[static_cast<size_t>(v)]) {
100 const double w = std::forward<WeightFunc>(weight)(u, v);
101 auto& kv = key[static_cast<size_t>(v)];
102 if (w < kv) {
103 kv = w;
104 parent[static_cast<size_t>(v)] = u;
105 }
106 }
107 }
108 }
109
110 std::vector<std::pair<Node, Node>> edges;
111 edges.reserve(n - 1);
112 for (Node v = Node{1}; v < static_cast<Node>(n); ++v) {
113 const auto p = parent[static_cast<size_t>(v)];
114 if (p != v) // safety
115 edges.emplace_back(p, v);
116 }
117 return edges;
118 }
119
123 template <typename Node>
124 auto find_odd_degree_nodes(const std::vector<std::pair<Node, Node>>& mst_edges, size_t n)
125 -> std::vector<Node> {
126 std::vector<int> deg(n, 0);
127 for (const auto& [u, v] : mst_edges) {
128 ++deg[static_cast<size_t>(u)];
129 ++deg[static_cast<size_t>(v)];
130 }
131 std::vector<Node> odd;
132 for (Node v = Node{0}; v < static_cast<Node>(n); ++v) {
133 if (deg[static_cast<size_t>(v)] % 2 != 0) odd.push_back(v);
134 }
135 return odd;
136 }
137
145 template <typename Node, typename WeightFunc>
146 auto greedy_min_weight_matching(const std::vector<Node>& odd_nodes, WeightFunc&& weight)
147 -> std::vector<std::pair<Node, Node>> {
148 const size_t k = odd_nodes.size();
149 if (k < 2) return {};
150
151 // Build edge list sorted by weight
152 std::vector<std::tuple<double, Node, Node>> edges;
153 edges.reserve(k * (k - 1) / 2);
154 for (size_t i = 0; i < k; ++i) {
155 for (size_t j = i + 1; j < k; ++j) {
156 edges.emplace_back(std::forward<WeightFunc>(weight)(odd_nodes[i], odd_nodes[j]),
158 }
159 }
160 std::sort(edges.begin(), edges.end(),
161 [](const auto& a, const auto& b) { return std::get<0>(a) < std::get<0>(b); });
162
163 std::vector<bool> used(k, false);
164 std::vector<std::pair<Node, Node>> matching;
165 matching.reserve(k / 2);
166
167 for (const auto& [w, u, v] : edges) {
168 // Map node -> index in odd_nodes (linear scan, k is small)
169 size_t iu = 0;
170 size_t iv = 0;
171 for (size_t t = 0; t < k; ++t) {
172 if (odd_nodes[t] == u) iu = t;
173 if (odd_nodes[t] == v) iv = t;
174 }
175 if (!used[iu] && !used[iv]) {
176 matching.emplace_back(u, v);
177 used[iu] = true;
178 used[iv] = true;
179 }
180 }
181 return matching;
182 }
183
190 template <typename Node>
191 auto build_multigraph(size_t n, const std::vector<std::pair<Node, Node>>& mst_edges,
192 const std::vector<std::pair<Node, Node>>& matching_edges)
193 -> std::vector<std::multiset<Node>> {
194 std::vector<std::multiset<Node>> adj(n);
195 const auto add = [&](Node a, Node b) {
196 adj[static_cast<size_t>(a)].insert(b);
197 adj[static_cast<size_t>(b)].insert(a);
198 };
199 for (const auto& [u, v] : mst_edges) add(u, v);
200 for (const auto& [u, v] : matching_edges) add(u, v);
201 return adj;
202 }
203
213 template <typename Node> auto hierholzer(std::vector<std::multiset<Node>> adj, Node start)
214 -> std::vector<std::pair<Node, Node>>;
215
220 template <typename Node>
221 auto shortcut_eulerian(const std::vector<std::pair<Node, Node>>& eulerian_circuit)
222 -> std::vector<Node> {
223 std::vector<Node> path;
225 for (const auto& [u, v] : eulerian_circuit) {
226 if (!visited.contains(u)) {
227 path.push_back(u);
228 visited.insert(u);
229 }
230 }
231 if (!path.empty()) path.push_back(path.front()); // close the loop
232 return path;
233 }
234
235} // namespace detail
236
237// ---------------------------------------------------------------------------
238// Christofides TSP - 3/2-approximation
239// ---------------------------------------------------------------------------
240
250template <typename Graph, typename WeightFunc>
251auto christofides_tsp(const Graph& G, WeightFunc&& weight) -> std::vector<typename Graph::node_t> {
252 using Node = typename Graph::node_t;
253 const size_t n = G.number_of_nodes();
254
255 if (n == 0) return {};
256 if (n == 1) return {Node{0}, Node{0}};
257
258 // 1. Minimum Spanning Tree
259 const auto mst_edges = detail::prim_mst<Node>(n, weight);
260
261 // 2. Odd-degree vertices in the MST
263
264 // 3. Minimum-weight perfect matching on odd vertices
265 const auto matching
266 = detail::greedy_min_weight_matching(odd_nodes, std::forward<WeightFunc>(weight));
267
268 // 4. Build the Eulerian multigraph (MST + matching)
269 auto adj = detail::build_multigraph<Node>(n, mst_edges, matching);
270
271 // 5. Eulerian circuit via Hierholzer
272 const auto eulerian_circuit = detail::hierholzer<Node>(std::move(adj), Node{0});
273
274 // 6. Shortcut -> Hamiltonian cycle
276}
277
278// ---------------------------------------------------------------------------
279// 2-Opt local search
280// ---------------------------------------------------------------------------
281
296template <typename Graph, typename WeightFunc>
297auto two_opt(std::vector<typename Graph::node_t> path, const Graph& G, WeightFunc&& weight)
298 -> std::vector<typename Graph::node_t> {
299 (void)G; // only used for type deduction
300
301 bool improved = true;
302 while (improved) {
303 improved = false;
304 for (size_t i = 1; i + 2 < path.size(); ++i) {
305 for (size_t j = i + 1; j + 1 < path.size(); ++j) {
306 if (j - i == 1) continue;
307
308 const double delta = -weight(path[i - 1], path[i]) - weight(path[j], path[j + 1])
309 + weight(path[i - 1], path[j])
310 + std::forward<WeightFunc>(weight)(path[i], path[j + 1]);
311
312 if (delta < -1.0e-12) {
313 std::reverse(path.begin() + static_cast<ptrdiff_t>(i),
314 path.begin() + static_cast<ptrdiff_t>(j + 1));
315 improved = true;
316 }
317 }
318 }
319 }
320 return path;
321}
322
323// ---------------------------------------------------------------------------
324// Combined solver: Christofides + 2-Opt
325// ---------------------------------------------------------------------------
326
340template <typename Graph, typename WeightFunc>
341auto solve_christofides_2opt_tsp(const Graph& G, WeightFunc&& weight)
342 -> std::vector<typename Graph::node_t> {
343 auto path = christofides_tsp(G, std::forward<WeightFunc>(weight));
344 if (path.size() <= 3) return path;
345 return two_opt(std::move(path), G, weight);
346}
Read-only map of maps of maps (view into a dict-of-dict-of-dict structure)
Definition coreviews.hpp:109
AdjacencyView(Atlas &d)
Construct an AdjacencyView from an Atlas container.
Definition coreviews.hpp:115
auto begin() const
Get iterator to the beginning of the view.
Definition coreviews.hpp:55
auto size() const -> size_t
Get the number of elements in the view.
Definition coreviews.hpp:49
Undirected graph data structure for XNetwork.
Definition hadlock.hpp:40
auto find_odd_degree_nodes(const std::vector< std::pair< Node, Node > > &mst_edges, size_t n) -> std::vector< Node >
Collect vertices with odd degree in the MST.
Definition tsp.hpp:124
auto shortcut_eulerian(const std::vector< std::pair< Node, Node > > &eulerian_circuit) -> std::vector< Node >
Convert an Eulerian circuit to a Hamiltonian cycle by skipping repeated vertices (shortcut).
Definition tsp.hpp:221
auto prim_mst(size_t n, WeightFunc &&weight) -> std::vector< std::pair< Node, Node > >
Prim's MST for dense (complete) graphs - O(n^2).
Definition tsp.hpp:73
auto greedy_min_weight_matching(const std::vector< Node > &odd_nodes, WeightFunc &&weight) -> std::vector< std::pair< Node, Node > >
Greedy minimum-weight perfect matching - O(k^2 log k).
Definition tsp.hpp:146
auto hierholzer(std::vector< std::multiset< Node > > adj, Node start) -> std::vector< std::pair< Node, Node > >
Hierholzer's algorithm - find an Eulerian circuit.
auto build_multigraph(size_t n, const std::vector< std::pair< Node, Node > > &mst_edges, const std::vector< std::pair< Node, Node > > &matching_edges) -> std::vector< std::multiset< Node > >
Build a multigraph adjacency list (MST + matching).
Definition tsp.hpp:191
constexpr int INF
Definition hadlock.hpp:42
auto christofides_tsp(const Graph &G, WeightFunc &&weight) -> std::vector< typename Graph::node_t >
Solve Metric TSP using the Christofides approximation algorithm.
Definition tsp.hpp:251
double calculate_total_distance(const std::vector< Node > &path, WeightFunc &&weight)
Compute the total length of a Hamiltonian path/cycle.
Definition tsp.hpp:50
auto two_opt(std::vector< typename Graph::node_t > path, const Graph &G, WeightFunc &&weight) -> std::vector< typename Graph::node_t >
Refine a TSP tour using 2-opt neighbourhood search.
Definition tsp.hpp:297
auto solve_christofides_2opt_tsp(const Graph &G, WeightFunc &&weight) -> std::vector< typename Graph::node_t >
Solve Metric TSP using Christofides followed by 2-Opt refinement.
Definition tsp.hpp:341