136#include <unordered_set>
144using namespace Aleph;
163template <
typename Func>
168 cout <<
"Usage: " <<
prog <<
" [--compare] [-n <nodes>] [-e <edges>] [--help]\n";
169 cout <<
"\nIf no flags are given, all demos are executed.\n";
170 cout <<
"\nIf -n/-e are provided, a random non-negative weighted graph is generated.\n";
171 cout <<
"(If --compare is also given and the graph is small enough, Floyd-Warshall is run too.)\n";
179 nodes.reserve(
static_cast<size_t>(n));
180 for (
int i = 0; i < n; ++i)
183 std::mt19937
rng(seed);
184 std::uniform_int_distribution<int>
node_dist(0, n - 1);
185 std::uniform_real_distribution<double>
weight_dist(1.0, 10.0);
187 std::unordered_set<long long>
used;
188 used.reserve(
static_cast<size_t>(e) * 2);
195 used.
insert(
static_cast<long long>(i) * n + (i + 1));
205 const long long key =
static_cast<long long>(u) * n + v;
219 nodes.reserve(
static_cast<size_t>(g.get_num_nodes()));
220 for (
auto it = g.get_node_it(); it.has_curr(); it.next())
221 nodes.push_back(it.get_curr());
229 for (
auto* src :
nodes)
230 for (
auto* tgt :
nodes)
241 const auto& dist =
floyd.get_dist_mat();
242 const double Inf = std::numeric_limits<double>::max();
246 for (
auto* src :
nodes)
249 for (
auto* tgt :
nodes)
255 const bool f_inf = (fd == Inf);
256 const bool j_inf = (
jd == std::numeric_limits<double>::infinity());
262 if (!
f_inf && std::abs(fd -
jd) > 1e-9)
269 cout <<
"\nComparison results:\n";
281 for (
auto it = g.get_node_it(); it.has_curr(); it.next())
282 if (it.get_curr()->get_info() == name)
283 return it.get_curr();
289 cout <<
"Graph (" << g.get_num_nodes() <<
" nodes, "
290 << g.get_num_arcs() <<
" arcs):\n";
291 for (
auto nit = g.get_node_it();
nit.has_curr();
nit.next())
293 auto* node =
nit.get_curr();
294 cout <<
" " << node->get_info() <<
" → ";
296 for (
auto ait = g.get_out_it(node);
ait.has_curr();
ait.next())
298 auto* arc =
ait.get_curr();
299 auto* tgt = g.get_tgt_node(arc);
300 if (!first)
cout <<
", ";
301 cout << tgt->get_info() <<
"(" <<
showpos << arc->get_info()
305 if (first)
cout <<
"(none)";
310template <
typename Func>
313 auto start = chrono::high_resolution_clock::now();
315 auto end = chrono::high_resolution_clock::now();
316 return chrono::duration<double, milli>(end - start).count();
325 cout <<
"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n";
326 cout <<
"Example 1: All-Pairs Shortest Paths with Negative Weights\n";
327 cout <<
"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n";
344 auto* A = g.insert_node(
"A");
345 auto*
B = g.insert_node(
"B");
346 auto* C = g.insert_node(
"C");
347 auto*
D = g.insert_node(
"D");
348 auto* E = g.insert_node(
"E");
350 g.insert_arc(A,
B, 3.0);
351 g.insert_arc(A, C, 8.0);
352 g.insert_arc(
B,
D, 1.0);
353 g.insert_arc(C,
B, -4.0);
354 g.insert_arc(C, E, 2.0);
355 g.insert_arc(
D, E, -3.0);
356 g.insert_arc(E, A, 10.0);
360 cout <<
"\n▶ Running Johnson's Algorithm:\n\n";
367 cout <<
" Node potentials (from Bellman-Ford):\n";
368 vector<Node*>
nodes = {A,
B, C,
D, E};
369 for (
auto* node :
nodes)
371 double h =
johnson.get_potential(node);
372 cout <<
" h(" << node->get_info() <<
") = " <<
showpos <<
h
377 cout <<
"\n Shortest distances (all pairs):\n\n";
379 for (
auto* tgt :
nodes)
382 for (
size_t i = 0; i <
nodes.size(); ++i)
386 for (
auto* src :
nodes)
388 cout <<
" " << src->get_info() <<
" │ ";
389 for (
auto* tgt :
nodes)
391 double dist =
johnson.get_distance(src, tgt);
392 if (dist == numeric_limits<double>::infinity())
401 cout <<
"\n Example path A → D:\n";
405 catch (
const domain_error& e)
407 cout <<
" ERROR: " << e.what() <<
"\n";
417 cout <<
"\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n";
418 cout <<
"Example 2: Negative Cycle Detection\n";
419 cout <<
"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n";
421 cout <<
"Johnson's algorithm uses Bellman-Ford internally, so it can\n";
422 cout <<
"detect negative cycles. If one exists, construction throws.\n\n";
436 auto* A = g.insert_node(
"A");
437 auto*
B = g.insert_node(
"B");
438 auto* C = g.insert_node(
"C");
440 g.insert_arc(A,
B, 2.0);
441 g.insert_arc(
B, C, 2.0);
442 g.insert_arc(C, A, -5.0);
446 cout <<
"\n▶ Attempting to run Johnson's Algorithm:\n\n";
451 cout <<
" Unexpected: No exception thrown!\n";
453 catch (
const domain_error& e)
455 cout <<
" ⚠ EXCEPTION: " << e.what() <<
"\n";
456 cout <<
"\n Johnson cannot compute shortest paths when negative cycles exist\n";
457 cout <<
" because shortest paths become undefined (can always improve by\n";
458 cout <<
" going around the negative cycle one more time).\n";
468 cout <<
"\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n";
469 cout <<
"Example 3: Understanding Edge Reweighting\n";
470 cout <<
"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n";
473 The key insight of Johnson's algorithm is REWEIGHTING:
475 w'(u,v) = w(u,v) + h(u) - h(v)
477 where h(v) is the shortest distance from a dummy source to v.
481 For ANY path p from s to t, the reweighted length is:
484 = Σ [w(u,v) + h(u) - h(v)]
485 = Σ w(u,v) + h(s) - h(t) (telescoping sum!)
488 Since h(s) and h(t) are constants, minimizing w'(p) also minimizes w(p)!
490 Why are reweighted edges non-negative?
491 ─────────────────────────────────────
492 Since h is computed by Bellman-Ford:
493 h(v) ≤ h(u) + w(u,v) (triangle inequality)
496 w(u,v) + h(u) - h(v) ≥ 0
503 auto* A = g.insert_node(
"A");
504 auto*
B = g.insert_node(
"B");
505 auto* C = g.insert_node(
"C");
507 g.insert_arc(A,
B, 5.0);
508 g.insert_arc(
B, C, -3.0);
509 g.insert_arc(A, C, 4.0);
511 cout <<
"\n Original graph:\n";
518 cout <<
"\n Node potentials:\n";
519 cout <<
" h(A) = " <<
johnson.get_potential(A) <<
"\n";
521 cout <<
" h(C) = " <<
johnson.get_potential(C) <<
"\n";
523 cout <<
"\n After reweighting, all edges become non-negative,\n";
524 cout <<
" allowing Dijkstra to be used!\n";
526 catch (
const domain_error& e)
528 cout <<
" Error: " << e.what() <<
"\n";
538 cout <<
"\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n";
539 cout <<
"Example 4: When to Use Johnson vs Floyd-Warshall\n";
540 cout <<
"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n";
543┌───────────────────────────────────────────────────────────────────────────┐
544│ All-Pairs Shortest Paths Algorithm Selection │
545├───────────────────────────────────────────────────────────────────────────┤
547│ Algorithm │ Time Complexity │ Best For │
548│ ────────────────┼────────────────────┼──────────────────────────────────│
549│ Floyd-Warshall │ O(V³) │ Dense graphs (E ≈ V²) │
550│ │ │ Simple implementation │
551│ │ │ Works with negative edges │
552│ ────────────────┼────────────────────┼──────────────────────────────────│
553│ Johnson │ O(V² log V + VE) │ Sparse graphs (E ≈ V) │
554│ │ │ = O(V² log V) when sparse │
555│ │ │ Works with negative edges │
556│ ────────────────┼────────────────────┼──────────────────────────────────│
557│ V × Dijkstra │ O(V(V log V + E)) │ Non-negative edges only │
558│ │ │ Simpler than Johnson │
560├───────────────────────────────────────────────────────────────────────────┤
561│ Sparsity Rule of Thumb: │
562│ ───────────────────────── │
563│ • E < V² / log V → Use Johnson │
564│ • E > V² / log V → Use Floyd-Warshall │
567│ • E < 100,000 → Johnson is faster │
568│ • E > 100,000 → Floyd-Warshall may be faster │
570└───────────────────────────────────────────────────────────────────────────┘
580 cout <<
"\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n";
581 cout <<
"Example 5: Currency Arbitrage Detection\n";
582 cout <<
"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n";
585 Currency arbitrage occurs when you can profit by converting currencies
586 in a cycle, ending up with more than you started with.
588 To detect this using shortest paths:
589 - Create edge (A→B) with weight = -log(exchange_rate(A→B))
590 - A negative cycle means: product of rates > 1 → arbitrage opportunity!
597 Product: 0.85 × 0.88 × 1.40 = 1.0472 > 1 → Arbitrage exists!
604 Sum: 0.163 + 0.128 - 0.336 = -0.045 < 0 → Negative cycle!
609 auto*
USD = g.insert_node(
"USD");
610 auto*
EUR = g.insert_node(
"EUR");
611 auto*
GBP = g.insert_node(
"GBP");
619 g.insert_arc(
EUR,
USD, -
log(1.0 / 0.85));
620 g.insert_arc(
GBP,
EUR, -
log(1.0 / 0.88));
621 g.insert_arc(
USD,
GBP, -
log(1.0 / 1.40));
623 cout <<
"\n▶ Checking for arbitrage opportunity:\n\n";
628 cout <<
" No arbitrage opportunity found (no negative cycles).\n";
630 catch (
const domain_error& e)
632 cout <<
" ⚠ ARBITRAGE OPPORTUNITY DETECTED!\n";
633 cout <<
" Negative cycle exists in the exchange rate graph.\n";
634 cout <<
"\n Profit path: USD → EUR → GBP → USD\n";
636 << 1000 * 0.85 * 0.88 * 1.40 <<
"\n";
650 for (
int i = 1; i <
argc; ++i)
653 if (
arg ==
"--help" ||
arg ==
"-h")
658 if (
arg ==
"--compare")
670 n = std::stoi(
argv[++i]);
680 e = std::stoi(
argv[++i]);
684 cout <<
"Unknown argument: " <<
arg <<
"\n";
689 if (n != -1 || e != -1)
705 cout <<
"Random graph benchmark: n=" << n <<
", e=" << e <<
"\n";
719 catch (
const std::exception&
ex)
721 cout <<
"ERROR: " <<
ex.what() <<
"\n";
728 cout <<
"\nSkipping Floyd-Warshall comparison for n > 200.\n";
736 auto* A = g.insert_node(
"A");
737 auto*
B = g.insert_node(
"B");
738 auto* C = g.insert_node(
"C");
739 auto*
D = g.insert_node(
"D");
740 auto* E = g.insert_node(
"E");
741 g.insert_arc(A,
B, 3.0);
742 g.insert_arc(A, C, 8.0);
743 g.insert_arc(
B,
D, 1.0);
744 g.insert_arc(C,
B, -4.0);
745 g.insert_arc(C, E, 2.0);
746 g.insert_arc(
D, E, -3.0);
747 g.insert_arc(E, A, 10.0);
752 cout <<
"╔══════════════════════════════════════════════════════════════════════╗\n";
753 cout <<
"║ Johnson's Algorithm for All-Pairs Shortest Paths ║\n";
754 cout <<
"╚══════════════════════════════════════════════════════════════════════╝\n\n";
Floyd-Warshall algorithm for all-pairs shortest paths.
Johnson's algorithm for all-pairs shortest paths.
WeightedDigraph::Node Node
Generic directed graph (digraph) wrapper template.
typename BaseGraph::Arc Arc
typename BaseGraph::Node Node
T & insert(const T &item)
Insert a new item by copy.
Compute the all-pairs shortest path distance matrix and the path reconstruction matrix for a graph g ...
Johnson's algorithm for all-pairs shortest paths.
iterator end() noexcept
Return an STL-compatible end iterator.
__gmp_expr< T, __gmp_unary_expr< __gmp_expr< T, U >, __gmp_log_function > > log(const __gmp_expr< T, U > &expr)
DynArray< Graph::Node * > nodes
void example_complexity_comparison()
Node * find_node(WeightedDigraph &g, const string &name)
void example_reweighting()
void print_graph(WeightedDigraph &g)
void example_currency_arbitrage()
void example_basic_all_pairs()
static void compare_with_floyd(WeightedDigraph &g)
static void usage(const char *prog)
static WeightedDigraph build_random_graph(int n, int e, unsigned seed)
void example_negative_cycle()
double measure_ms(Func &&f)
Main namespace for Aleph-w library functions.
std::string to_string(const time_t t, const std::string &format)
Format a time_t value into a string using format.
DynList< T > maps(const C &c, Op op)
Classic map operation.
Arc of graph implemented with double-linked adjacency lists.
static void set_zero(Arc *a)
Distance_Type operator()(Arc *a) const
Generic graph and digraph implementations.