Skip to content

Commit 0f18a22

Browse files
authored
TSP (2-opt, 3-opt, double-bridge, Held-Karp lower bound) (#274)
* TSP * TSP: add doc
1 parent 72100e5 commit 0f18a22

8 files changed

+735
-0
lines changed

tsp/calc_lkh_alpha.hpp

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
#pragma once
2+
3+
#include <vector>
4+
5+
// http://webhotel4.ruc.dk/~keld/research/LKH/LKH-2.0/DOC/LKH_REPORT.pdf, p.20
6+
template <class DistanceMatrix> auto calc_lkh_alpha(const DistanceMatrix &dist) {
7+
using T = decltype((*dist.adjacents(0).begin()).second);
8+
9+
std::vector<std::vector<int>> to(dist.n());
10+
11+
for (auto [s, t] : mst_edges(dist)) {
12+
to.at(s).push_back(t);
13+
to.at(t).push_back(s);
14+
}
15+
16+
std::vector ret(dist.n(), std::vector<T>(dist.n()));
17+
18+
for (int s = 0; s < dist.n(); ++s) {
19+
auto rec = [&](auto &&self, int now, int prv, T hi) -> void {
20+
ret.at(s).at(now) = dist(s, now) - hi;
21+
for (int nxt : to.at(now)) {
22+
if (nxt == prv) continue;
23+
self(self, nxt, now, std::max(hi, dist(now, nxt)));
24+
}
25+
};
26+
rec(rec, s, -1, T());
27+
}
28+
29+
// Determining special node for the 1-tree
30+
// Reference: p.26 of http://webhotel4.ruc.dk/~keld/research/LKH/LKH-2.0/DOC/LKH_REPORT.pdf
31+
int best_one = -1;
32+
T longest_2nd_nearest = T();
33+
34+
for (int one = 0; one < dist.n(); ++one) {
35+
if (to.at(one).size() != 1) continue;
36+
const int ng = to.at(one).front();
37+
bool found = false;
38+
T second_nearest = T();
39+
40+
for (auto [v, len] : dist.adjacents(one)) {
41+
if (v == ng) continue;
42+
if (!found) {
43+
found = true, second_nearest = len;
44+
} else if (len < second_nearest) {
45+
second_nearest = len;
46+
}
47+
}
48+
49+
if (found and (best_one < 0 or second_nearest > longest_2nd_nearest))
50+
best_one = one, longest_2nd_nearest = second_nearest;
51+
}
52+
53+
if (best_one != -1) {
54+
for (auto [v, len] : dist.adjacents(best_one)) {
55+
if (v == to.at(best_one).front()) continue;
56+
ret.at(best_one).at(v) = ret.at(v).at(best_one) = len - longest_2nd_nearest;
57+
}
58+
}
59+
60+
return ret;
61+
}

tsp/csr_distance_matrix.hpp

+86
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
#pragma once
2+
3+
#include <algorithm>
4+
#include <utility>
5+
#include <vector>
6+
7+
template <class T> class csr_distance_matrix {
8+
9+
int _rows = 0;
10+
std::vector<int> begins;
11+
std::vector<std::pair<int, T>> vals;
12+
13+
public:
14+
csr_distance_matrix() : csr_distance_matrix({}, 0) {}
15+
16+
csr_distance_matrix(const std::vector<std::tuple<int, int, T>> &init, int rows)
17+
: _rows(rows), begins(rows + 1) {
18+
std::vector<int> degs(rows);
19+
for (const auto &p : init) ++degs.at(std::get<0>(p));
20+
21+
for (int i = 0; i < rows; ++i) begins.at(i + 1) = begins.at(i) + degs.at(i);
22+
23+
vals.resize(init.size(), std::make_pair(-1, T()));
24+
for (auto [i, j, w] : init) vals.at(begins.at(i + 1) - (degs.at(i)--)) = {j, w};
25+
}
26+
27+
void apply_pi(const std::vector<T> &pi) {
28+
for (int i = 0; i < n(); ++i) {
29+
for (auto &[j, d] : adjacents(i)) d += pi.at(i) + pi.at(j);
30+
}
31+
}
32+
33+
int n() const noexcept { return _rows; }
34+
35+
struct adjacents_sequence {
36+
csr_distance_matrix *ptr;
37+
int from;
38+
39+
using iterator = typename std::vector<std::pair<int, T>>::iterator;
40+
iterator begin() { return std::next(ptr->vals.begin(), ptr->begins.at(from)); }
41+
iterator end() { return std::next(ptr->vals.begin(), ptr->begins.at(from + 1)); }
42+
};
43+
44+
struct const_adjacents_sequence {
45+
const csr_distance_matrix *ptr;
46+
const int from;
47+
48+
using const_iterator = typename std::vector<std::pair<int, T>>::const_iterator;
49+
const_iterator begin() const {
50+
return std::next(ptr->vals.cbegin(), ptr->begins.at(from));
51+
}
52+
const_iterator end() const {
53+
return std::next(ptr->vals.cbegin(), ptr->begins.at(from + 1));
54+
}
55+
};
56+
57+
adjacents_sequence adjacents(int from) { return {this, from}; }
58+
59+
const_adjacents_sequence adjacents(int from) const { return {this, from}; }
60+
const_adjacents_sequence operator()(int from) const { return {this, from}; }
61+
};
62+
63+
template <class DistanceMatrix> auto build_adjacent_info(const DistanceMatrix &dist, int sz) {
64+
using T = decltype((*dist.adjacents(0).begin()).second);
65+
66+
const std::vector<std::vector<T>> alpha = calc_lkh_alpha(dist);
67+
68+
std::vector<std::tuple<int, int, T>> adjacent_edges;
69+
70+
std::vector<std::tuple<T, T, int>> candidates;
71+
for (int i = 0; i < dist.n(); ++i) {
72+
candidates.clear();
73+
for (auto [j, d] : dist.adjacents(i)) {
74+
if (i != j) candidates.emplace_back(alpha.at(i).at(j), d, j);
75+
}
76+
77+
const int final_sz = std::min<int>(sz, candidates.size());
78+
std::nth_element(candidates.begin(), candidates.begin() + final_sz, candidates.end());
79+
80+
candidates.resize(final_sz);
81+
std::sort(candidates.begin(), candidates.end(),
82+
[&](const auto &l, const auto &r) { return std::get<1>(l) < std::get<1>(r); });
83+
for (auto [alpha, dij, j] : candidates) adjacent_edges.emplace_back(i, j, dij);
84+
}
85+
return csr_distance_matrix(adjacent_edges, dist.n());
86+
}

tsp/dense_distance_matrix.hpp

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
#pragma once
2+
3+
#include <vector>
4+
5+
template <class T> class dense_distance_matrix {
6+
int _n;
7+
std::vector<T> _d;
8+
9+
public:
10+
dense_distance_matrix(const std::vector<std::vector<T>> &distance_vecvec)
11+
: _n(distance_vecvec.size()) {
12+
_d.reserve(n() * n());
13+
for (const auto &vec : distance_vecvec) _d.insert(end(_d), begin(vec), end(vec));
14+
}
15+
16+
template <class U> void apply_pi(const std::vector<U> &pi) {
17+
for (int i = 0; i < n(); ++i) {
18+
for (int j = 0; j < n(); ++j) _d.at(i * n() + j) += pi.at(i) + pi.at(j);
19+
}
20+
}
21+
22+
int n() const noexcept { return _n; }
23+
24+
T dist(int s, int t) const { return _d.at(s * n() + t); }
25+
T operator()(int s, int t) const { return dist(s, t); }
26+
27+
struct adjacents_sequence {
28+
const dense_distance_matrix *ptr;
29+
int from;
30+
struct iterator {
31+
const dense_distance_matrix *ptr;
32+
int from;
33+
int to;
34+
iterator operator++() { return {ptr, from, to++}; }
35+
std::pair<int, T> operator*() const { return {to, ptr->dist(from, to)}; }
36+
bool operator!=(const iterator &rhs) const {
37+
return to != rhs.to or ptr != rhs.ptr or from != rhs.from;
38+
}
39+
};
40+
iterator begin() const { return iterator{ptr, from, 0}; }
41+
iterator end() const { return iterator{ptr, from, ptr->n()}; }
42+
};
43+
44+
adjacents_sequence adjacents(int from) const { return {this, from}; }
45+
};

tsp/held_karp.hpp

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
#pragma once
2+
3+
#include "csr_distance_matrix.hpp"
4+
#include "minimum_one_tree.hpp"
5+
6+
#include <algorithm>
7+
#include <utility>
8+
#include <vector>
9+
10+
// Held–Karp lower bound
11+
// http://webhotel4.ruc.dk/~keld/research/LKH/LKH-2.0/DOC/LKH_REPORT.pdf
12+
// p.26, p.33
13+
template <class DistanceMatrix> auto held_karp_lower_bound(const DistanceMatrix &dist) {
14+
using T = decltype((*dist.adjacents(0).begin()).second);
15+
16+
std::vector<T> best_pi(dist.n()), pi(dist.n());
17+
std::vector<int> V;
18+
T W;
19+
std::tie(W, V) = minimum_one_tree(dist, pi);
20+
if (std::count(V.cbegin(), V.cend(), 0) == dist.n()) return std::make_pair(W, pi);
21+
22+
std::vector<int> lastV = V;
23+
24+
T BestW = W;
25+
const int initial_period = (dist.n() + 1) / 2;
26+
bool is_initial_phase = true;
27+
int period = initial_period;
28+
29+
const auto sparse_subgraph = build_adjacent_info(dist, 50); // p.47
30+
31+
for (long long t0 = 1; t0 > 0; period /= 2, t0 /= 2) {
32+
for (int p = 1; t0 > 0 and p <= period; ++p) {
33+
for (int i = 0; i < dist.n(); ++i) {
34+
if (V.at(i) != 0) pi.at(i) += t0 * (7 * V.at(i) + 3 * lastV.at(i)) / 10;
35+
}
36+
std::swap(lastV, V);
37+
std::tie(W, V) = minimum_one_tree(sparse_subgraph, pi);
38+
if (std::count(begin(V), begin(V) + dist.n(), 0) == dist.n())
39+
return std::make_pair(W, pi);
40+
if (W > BestW) {
41+
BestW = W;
42+
best_pi = pi;
43+
if (is_initial_phase) t0 *= 2;
44+
if (p == period) period = std::min(period * 2, initial_period);
45+
46+
} else if (is_initial_phase and p > period / 2) {
47+
is_initial_phase = false;
48+
p = 0;
49+
t0 = 3 * t0 / 4;
50+
}
51+
}
52+
}
53+
BestW = minimum_one_tree(dist, best_pi).first;
54+
return std::make_pair(BestW, best_pi);
55+
}

tsp/minimum_one_tree.hpp

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
#pragma once
2+
3+
#include <numeric>
4+
#include <utility>
5+
#include <vector>
6+
7+
template <class T, class DistanceMatrix>
8+
std::pair<T, std::vector<int>>
9+
minimum_one_tree(const DistanceMatrix &dist, const std::vector<T> &pi) {
10+
11+
assert(dist.n() > 2);
12+
std::vector<T> dp(dist.n(), std::numeric_limits<T>::max());
13+
std::vector<int> prv(dist.n(), -1);
14+
std::vector<int> used(dist.n());
15+
16+
auto fix_v = [&](int x) -> void {
17+
dp.at(x) = std::numeric_limits<T>::max();
18+
used.at(x) = 1;
19+
for (auto [y, d] : dist.adjacents(x)) {
20+
if (used.at(y)) continue;
21+
if (T len = pi.at(x) + pi.at(y) + d; len < dp.at(y)) dp.at(y) = len, prv.at(y) = x;
22+
}
23+
};
24+
25+
T W = T();
26+
std::vector<int> V(dist.n(), -2);
27+
28+
fix_v(0);
29+
for (int t = 0; t < dist.n() - 1; ++t) {
30+
int i = std::min_element(dp.cbegin(), dp.cend()) - dp.cbegin();
31+
W += dp.at(i);
32+
++V.at(i);
33+
++V.at(prv.at(i));
34+
fix_v(i);
35+
}
36+
37+
// p.26, http://webhotel4.ruc.dk/~keld/research/LKH/LKH-2.0/DOC/LKH_REPORT.pdf
38+
T wlo = T();
39+
int ilo = -1, jlo = -1;
40+
for (int i = 0; i < dist.n(); ++i) {
41+
if (V.at(i) != -1) continue;
42+
T tmp = T();
43+
int jtmp = -1;
44+
for (auto [j, d] : dist.adjacents(i)) {
45+
if (prv.at(i) == j or prv.at(j) == i or i == j) continue;
46+
if (T len = pi.at(i) + pi.at(j) + d; jtmp == -1 or tmp > len) tmp = len, jtmp = j;
47+
}
48+
if (jtmp != -1 and (ilo == -1 or wlo < tmp)) wlo = tmp, ilo = i, jlo = jtmp;
49+
}
50+
++V.at(ilo);
51+
++V.at(jlo);
52+
53+
W += wlo - std::accumulate(pi.cbegin(), pi.cend(), T()) * 2;
54+
55+
return {W, V};
56+
}

tsp/mst_edges.hpp

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
2+
#pragma once
3+
4+
#include <algorithm>
5+
#include <limits>
6+
#include <utility>
7+
#include <vector>
8+
9+
template <class DistanceMatrix>
10+
std::vector<std::pair<int, int>> mst_edges(const DistanceMatrix &dist) {
11+
using T = decltype((*dist.adjacents(0).begin()).second);
12+
13+
if (dist.n() <= 1) return {};
14+
15+
std::vector<T> dp(dist.n(), std::numeric_limits<T>::max());
16+
std::vector<int> prv(dist.n(), -1);
17+
std::vector<int> used(dist.n());
18+
std::vector<std::pair<int, int>> ret(dist.n() - 1);
19+
20+
for (int t = 0; t < dist.n(); ++t) {
21+
int x = std::min_element(dp.cbegin(), dp.cend()) - dp.cbegin();
22+
dp.at(x) = std::numeric_limits<T>::max();
23+
used.at(x) = 1;
24+
if (t > 0) ret.at(t - 1) = {prv.at(x), x};
25+
26+
for (auto [y, len] : dist.adjacents(x)) {
27+
if (!used.at(y) and len < dp.at(y)) dp.at(y) = len, prv.at(y) = x;
28+
}
29+
}
30+
31+
return ret;
32+
}

0 commit comments

Comments
 (0)