Aleph-w 3.0
A C++ Library for Data Structures and Algorithms
Loading...
Searching...
No Matches
eulerian_test.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
38#include <gtest/gtest.h>
39#include <eulerian.H>
40#include <tpl_graph.H>
41
42using namespace Aleph;
43
44// =============================================================================
45// Test Fixtures
46// =============================================================================
47
48class EulerianUndirectedTest : public ::testing::Test
49{
50protected:
53 using Arc = Graph::Arc;
54
56
57 Node* add_node(int val)
58 {
59 return g.insert_node(val);
60 }
61
62 Arc* add_edge(Node* n1, Node* n2)
63 {
64 return g.insert_arc(n1, n2, 1);
65 }
66};
67
68class EulerianDigraphTest : public ::testing::Test
69{
70protected:
74
76
77 Node* add_node(int val)
78 {
79 return g.insert_node(val);
80 }
81
82 Arc* add_arc(Node* src, Node* tgt)
83 {
84 return g.insert_arc(src, tgt, 1);
85 }
86};
87
88// =============================================================================
89// Undirected Graph Tests
90// =============================================================================
91
93{
94 // Empty graph is trivially Eulerian (no edges to traverse)
96 EXPECT_TRUE(test(g));
97}
98
100{
101 add_node(1);
102
104 EXPECT_TRUE(test(g)); // Degree 0 is even
105}
106
108{
109 /*
110 * Triangle: all vertices have degree 2 (even)
111 * 1
112 * / \
113 * 2---3
114 */
115 auto n1 = add_node(1);
116 auto n2 = add_node(2);
117 auto n3 = add_node(3);
118
119 add_edge(n1, n2);
120 add_edge(n2, n3);
121 add_edge(n3, n1);
122
124 EXPECT_TRUE(test(g));
125}
126
128{
129 // Square: all vertices have degree 2 (even)
130 // 1---2
131 // | |
132 // 4---3
133 auto n1 = add_node(1);
134 auto n2 = add_node(2);
135 auto n3 = add_node(3);
136 auto n4 = add_node(4);
137
138 add_edge(n1, n2);
139 add_edge(n2, n3);
140 add_edge(n3, n4);
141 add_edge(n4, n1);
142
144 EXPECT_TRUE(test(g));
145}
146
148{
149 // Square with one diagonal: degrees are 3,3,2,2 - NOT Eulerian
150 // 1---2
151 // |\ /|
152 // 4---3
153 auto n1 = add_node(1);
154 auto n2 = add_node(2);
155 auto n3 = add_node(3);
156 auto n4 = add_node(4);
157
158 add_edge(n1, n2);
159 add_edge(n2, n3);
160 add_edge(n3, n4);
161 add_edge(n4, n1);
162 add_edge(n1, n3); // Diagonal
163
165 EXPECT_FALSE(test(g)); // n1 and n3 have odd degree
166}
167
169{
170 // Square with both diagonals: all vertices have degree 4 (even)
171 // 1---2
172 // |\ /|
173 // |/ \|
174 // 4---3
175 auto n1 = add_node(1);
176 auto n2 = add_node(2);
177 auto n3 = add_node(3);
178 auto n4 = add_node(4);
179
180 add_edge(n1, n2);
181 add_edge(n2, n3);
182 add_edge(n3, n4);
183 add_edge(n4, n1);
184 add_edge(n1, n3); // Diagonal 1
185 add_edge(n2, n4); // Diagonal 2
186
187 // Debug: verify degrees
188 EXPECT_EQ(g.get_num_arcs(n1), 3) << "n1 should have degree 3";
189 EXPECT_EQ(g.get_num_arcs(n2), 3) << "n2 should have degree 3";
190 EXPECT_EQ(g.get_num_arcs(n3), 3) << "n3 should have degree 3";
191 EXPECT_EQ(g.get_num_arcs(n4), 3) << "n4 should have degree 3";
192
194 // With degree 3 (odd), this should NOT be Eulerian
195 EXPECT_FALSE(test(g)); // All degrees are 3 (odd)
196}
197
199{
200 // Path 1-2-3: vertices 1 and 3 have degree 1 (odd)
201 // Semi-Eulerian (has Eulerian path) but NOT Eulerian (no cycle)
202 auto n1 = add_node(1);
203 auto n2 = add_node(2);
204 auto n3 = add_node(3);
205
206 add_edge(n1, n2);
207 add_edge(n2, n3);
208
210 EXPECT_FALSE(test(g));
211}
212
214{
215 // Star graph: center has degree 4, leaves have degree 1
216 // 2
217 // |
218 // 3---1---4
219 // |
220 // 5
221 auto center = add_node(1);
222 auto n2 = add_node(2);
223 auto n3 = add_node(3);
224 auto n4 = add_node(4);
225 auto n5 = add_node(5);
226
227 add_edge(center, n2);
228 add_edge(center, n3);
229 add_edge(center, n4);
230 add_edge(center, n5);
231
233 EXPECT_FALSE(test(g)); // Leaves have odd degree
234}
235
237{
238 // Classic Königsberg bridges problem - NOT Eulerian
239 // All 4 land masses have odd degree
240 auto a = add_node(1); // North bank
241 auto b = add_node(2); // South bank
242 auto c = add_node(3); // Island 1
243 auto d = add_node(4); // Island 2
244
245 // 7 bridges
246 add_edge(a, c);
247 add_edge(a, c); // Multi-edge not supported, but for degree count
248 add_edge(a, d);
249 add_edge(b, c);
250 add_edge(b, c);
251 add_edge(b, d);
252 add_edge(c, d);
253
255 // Degrees: a=3, b=3, c=5, d=3 - all odd
256 EXPECT_FALSE(test(g));
257}
258
260{
261 // K4: each vertex has degree 3 (odd) - NOT Eulerian
262 auto n1 = add_node(1);
263 auto n2 = add_node(2);
264 auto n3 = add_node(3);
265 auto n4 = add_node(4);
266
267 add_edge(n1, n2);
268 add_edge(n1, n3);
269 add_edge(n1, n4);
270 add_edge(n2, n3);
271 add_edge(n2, n4);
272 add_edge(n3, n4);
273
275 EXPECT_FALSE(test(g));
276}
277
279{
280 // K5: each vertex has degree 4 (even) - IS Eulerian
281 auto n1 = add_node(1);
282 auto n2 = add_node(2);
283 auto n3 = add_node(3);
284 auto n4 = add_node(4);
285 auto n5 = add_node(5);
286
287 add_edge(n1, n2); add_edge(n1, n3); add_edge(n1, n4); add_edge(n1, n5);
288 add_edge(n2, n3); add_edge(n2, n4); add_edge(n2, n5);
289 add_edge(n3, n4); add_edge(n3, n5);
290 add_edge(n4, n5);
291
293 EXPECT_TRUE(test(g));
294}
295
297{
298 /*
299 * Two triangles sharing an edge (bowtie)
300 * 1
301 * /|\
302 * 2-+-3
303 * \|/
304 * 4
305 */
306 auto n1 = add_node(1);
307 auto n2 = add_node(2);
308 auto n3 = add_node(3);
309 auto n4 = add_node(4);
310
311 // Top triangle
312 add_edge(n1, n2);
313 add_edge(n1, n3);
314 add_edge(n2, n3);
315 // Bottom triangle shares edge 2-3
316 add_edge(n2, n4);
317 add_edge(n3, n4);
318
320 // Degrees: n1=2, n2=3, n3=3, n4=2
321 EXPECT_FALSE(test(g));
322}
323
324// =============================================================================
325// Directed Graph (Digraph) Tests
326// =============================================================================
327
333
335{
336 add_node(1);
337
339 EXPECT_TRUE(test(g)); // in-deg = out-deg = 0
340}
341
343{
344 // Directed cycle: 1→2→3→1
345 // Each vertex: in-degree = out-degree = 1
346 auto n1 = add_node(1);
347 auto n2 = add_node(2);
348 auto n3 = add_node(3);
349
350 add_arc(n1, n2);
351 add_arc(n2, n3);
352 add_arc(n3, n1);
353
355 EXPECT_TRUE(test(g));
356}
357
359{
360 // Path 1→2→3: NOT Eulerian
361 // n1: in=0, out=1; n3: in=1, out=0
362 auto n1 = add_node(1);
363 auto n2 = add_node(2);
364 auto n3 = add_node(3);
365
366 add_arc(n1, n2);
367 add_arc(n2, n3);
368
370 EXPECT_FALSE(test(g));
371}
372
374{
375 // Directed square cycle: 1→2→3→4→1
376 auto n1 = add_node(1);
377 auto n2 = add_node(2);
378 auto n3 = add_node(3);
379 auto n4 = add_node(4);
380
381 add_arc(n1, n2);
382 add_arc(n2, n3);
383 add_arc(n3, n4);
384 add_arc(n4, n1);
385
387 EXPECT_TRUE(test(g));
388}
389
391{
392 // Two directed triangles
393 auto n1 = add_node(1);
394 auto n2 = add_node(2);
395 auto n3 = add_node(3);
396 auto n4 = add_node(4);
397 auto n5 = add_node(5);
398 auto n6 = add_node(6);
399
400 // First cycle
401 add_arc(n1, n2);
402 add_arc(n2, n3);
403 add_arc(n3, n1);
404
405 // Second cycle (disconnected)
406 add_arc(n4, n5);
407 add_arc(n5, n6);
408 add_arc(n6, n4);
409
411 // Two disconnected cycles - NOT Eulerian (can't traverse all in one path)
412 EXPECT_FALSE(test(g));
413 // But degree conditions are satisfied
414 EXPECT_TRUE(test.test_degree_only(g));
415}
416
418{
419 // Node with more outgoing than incoming
420 // 1 ← 2
421 // ↓ ↓
422 // 3 ← 4
423 auto n1 = add_node(1);
424 auto n2 = add_node(2);
425 auto n3 = add_node(3);
426 auto n4 = add_node(4);
427
428 add_arc(n2, n1); // n1: in=1
429 add_arc(n1, n3); // n1: out=1
430 add_arc(n2, n4); // n2: out=2
431 add_arc(n4, n3); // n4: in=1, out=1
432
434 // n2: in=0, out=2 → NOT balanced
435 EXPECT_FALSE(test(g));
436}
437
439{
440 // Two cycles sharing a node (figure-8)
441 // 2→1→3→2 and 4→1→5→4
442 // Node 1: in=2, out=2
443 auto n1 = add_node(1);
444 auto n2 = add_node(2);
445 auto n3 = add_node(3);
446 auto n4 = add_node(4);
447 auto n5 = add_node(5);
448
449 // First cycle
450 add_arc(n2, n1);
451 add_arc(n1, n3);
452 add_arc(n3, n2);
453
454 // Second cycle through n1
455 add_arc(n4, n1);
456 add_arc(n1, n5);
457 add_arc(n5, n4);
458
460 // n1: in=2, out=2 ✓
461 // others: in=1, out=1 ✓
462 EXPECT_TRUE(test(g));
463}
464
466{
467 // Complete directed graph K3: every pair connected both ways
468 auto n1 = add_node(1);
469 auto n2 = add_node(2);
470 auto n3 = add_node(3);
471
472 add_arc(n1, n2); add_arc(n2, n1);
473 add_arc(n1, n3); add_arc(n3, n1);
474 add_arc(n2, n3); add_arc(n3, n2);
475
477 // Each vertex: in=2, out=2
478 EXPECT_TRUE(test(g));
479}
480
481// =============================================================================
482// Edge Cases
483// =============================================================================
484
485// Note: Self-loop behavior is implementation-defined.
486// In Aleph, self-loops may not count as degree 2.
487// TEST_F(EulerianUndirectedTest, SelfLoop) - DISABLED: implementation-specific
488
490{
491 // Disconnected graph with Eulerian components
492 auto n1 = add_node(1);
493 auto n2 = add_node(2);
494 auto n3 = add_node(3);
495
496 auto n4 = add_node(4);
497 auto n5 = add_node(5);
498 auto n6 = add_node(6);
499
500 // Triangle 1
501 add_edge(n1, n2);
502 add_edge(n2, n3);
503 add_edge(n3, n1);
504
505 // Triangle 2 (disconnected)
506 add_edge(n4, n5);
507 add_edge(n5, n6);
508 add_edge(n6, n4);
509
511 // All degrees even, but not connected - NOT Eulerian
512 EXPECT_FALSE(test(g));
513 // But degree conditions alone are satisfied
514 EXPECT_TRUE(test.test_degree_only(g));
515}
516
517// =============================================================================
518// Tests for Eulerian_Type enum and compute() method
519// =============================================================================
520
522{
523 // Triangle: all degrees even (2) - has Eulerian cycle
524 auto n1 = add_node(1);
525 auto n2 = add_node(2);
526 auto n3 = add_node(3);
527 add_edge(n1, n2);
528 add_edge(n2, n3);
529 add_edge(n3, n1);
530
532 EXPECT_EQ(test.compute(g), Eulerian_Type::Cycle);
533 EXPECT_TRUE(test.has_eulerian_path(g));
534}
535
537{
538 // Path 1-2-3: nodes 1 and 3 have odd degree (1), node 2 even (2)
539 // Has Eulerian path but not cycle
540 auto n1 = add_node(1);
541 auto n2 = add_node(2);
542 auto n3 = add_node(3);
543 add_edge(n1, n2);
544 add_edge(n2, n3);
545
547 EXPECT_EQ(test.compute(g), Eulerian_Type::Path);
548 EXPECT_TRUE(test.has_eulerian_path(g));
549 EXPECT_FALSE(test(g)); // No cycle
550}
551
553{
554 // Star with 4 leaves: center has degree 4, leaves have degree 1 (odd)
555 // 4 vertices with odd degree - no Eulerian path or cycle
556 auto center = add_node(0);
557 auto n1 = add_node(1);
558 auto n2 = add_node(2);
559 auto n3 = add_node(3);
560 auto n4 = add_node(4);
561 add_edge(center, n1);
562 add_edge(center, n2);
563 add_edge(center, n3);
564 add_edge(center, n4);
565
567 EXPECT_EQ(test.compute(g), Eulerian_Type::None);
568 EXPECT_FALSE(test.has_eulerian_path(g));
569}
570
572{
573 // Directed cycle 1→2→3→1
574 auto n1 = add_node(1);
575 auto n2 = add_node(2);
576 auto n3 = add_node(3);
577 add_arc(n1, n2);
578 add_arc(n2, n3);
579 add_arc(n3, n1);
580
582 EXPECT_EQ(test.compute(g), Eulerian_Type::Cycle);
583}
584
586{
587 // Directed path 1→2→3: n1 has out-in=1, n3 has in-out=1
588 auto n1 = add_node(1);
589 auto n2 = add_node(2);
590 auto n3 = add_node(3);
591 add_arc(n1, n2);
592 add_arc(n2, n3);
593
595 EXPECT_EQ(test.compute(g), Eulerian_Type::Path);
596 EXPECT_TRUE(test.has_eulerian_path(g));
597}
598
599// =============================================================================
600// Tests for Hierholzer's Algorithm (Find_Eulerian_Path)
601// =============================================================================
602
604{
605 // Triangle: should find cycle with 3 edges
606 auto n1 = add_node(1);
607 auto n2 = add_node(2);
608 auto n3 = add_node(3);
609 auto e1 = add_edge(n1, n2);
610 auto e2 = add_edge(n2, n3);
611 auto e3 = add_edge(n3, n1);
612
614 auto result = finder(g);
615
616 EXPECT_EQ(result.type, Eulerian_Type::Cycle);
617 EXPECT_EQ(result.path.size(), 3);
618
619 // All edges should be in the path exactly once
620 std::set<Graph::Arc*> edges_in_path;
621 for (auto arc : result.path)
623
625 EXPECT_TRUE(edges_in_path.count(e1) > 0);
626 EXPECT_TRUE(edges_in_path.count(e2) > 0);
627 EXPECT_TRUE(edges_in_path.count(e3) > 0);
628}
629
631{
632 // Path 1-2-3: should find path with 2 edges
633 auto n1 = add_node(1);
634 auto n2 = add_node(2);
635 auto n3 = add_node(3);
636 add_edge(n1, n2);
637 add_edge(n2, n3);
638
640 auto result = finder(g);
641
642 EXPECT_EQ(result.type, Eulerian_Type::Path);
643 EXPECT_EQ(result.path.size(), 2);
644}
645
647{
648 auto n1 = add_node(1);
649 auto n2 = add_node(2);
650 auto n3 = add_node(3);
651 auto n4 = add_node(4);
652
653 // Complete graph K4: all degrees = 3 (odd) -> NOT Eulerian
654 add_edge(n1, n2);
655 add_edge(n1, n3);
656 add_edge(n1, n4);
657 add_edge(n2, n3);
658 add_edge(n2, n4);
659 add_edge(n3, n4);
660
662 auto result = finder(g);
663
664 EXPECT_EQ(result.type, Eulerian_Type::None);
665 EXPECT_TRUE(result.path.is_empty());
666}
667
669{
670 // Two triangles sharing a vertex (bow-tie/figure-8 shape)
671 // Triangle 1: 1-2-3-1
672 // Triangle 2: 1-4-5-1 (shares vertex 1)
673 // Degrees: 1→4, 2→2, 3→2, 4→2, 5→2 (all even!)
674 auto n1 = add_node(1);
675 auto n2 = add_node(2);
676 auto n3 = add_node(3);
677 auto n4 = add_node(4);
678 auto n5 = add_node(5);
679
680 add_edge(n1, n2);
681 add_edge(n2, n3);
682 add_edge(n3, n1); // First triangle
683 add_edge(n1, n4);
684 add_edge(n4, n5);
685 add_edge(n5, n1); // Second triangle shares vertex n1
686
688 auto result = finder(g);
689
690 EXPECT_EQ(result.type, Eulerian_Type::Cycle);
691 EXPECT_EQ(result.path.size(), 6);
692}
693
695{
696 // Triangle: verify node sequence
697 auto n1 = add_node(1);
698 auto n2 = add_node(2);
699 auto n3 = add_node(3);
700 add_edge(n1, n2);
701 add_edge(n2, n3);
702 add_edge(n3, n1);
703
705 auto nodes = finder.find_node_sequence(g);
706
707 // Cycle should have 4 nodes (start == end)
708 EXPECT_EQ(nodes.size(), 4);
709 EXPECT_EQ(nodes.get_first(), nodes.get_last());
710}
711
713{
714 // Star graph: 4 odd-degree vertices, not Eulerian
715 auto center = add_node(0);
716 auto n1 = add_node(1);
717 auto n2 = add_node(2);
718 auto n3 = add_node(3);
719 auto n4 = add_node(4);
720 add_edge(center, n1);
721 add_edge(center, n2);
722 add_edge(center, n3);
723 add_edge(center, n4);
724
726 auto result = finder(g);
727
728 EXPECT_EQ(result.type, Eulerian_Type::None);
729 EXPECT_TRUE(result.path.is_empty());
730}
731
733{
734 // Directed cycle 1→2→3→1
735 auto n1 = add_node(1);
736 auto n2 = add_node(2);
737 auto n3 = add_node(3);
738 add_arc(n1, n2);
739 add_arc(n2, n3);
740 add_arc(n3, n1);
741
743 auto result = finder(g);
744
745 EXPECT_EQ(result.type, Eulerian_Type::Cycle);
746 EXPECT_EQ(result.path.size(), 3);
747}
748
750{
751 // Directed path 1→2→3
752 auto n1 = add_node(1);
753 auto n2 = add_node(2);
754 auto n3 = add_node(3);
755 add_arc(n1, n2);
756 add_arc(n2, n3);
757
759 auto result = finder(g);
760
761 EXPECT_EQ(result.type, Eulerian_Type::Path);
762 EXPECT_EQ(result.path.size(), 2);
763}
764
766{
767 // Figure-8: two cycles sharing a vertex
768 // 1→2→3→1 and 3→4→5→3
769 auto n1 = add_node(1);
770 auto n2 = add_node(2);
771 auto n3 = add_node(3);
772 auto n4 = add_node(4);
773 auto n5 = add_node(5);
774
775 add_arc(n1, n2);
776 add_arc(n2, n3);
777 add_arc(n3, n1);
778 add_arc(n3, n4);
779 add_arc(n4, n5);
780 add_arc(n5, n3);
781
783 auto result = finder(g);
784
785 EXPECT_EQ(result.type, Eulerian_Type::Cycle);
786 EXPECT_EQ(result.path.size(), 6);
787}
788
789// =============================================================================
790// Main
791// =============================================================================
792
793int main(int argc, char **argv)
794{
795 ::testing::InitGoogleTest(&argc, argv);
796 return RUN_ALL_TESTS();
797}
int main()
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
T & insert(const T &item)
Insert a new item by copy.
Definition htlist.H:1502
Finds and constructs an Eulerian path or cycle using Hierholzer's algorithm.
Definition eulerian.H:561
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
Graph_Arc< int > Arc
The node class type.
Definition tpl_graph.H:433
Arc * insert_arc(Node *src_node, Node *tgt_node, void *a)
Definition tpl_graph.H:604
Tests whether a graph or digraph is Eulerian.
Definition eulerian.H:171
Arc * add_arc(Node *src, Node *tgt)
Node * add_node(int val)
Node * add_node(int val)
Arc * add_edge(Node *n1, Node *n2)
Eulerian graph detection and path/cycle finding.
TEST_F(EulerianUndirectedTest, EmptyGraph)
void add_edge(GT &g, const string &src, const string &tgt, int weight=1)
DynArray< Graph::Node * > nodes
Definition graphpic.C:406
Main namespace for Aleph-w library functions.
Definition ah-arena.H:89
DynList< T > maps(const C &c, Op op)
Classic map operation.
Arc of graph implemented with double-linked adjacency lists.
Definition tpl_graph.H:222
TestDigraph::Arc * add_arc(TestDigraph &g, TestDigraph::Node *src, TestDigraph::Node *tgt, int value=0)
TestDigraph::Node * add_node(TestDigraph &g, int value)
void test(unsigned long n, gsl_rng *r)
Generic graph and digraph implementations.