Aleph-w 3.0
A C++ Library for Data Structures and Algorithms
Loading...
Searching...
No Matches
test_kosaraju.cc
Go to the documentation of this file.
1
2/*
3 Aleph_w
4
5 Data structures & Algorithms
6 version 2.0.0b
7 https://github.com/lrleon/Aleph-w
8
9 This file is part of Aleph-w library
10
11 Copyright (c) 2002-2026 Leandro Rabindranath Leon
12
13 Permission is hereby granted, free of charge, to any person obtaining a copy
14 of this software and associated documentation files (the "Software"), to deal
15 in the Software without restriction, including without limitation the rights
16 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 copies of the Software, and to permit persons to whom the Software is
18 furnished to do so, subject to the following conditions:
19
20 The above copyright notice and this permission notice shall be included in all
21 copies or substantial portions of the Software.
22
23 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 SOFTWARE.
30*/
31
32
45#include <gtest/gtest.h>
46
47#include <kosaraju.H>
48#include <tpl_graph.H>
49#include <Tarjan.H>
50
51using namespace Aleph;
52
53// Graph types for tests
55using Node = GT::Node;
56using Arc = GT::Arc;
57
58// ========== Helper Functions ==========
59
60// Create a simple chain: 0 -> 1 -> 2 -> ... -> (n-1)
61GT create_chain(size_t n) {
62 GT g;
63 std::vector<Node*> nodes;
64 for (size_t i = 0; i < n; ++i)
65 nodes.push_back(g.insert_node(static_cast<int>(i)));
66 for (size_t i = 0; i < n - 1; ++i)
67 g.insert_arc(nodes[i], nodes[i + 1], static_cast<int>(i));
68 return g;
69}
70
71// Create a cycle: 0 -> 1 -> 2 -> ... -> (n-1) -> 0
72GT create_cycle(size_t n) {
73 GT g;
74 std::vector<Node*> nodes;
75 for (size_t i = 0; i < n; ++i)
76 nodes.push_back(g.insert_node(static_cast<int>(i)));
77 for (size_t i = 0; i < n; ++i)
78 g.insert_arc(nodes[i], nodes[(i + 1) % n], static_cast<int>(i));
79 return g;
80}
81
82// Count total nodes across all SCCs
84 size_t count = 0;
85 for (const auto& scc : sccs)
86 count += scc.size();
87 return count;
88}
89
90// ========== TEST 1: Empty Graph ==========
92 GT g;
93
95
96 ASSERT_EQ(sccs.size(), 0u);
97}
98
99// ========== TEST 2: Single Node ==========
101 GT g;
102 g.insert_node(0);
103
105
106 ASSERT_EQ(sccs.size(), 1u);
107 ASSERT_EQ(sccs.get_first().size(), 1u);
108}
109
110// ========== TEST 3: Single Node with Self-Loop ==========
112 GT g;
113 Node* n = g.insert_node(0);
114 g.insert_arc(n, n, 0);
115
117
118 ASSERT_EQ(sccs.size(), 1u);
119 ASSERT_EQ(sccs.get_first().size(), 1u);
120}
121
122// ========== TEST 4: Two Disconnected Nodes ==========
124 GT g;
125 g.insert_node(0);
126 g.insert_node(1);
127
129
130 ASSERT_EQ(sccs.size(), 2u);
131 size_t total = count_total_nodes(sccs);
132 ASSERT_EQ(total, 2u);
133}
134
135// ========== TEST 5: Simple Chain (No SCC) ==========
137 GT g = create_chain(5);
138
140
141 // Each node is its own SCC (no cycles)
142 ASSERT_EQ(sccs.size(), 5u);
143}
144
145// ========== TEST 6: Simple Cycle (Single SCC) ==========
147 GT g = create_cycle(5);
148
150
151 // All nodes form one SCC
152 ASSERT_EQ(sccs.size(), 1u);
153 ASSERT_EQ(sccs.get_first().size(), 5u);
154}
155
156// ========== TEST 7: Two Separate Cycles ==========
158 GT g;
159
160 // Cycle 1: 0 -> 1 -> 2 -> 0
161 Node* n0 = g.insert_node(0);
162 Node* n1 = g.insert_node(1);
163 Node* n2 = g.insert_node(2);
164 g.insert_arc(n0, n1, 0);
165 g.insert_arc(n1, n2, 1);
166 g.insert_arc(n2, n0, 2);
167
168 // Cycle 2: 3 -> 4 -> 3
169 Node* n3 = g.insert_node(3);
170 Node* n4 = g.insert_node(4);
171 g.insert_arc(n3, n4, 3);
172 g.insert_arc(n4, n3, 4);
173
175
176 ASSERT_EQ(sccs.size(), 2u);
177 size_t total = count_total_nodes(sccs);
178 ASSERT_EQ(total, 5u);
179}
180
181// ========== TEST 8: Two Cycles Connected by One Arc ==========
183 GT g;
184
185 // Cycle 1: 0 -> 1 -> 0
186 Node* n0 = g.insert_node(0);
187 Node* n1 = g.insert_node(1);
188 g.insert_arc(n0, n1, 0);
189 g.insert_arc(n1, n0, 1);
190
191 // Cycle 2: 2 -> 3 -> 2
192 Node* n2 = g.insert_node(2);
193 Node* n3 = g.insert_node(3);
194 g.insert_arc(n2, n3, 2);
195 g.insert_arc(n3, n2, 3);
196
197 // Cross arc: 1 -> 2 (makes two SCCs)
198 g.insert_arc(n1, n2, 4);
199
201
202 // Still 2 SCCs because there's no path from cycle 2 back to cycle 1
203 ASSERT_EQ(sccs.size(), 2u);
204}
205
206// ========== TEST 9: Two Cycles Connected Bidirectionally ==========
208 GT g;
209
210 // Cycle 1: 0 -> 1 -> 0
211 Node* n0 = g.insert_node(0);
212 Node* n1 = g.insert_node(1);
213 g.insert_arc(n0, n1, 0);
214 g.insert_arc(n1, n0, 1);
215
216 // Cycle 2: 2 -> 3 -> 2
217 Node* n2 = g.insert_node(2);
218 Node* n3 = g.insert_node(3);
219 g.insert_arc(n2, n3, 2);
220 g.insert_arc(n3, n2, 3);
221
222 // Cross arcs in both directions
223 g.insert_arc(n1, n2, 4);
224 g.insert_arc(n3, n0, 5);
225
227
228 // Now all 4 nodes are in one SCC
229 ASSERT_EQ(sccs.size(), 1u);
230 ASSERT_EQ(sccs.get_first().size(), 4u);
231}
232
233// ========== TEST 10: Classic Example from CLRS ==========
235 // This is the classic example from CLRS (Cormen et al.)
236 // Graph with 8 nodes and specific SCCs
237 GT g;
238
239 Node* a = g.insert_node(0); // a
240 Node* b = g.insert_node(1); // b
241 Node* c = g.insert_node(2); // c
242 Node* d = g.insert_node(3); // d
243 Node* e = g.insert_node(4); // e
244 Node* f = g.insert_node(5); // f
245 Node* h = g.insert_node(6); // h (renamed from g to avoid conflict)
246 Node* i = g.insert_node(7); // i (renamed from h)
247
248 // Arcs
249 g.insert_arc(a, b, 0);
250 g.insert_arc(b, c, 1);
251 g.insert_arc(c, a, 2); // SCC 1: {a, b, c}
252
253 g.insert_arc(b, e, 3);
254 g.insert_arc(c, d, 4);
255
256 g.insert_arc(d, e, 5);
257 g.insert_arc(e, f, 6);
258 g.insert_arc(f, d, 7); // SCC 2: {d, e, f}
259
260 g.insert_arc(f, h, 8);
261 g.insert_arc(h, i, 9);
262 g.insert_arc(i, h, 10); // SCC 3: {h, i}
263
265
266 // Should have 3 SCCs
267 ASSERT_EQ(sccs.size(), 3u);
269}
270
271// ========== TEST 11: Subgraph Version ==========
273 GT g;
274
275 // Create two SCCs
276 Node* n0 = g.insert_node(0);
277 Node* n1 = g.insert_node(1);
278 Node* n2 = g.insert_node(2);
279 Node* n3 = g.insert_node(3);
280
281 // SCC 1: 0 <-> 1
282 g.insert_arc(n0, n1, 0);
283 g.insert_arc(n1, n0, 1);
284
285 // SCC 2: 2 <-> 3
286 g.insert_arc(n2, n3, 2);
287 g.insert_arc(n3, n2, 3);
288
289 // Cross arc: 1 -> 2
290 Arc* cross = g.insert_arc(n1, n2, 4);
291
293 DynList<Arc*> arc_list;
295
296 ASSERT_EQ(blk_list.size(), 2u);
297 ASSERT_EQ(arc_list.size(), 1u);
298 ASSERT_EQ(arc_list.get_first(), cross);
299
300 // Verify each subgraph
301 for (auto& blk : blk_list)
302 {
303 ASSERT_EQ(blk.get_num_nodes(), 2u);
304 ASSERT_EQ(blk.get_num_arcs(), 2u);
305 }
306}
307
308// ========== TEST 12: Cross Arc List Correctness ==========
310 GT g;
311
312 Node* n0 = g.insert_node(0);
313 Node* n1 = g.insert_node(1);
314 Node* n2 = g.insert_node(2);
315
316 // SCC 1: {0}
317 // SCC 2: {1}
318 // SCC 3: {2}
319
320 Arc* a01 = g.insert_arc(n0, n1, 0);
321 Arc* a12 = g.insert_arc(n1, n2, 1);
322 Arc* a02 = g.insert_arc(n0, n2, 2);
323
325 DynList<Arc*> arc_list;
327
328 ASSERT_EQ(blk_list.size(), 3u);
329 ASSERT_EQ(arc_list.size(), 3u);
330
331 // All arcs should be cross arcs
332 bool found_a01 = false, found_a12 = false, found_a02 = false;
333 for (auto a : arc_list) {
334 if (a == a01) found_a01 = true;
335 if (a == a12) found_a12 = true;
336 if (a == a02) found_a02 = true;
337 }
338
342}
343
344// ========== TEST 13: is_strongly_connected True Case ==========
350
351// ========== TEST 14: is_strongly_connected False Case ==========
357
358// ========== TEST 15: is_strongly_connected Empty Graph ==========
364
365// ========== TEST 16: is_strongly_connected Single Node ==========
372
373// ========== TEST 17: kosaraju_scc_count ==========
375 GT g;
376
377 // Create 4 separate SCCs
378 Node* n0 = g.insert_node(0);
379 Node* n1 = g.insert_node(1);
380 Node* n2 = g.insert_node(2);
381 Node* n3 = g.insert_node(3);
382
383 g.insert_arc(n0, n1, 0);
384 g.insert_arc(n2, n3, 1);
385
387}
388
389// ========== TEST 18: Functor Interface ==========
391 GT g = create_cycle(3);
392
394
395 // Test list version
396 auto sccs = functor(g);
397 ASSERT_EQ(sccs.size(), 1u);
398 ASSERT_EQ(sccs.get_first().size(), 3u);
399
400 // Test subgraph version
402 DynList<Arc*> arc_list;
403 functor(g, blk_list, arc_list);
404 ASSERT_EQ(blk_list.size(), 1u);
405 ASSERT_EQ(arc_list.size(), 0u);
406}
407
408// ========== TEST 19: Large Chain ==========
410 GT g = create_chain(100);
411
413
414 ASSERT_EQ(sccs.size(), 100u);
415}
416
417// ========== TEST 20: Large Cycle ==========
419 GT g = create_cycle(100);
420
422
423 ASSERT_EQ(sccs.size(), 1u);
424 ASSERT_EQ(sccs.get_first().size(), 100u);
425}
426
427// ========== TEST 21: Node Mapping Correctness ==========
429 GT g;
430
431 Node* n0 = g.insert_node(0);
432 Node* n1 = g.insert_node(1);
433 g.insert_arc(n0, n1, 0);
434 g.insert_arc(n1, n0, 1);
435
437 DynList<Arc*> arc_list;
439
440 ASSERT_EQ(blk_list.size(), 1u);
441
442 // Verify mapping
444 for (typename GT::Node_Iterator it(blk); it.has_curr(); it.next())
445 {
446 auto blk_node = it.get_curr();
448 ASSERT_NE(orig_node, nullptr);
449 ASSERT_EQ(blk_node->get_info(), orig_node->get_info());
450 }
451}
452
453// ========== TEST 22: Arc Mapping Correctness ==========
455 GT g;
456
457 Node* n0 = g.insert_node(0);
458 Node* n1 = g.insert_node(1);
459 Arc* a01 = g.insert_arc(n0, n1, 10);
460 Arc* a10 = g.insert_arc(n1, n0, 20);
461
463 DynList<Arc*> arc_list;
465
467
468 // Verify arc count matches
469 ASSERT_EQ(blk.get_num_arcs(), 2u);
470
471 // Verify original arcs are mapped to block arcs
472 auto blk_a01 = mapped_arc<GT>(a01);
473 auto blk_a10 = mapped_arc<GT>(a10);
474 ASSERT_NE(blk_a01, nullptr);
475 ASSERT_NE(blk_a10, nullptr);
476
477 // Verify the block arcs are different
479}
480
481// ========== TEST 23: Diamond Graph ==========
483 /*
484 * 0
485 * / \
486 * 1 2
487 * \ /
488 * 3
489 */
490 GT g;
491
492 Node* n0 = g.insert_node(0);
493 Node* n1 = g.insert_node(1);
494 Node* n2 = g.insert_node(2);
495 Node* n3 = g.insert_node(3);
496
497 g.insert_arc(n0, n1, 0);
498 g.insert_arc(n0, n2, 1);
499 g.insert_arc(n1, n3, 2);
500 g.insert_arc(n2, n3, 3);
501
503
504 // No cycles, each node is its own SCC
505 ASSERT_EQ(sccs.size(), 4u);
506}
507
508// ========== TEST 24: Complete Digraph (K4) ==========
510 GT g;
511
512 std::vector<Node*> nodes;
513 for (int i = 0; i < 4; ++i)
514 nodes.push_back(g.insert_node(i));
515
516 // Add all directed edges
517 for (int i = 0; i < 4; ++i)
518 for (int j = 0; j < 4; ++j)
519 if (i != j)
520 g.insert_arc(nodes[i], nodes[j], i * 10 + j);
521
523
524 // Complete digraph is strongly connected
525 ASSERT_EQ(sccs.size(), 1u);
526 ASSERT_EQ(sccs.get_first().size(), 4u);
527}
528
529// ========== TEST 25: Multiple Self-Loops ==========
531 GT g;
532
533 Node* n0 = g.insert_node(0);
534 Node* n1 = g.insert_node(1);
535 Node* n2 = g.insert_node(2);
536
537 g.insert_arc(n0, n0, 0);
538 g.insert_arc(n1, n1, 1);
539 g.insert_arc(n2, n2, 2);
540
542
543 // Each node is its own SCC (self-loops don't connect nodes)
544 ASSERT_EQ(sccs.size(), 3u);
545}
546
547// ========== TEST 26: Tree Structure ==========
549 /*
550 * 0
551 * / \
552 * 1 2
553 * / \
554 * 3 4
555 */
556 GT g;
557
558 Node* n0 = g.insert_node(0);
559 Node* n1 = g.insert_node(1);
560 Node* n2 = g.insert_node(2);
561 Node* n3 = g.insert_node(3);
562 Node* n4 = g.insert_node(4);
563
564 g.insert_arc(n0, n1, 0);
565 g.insert_arc(n0, n2, 1);
566 g.insert_arc(n1, n3, 2);
567 g.insert_arc(n1, n4, 3);
568
570
571 // Tree has no cycles, each node is its own SCC
572 ASSERT_EQ(sccs.size(), 5u);
573}
574
575// ========== TEST 27: Comparison with Tarjan ==========
577 GT g;
578
579 // Create a moderately complex graph
580 Node* n0 = g.insert_node(0);
581 Node* n1 = g.insert_node(1);
582 Node* n2 = g.insert_node(2);
583 Node* n3 = g.insert_node(3);
584 Node* n4 = g.insert_node(4);
585
586 g.insert_arc(n0, n1, 0);
587 g.insert_arc(n1, n2, 1);
588 g.insert_arc(n2, n0, 2);
589 g.insert_arc(n2, n3, 3);
590 g.insert_arc(n3, n4, 4);
591 g.insert_arc(n4, n3, 5);
592
594
595 // Use Tarjan for comparison
597 Tarjan_Connected_Components<GT>().connected_components(g, tarjan_sccs);
598
599 // Both should find the same number of SCCs
601}
602
603// ========== TEST 28: Stress Test ==========
605 GT g;
606 const size_t N = 500;
607
608 std::vector<Node*> nodes;
609 for (size_t i = 0; i < N; ++i)
610 nodes.push_back(g.insert_node(static_cast<int>(i)));
611
612 // Create a large cycle
613 for (size_t i = 0; i < N; ++i)
614 g.insert_arc(nodes[i], nodes[(i + 1) % N], static_cast<int>(i));
615
617
618 ASSERT_EQ(sccs.size(), 1u);
619 ASSERT_EQ(sccs.get_first().size(), N);
620}
621
622// ========== TEST 29: Graph with Isolated Nodes and SCCs ==========
624 GT g;
625
626 // Isolated nodes
627 g.insert_node(0);
628 g.insert_node(1);
629
630 // SCC: 2 <-> 3
631 Node* n2 = g.insert_node(2);
632 Node* n3 = g.insert_node(3);
633 g.insert_arc(n2, n3, 0);
634 g.insert_arc(n3, n2, 1);
635
637
638 ASSERT_EQ(sccs.size(), 3u);
639}
640
641// ========== TEST 30: Parallel Arcs ==========
643 GT g;
644
645 Node* n0 = g.insert_node(0);
646 Node* n1 = g.insert_node(1);
647
648 // Multiple arcs in same direction
649 g.insert_arc(n0, n1, 0);
650 g.insert_arc(n0, n1, 1);
651 g.insert_arc(n1, n0, 2);
652
654
655 // Both nodes in same SCC (there's a cycle)
656 ASSERT_EQ(sccs.size(), 1u);
657 ASSERT_EQ(sccs.get_first().size(), 2u);
658}
659
660// ========== TEST 31: Dense Graph Performance ==========
662 GT g;
663 const size_t N = 100;
664 std::vector<Node*> nodes;
665
666 for (size_t i = 0; i < N; ++i)
667 nodes.push_back(g.insert_node(static_cast<int>(i)));
668
669 // Create complete digraph (every node connects to every other node)
670 for (size_t i = 0; i < N; ++i)
671 for (size_t j = 0; j < N; ++j)
672 if (i != j)
673 g.insert_arc(nodes[i], nodes[j], static_cast<int>(i * N + j));
674
676
677 // Complete digraph is strongly connected (one SCC)
678 ASSERT_EQ(sccs.size(), 1u);
679 ASSERT_EQ(sccs.get_first().size(), N);
680}
681
682// ========== TEST 32: Multiple Self-Loops on Same Node ==========
684 GT g;
685 Node* n = g.insert_node(0);
686
687 // Add multiple self-loops on the same node
688 g.insert_arc(n, n, 0);
689 g.insert_arc(n, n, 1);
690 g.insert_arc(n, n, 2);
691
693
694 ASSERT_EQ(sccs.size(), 1u);
695 ASSERT_EQ(sccs.get_first().size(), 1u);
696}
697
698// ========== TEST 33: Nodes Without Any Arcs ==========
700 GT g;
701 const size_t N = 10;
702
703 // Insert nodes without any arcs
704 for (size_t i = 0; i < N; ++i)
705 g.insert_node(static_cast<int>(i));
706
708
709 // Each node is its own SCC
710 ASSERT_EQ(sccs.size(), N);
711 for (auto& scc : sccs)
712 ASSERT_EQ(scc.size(), 1u);
713}
714
715// ========== TEST 34: Fully Bidirectional Graph ==========
717 GT g;
718
719 std::vector<Node*> nodes;
720 for (int i = 0; i < 5; ++i)
721 nodes.push_back(g.insert_node(i));
722
723 // Create a path with bidirectional edges (every edge has its reverse)
724 for (size_t i = 0; i < 4; ++i) {
725 g.insert_arc(nodes[i], nodes[i+1], static_cast<int>(i));
726 g.insert_arc(nodes[i+1], nodes[i], static_cast<int>(i+10));
727 }
728
730
731 // All nodes should be in one SCC (can reach each other)
732 ASSERT_EQ(sccs.size(), 1u);
733 ASSERT_EQ(sccs.get_first().size(), 5u);
734}
735
736// ========== TEST 35: Transposed Cycle Correctness ==========
738 // Verify that transposition doesn't create spurious cycles
739 // Graph: 0 → 1 → 2 → 3 (no cycles in original)
740 // Transposed: 0 ← 1 ← 2 ← 3 (still no cycles)
741 GT g = create_chain(4);
742
744
745 // Should still be 4 SCCs (each node is its own SCC)
746 ASSERT_EQ(sccs.size(), 4u);
747
748 // Verify each SCC has exactly one node
749 for (auto& scc : sccs)
750 ASSERT_EQ(scc.size(), 1u);
751}
752
753// ========== GoogleTest main ==========
754int main(int argc, char **argv)
755{
756 ::testing::InitGoogleTest(&argc, argv);
757 return RUN_ALL_TESTS();
758}
Tarjan's algorithm for strongly connected components.
int main()
WeightedDigraph::Node Node
WeightedDigraph::Arc Arc
long double h
Definition btreepic.C:154
Generic directed graph (digraph) wrapper template.
Definition graph-dry.H:3848
typename BaseGraph::Arc Arc
Definition graph-dry.H:3852
typename BaseGraph::Node Node
Definition graph-dry.H:3851
Dynamic singly linked list with functional programming support.
Definition htlist.H:1423
T & get_first() const
Return the first item of the list.
Definition htlist.H:1675
size_t size() const noexcept
Count the number of elements of the list.
Definition htlist.H:1319
virtual Node * insert_node(Node *node) noexcept
Insertion of a node already allocated.
Definition tpl_graph.H:524
Arc * insert_arc(Node *src_node, Node *tgt_node, void *a)
Definition tpl_graph.H:604
#define TEST(name)
#define N
Definition fib.C:294
DynArray< Graph::Node * > nodes
Definition graphpic.C:406
void kosaraju_connected_components(const GT &g, DynList< GT > &blk_list, DynList< typename GT::Arc * > &arc_list)
Compute strongly connected components using Kosaraju's algorithm.
Definition kosaraju.H:210
size_t kosaraju_scc_count(const GT &g)
Count the number of strongly connected components.
Definition kosaraju.H:381
bool is_strongly_connected(const GT &g)
Check if a directed graph is strongly connected.
Definition kosaraju.H:402
Kosaraju's algorithm for finding strongly connected components.
Main namespace for Aleph-w library functions.
Definition ah-arena.H:89
DynList< T > maps(const C &c, Op op)
Classic map operation.
Itor::difference_type count(const Itor &beg, const Itor &end, const T &value)
Count elements equal to a value.
Definition ahAlgo.H:127
Arc of graph implemented with double-linked adjacency lists.
Definition tpl_graph.H:222
Functor wrapper for Kosaraju's algorithm.
Definition kosaraju.H:343
size_t count_total_nodes(const DynList< DynList< Node * > > &sccs)
GT create_cycle(size_t n)
GT create_chain(size_t n)
Generic graph and digraph implementations.