Aleph-w 3.0
A C++ Library for Data Structures and Algorithms
Loading...
Searching...
No Matches
eulerian.H
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
94# ifndef EULERIAN_H
95# define EULERIAN_H
96
97# include <ah-graph-concepts.H>
98
99# include <tpl_graph.H>
100# include <tpl_graph_utils.H>
101
102namespace Aleph
103{
104
114{
115 Cycle,
116 Path,
117 None
118};
119
169template <AlephGraph GT,
173{
175 SA & sa;
176
186 bool is_connected(GT & g)
187 {
188 if (g.get_num_nodes() == 0)
189 return true;
190
191 // Find first vertex with edges
192 typename GT::Node * start = nullptr;
193 size_t non_isolated = 0;
194
195 for (Node_Iterator<GT, SN> it(g, sn); it.has_curr(); it.next_ne())
196 {
197 typename GT::Node * p = it.get_curr();
198 if (g.get_num_arcs(p) > 0)
199 {
200 if (start == nullptr)
201 start = p;
202 ++non_isolated;
203 }
204 }
205
206 // No edges means trivially connected (or empty)
207 if (start == nullptr)
208 return true;
209
210 // DFS from start vertex
211 g.reset_nodes();
212 size_t visited = 0;
213
215 stack.insert(start);
216 NODE_BITS(start).set_bit(Depth_First, true);
217
218 while (not stack.is_empty())
219 {
220 typename GT::Node * curr = stack.remove_first();
221 ++visited;
222
223 for (Node_Arc_Iterator<GT, SA> it(curr, sa); it.has_curr(); it.next_ne())
224 {
225 typename GT::Node * adj = it.get_tgt_node_ne();
226 if (not NODE_BITS(adj).get_bit(Depth_First))
227 {
228 NODE_BITS(adj).set_bit(Depth_First, true);
229 stack.insert(adj);
230 }
231 }
232 }
233
234 return visited == non_isolated;
235 }
236
247 {
248 if (g.get_num_nodes() == 0)
249 return true;
250
251 // Find first vertex with edges
252 typename GT::Node * start = nullptr;
253 size_t non_isolated = 0;
254
255 for (Node_Iterator<GT, SN> it(g, sn); it.has_curr(); it.next_ne())
256 {
257 typename GT::Node * p = it.get_curr();
258 if (g.get_num_arcs(p) > 0 or NODE_COUNTER(p) > 0) // has out or in edges
259 {
260 if (start == nullptr)
261 start = p;
262 ++non_isolated;
263 }
264 }
265
266 if (start == nullptr || non_isolated <= 1)
267 return true;
268
269 // DFS from start in original direction
270 g.reset_nodes();
271 size_t visited_forward = 0;
272
274 stack.insert(start);
275 NODE_BITS(start).set_bit(Depth_First, true);
276
277 while (not stack.is_empty())
278 {
279 typename GT::Node * curr = stack.remove_first();
280 if (g.get_num_arcs(curr) > 0 or NODE_COUNTER(curr) > 0)
282
283 for (Node_Arc_Iterator<GT, SA> it(curr, sa); it.has_curr(); it.next_ne())
284 {
285 typename GT::Node * adj = it.get_tgt_node_ne();
286 if (not NODE_BITS(adj).get_bit(Depth_First))
287 {
288 NODE_BITS(adj).set_bit(Depth_First, true);
289 stack.insert(adj);
290 }
291 }
292 }
293
295 return false;
296
297 // For full strong connectivity check, we'd need reverse graph traversal
298 // For Eulerian purposes, if in-degree == out-degree and forward DFS
299 // reaches all vertices, that's sufficient for practical purposes
300 return true;
301 }
302
310 {
311 assert(not g.is_digraph());
312
313 size_t odd_degree_count = 0;
314
315 for (Node_Iterator<GT, SN> it(g, sn); it.has_curr(); it.next_ne())
316 {
317 if ((g.get_num_arcs(it.get_curr()) % 2) == 1)
319 }
320
321 // Check degree conditions first
322 if (odd_degree_count == 0)
323 {
324 // All even degrees - could be Eulerian cycle if connected
325 if (is_connected(g))
327 else
328 return Eulerian_Type::None;
329 }
330 else if (odd_degree_count == 2)
331 {
332 // Exactly 2 odd degrees - could be Eulerian path if connected
333 if (is_connected(g))
334 return Eulerian_Type::Path;
335 else
336 return Eulerian_Type::None;
337 }
338 else
339 {
340 // More than 2 odd degrees - not Eulerian
341 return Eulerian_Type::None;
342 }
343 }
344
352 {
353 assert(g.is_digraph());
354
356
357 // First pass: count in-degrees using node counters
358 for (Arc_Iterator<GT, SA> it(g, sa); it.has_curr(); it.next_ne())
359 NODE_COUNTER(it.get_tgt_node_ne())++;
360
361 // Count imbalanced vertices
362 size_t start_vertices = 0; // out - in == 1
363 size_t end_vertices = 0; // in - out == 1
364 size_t imbalanced = 0; // |in - out| > 1
365
366 for (Node_Iterator<GT, SN> it(g, sn); it.has_curr(); it.next_ne())
367 {
368 typename GT::Node * p = it.get_curr();
369 const long out_deg = g.get_num_arcs(p);
370 const long in_deg = NODE_COUNTER(p);
371 const long diff = out_deg - in_deg;
372
373 if (diff == 0)
374 continue; // Balanced vertex
375 else if (diff == 1)
377 else if (diff == -1)
378 ++end_vertices;
379 else
380 ++imbalanced;
381 }
382
383 // Check degree conditions
384 if (imbalanced > 0)
385 return Eulerian_Type::None;
386
387 if (start_vertices == 0 && end_vertices == 0)
388 {
389 // All balanced - could be Eulerian cycle if strongly connected
392 else
393 return Eulerian_Type::None;
394 }
395 else if (start_vertices == 1 && end_vertices == 1)
396 {
397 // One start, one end - could be Eulerian path
398 // Connectivity check is simpler for path
399 return Eulerian_Type::Path;
400 }
401 else
402 {
403 return Eulerian_Type::None;
404 }
405 }
406
407public:
408
415 Test_Eulerian(SN && __sn = SN(), SA && __sa = SA())
416 : sn(__sn), sa(__sa)
417 {
418 // empty
419 }
420
439 {
440 if (g.is_digraph())
441 return analyze_digraph(g);
442 else
443 return analyze_graph(g);
444 }
445
458 bool operator () (GT & g)
459 {
460 return compute(g) == Eulerian_Type::Cycle;
461 }
462
475 {
476 auto result = compute(g);
477 return result == Eulerian_Type::Cycle || result == Eulerian_Type::Path;
478 }
479
493 {
494 if (g.is_digraph())
495 {
497 for (Arc_Iterator<GT, SA> it(g, sa); it.has_curr(); it.next_ne())
498 NODE_COUNTER(it.get_tgt_node_ne())++;
499
500 for (Node_Iterator<GT, SN> it(g, sn); it.has_curr(); it.next_ne())
501 {
502 typename GT::Node * p = it.get_curr();
503 if (g.get_num_arcs(p) != NODE_COUNTER(p))
504 return false;
505 }
506 return true;
507 }
508 else
509 {
510 for (Node_Iterator<GT, SN> it(g, sn); it.has_curr(); it.next_ne())
511 if ((g.get_num_arcs(it.get_curr()) % 2) == 1)
512 return false;
513 return true;
514 }
515 }
516};
517
518
559template <AlephGraph GT,
563{
565 SA & sa;
566
578 {
579 typename GT::Node * start = nullptr;
580 typename GT::Node * odd_vertex = nullptr;
581
582 if (g.is_digraph())
583 {
584 // For digraphs, find vertex with out-degree > in-degree (start of path)
586 for (Arc_Iterator<GT, SA> it(g, sa); it.has_curr(); it.next_ne())
587 NODE_COUNTER(it.get_tgt_node_ne())++;
588
589 for (Node_Iterator<GT, SN> it(g, sn); it.has_curr(); it.next_ne())
590 {
591 typename GT::Node * p = it.get_curr();
592 if (g.get_num_arcs(p) > 0)
593 {
594 if (start == nullptr)
595 start = p;
596 if (g.get_num_arcs(p) > NODE_COUNTER(p))
597 odd_vertex = p; // out > in: this is start of path
598 }
599 }
600 }
601 else
602 {
603 // For undirected, find vertex with odd degree
604 for (Node_Iterator<GT, SN> it(g, sn); it.has_curr(); it.next_ne())
605 {
606 typename GT::Node * p = it.get_curr();
607 if (g.get_num_arcs(p) > 0)
608 {
609 if (start == nullptr)
610 start = p;
611 if ((g.get_num_arcs(p) % 2) == 1)
612 odd_vertex = p;
613 }
614 }
615 }
616
617 // For paths, must start at odd-degree vertex
618 if (type == Eulerian_Type::Path && odd_vertex != nullptr)
619 return odd_vertex;
620
621 return start;
622 }
623
632 {
633 // Mark all arcs as unused
634 g.reset_arcs();
635
639
640 stack.insert(start);
641
642 while (not stack.is_empty())
643 {
644 typename GT::Node * curr = stack.get_first();
645
646 // Find an unused edge from curr
647 typename GT::Arc * unused_arc = nullptr;
648 for (Node_Arc_Iterator<GT, SA> it(curr, sa); it.has_curr(); it.next_ne())
649 {
650 typename GT::Arc * arc = it.get_curr();
652 {
653 unused_arc = arc;
654 break;
655 }
656 }
657
658 if (unused_arc != nullptr)
659 {
660 // Mark arc as used
661 ARC_BITS(unused_arc).set_bit(Depth_First, true);
662
663 // Get the other endpoint
664 typename GT::Node * next = g.get_connected_node(unused_arc, curr);
665
666 stack.insert(next);
667 current_path.insert(unused_arc);
668 }
669 else
670 {
671 // No unused edges from curr - backtrack
672 stack.remove_first();
673 if (not current_path.is_empty())
674 result.insert(current_path.remove_first());
675 }
676 }
677
678 // Reverse to get correct order
680 while (not result.is_empty())
681 reversed.insert(result.remove_first());
682
683 return reversed;
684 }
685
694 {
695 // Mark all arcs as unused
696 g.reset_arcs();
697
701
702 stack.insert(start);
703
704 while (not stack.is_empty())
705 {
706 typename GT::Node * curr = stack.get_first();
707
708 // Find an unused outgoing edge from curr
709 typename GT::Arc * unused_arc = nullptr;
710 for (Node_Arc_Iterator<GT, SA> it(curr, sa); it.has_curr(); it.next_ne())
711 {
712 typename GT::Arc * arc = it.get_curr();
714 {
715 unused_arc = arc;
716 break;
717 }
718 }
719
720 if (unused_arc != nullptr)
721 {
722 // Mark arc as used
723 ARC_BITS(unused_arc).set_bit(Depth_First, true);
724
725 // Get target node
726 typename GT::Node * next = g.get_tgt_node(unused_arc);
727
728 stack.insert(next);
729 current_path.insert(unused_arc);
730 }
731 else
732 {
733 // No unused edges from curr - backtrack
734 stack.remove_first();
735 if (not current_path.is_empty())
736 result.insert(current_path.remove_first());
737 }
738 }
739
740 // Reverse to get correct order
742 while (not result.is_empty())
743 reversed.insert(result.remove_first());
744
745 return reversed;
746 }
747
748public:
749
756 Find_Eulerian_Path(SN && __sn = SN(), SA && __sa = SA())
757 : sn(__sn), sa(__sa)
758 {
759 // empty
760 }
761
770
790 {
791 Result result;
792
793 // First check if Eulerian path/cycle exists
794 Test_Eulerian<GT, SN, SA> tester(std::forward<SN>(sn), std::forward<SA>(sa));
795 result.type = tester.compute(g);
796
797 if (result.type == Eulerian_Type::None)
798 return result;
799
800 // Find starting vertex
801 typename GT::Node * start = find_start_vertex(g, result.type);
802
803 if (start == nullptr)
804 {
805 result.type = Eulerian_Type::None;
806 return result;
807 }
808
809 // Run Hierholzer's algorithm
810 if (g.is_digraph())
811 result.path = hierholzer_directed(g, start);
812 else
813 result.path = hierholzer_undirected(g, start);
814
815 return result;
816 }
817
825 {
826 return (*this)(g).path;
827 }
828
836 {
837 Result result = (*this)(g);
839
840 if (result.type == Eulerian_Type::None || result.path.is_empty())
841 return nodes;
842
843 // Get first node
844 typename GT::Arc * first_arc = result.path.get_first();
845 typename GT::Node * curr;
846
847 if (g.is_digraph())
848 curr = g.get_src_node(first_arc);
849 else
850 {
851 // For undirected, need to find proper starting node
852 typename GT::Node * start = find_start_vertex(g, result.type);
853 curr = start;
854 }
855
856 nodes.append(curr);
857
858 for (auto arc : result.path)
859 {
860 if (g.is_digraph())
861 curr = g.get_tgt_node(arc);
862 else
863 curr = g.get_connected_node(arc, curr);
864 nodes.append(curr);
865 }
866
867 return nodes;
868 }
869};
870
871} // end namespace Aleph
872
873# endif // EULERIAN_H
C++20 concepts for the protocol shared by graph algorithms.
List_Graph< Graph_Node< Node_Info >, Graph_Arc< Arc_Info > > GT
Doubly-linked list (defined in tpl_dynList.H).
Definition htlist.H:1155
T & insert(const T &item)
Definition htlist.H:1220
T & get_first() const
Return the first item of the list.
Definition htlist.H:1375
void next_ne() noexcept
Advances the iterator to the next filtered element (noexcept version).
Finds and constructs an Eulerian path or cycle using Hierholzer's algorithm.
Definition eulerian.H:563
GT::Node * find_start_vertex(GT &g, Eulerian_Type type)
Find starting vertex for Eulerian path/cycle.
Definition eulerian.H:577
DynList< typename GT::Node * > find_node_sequence(GT &g)
Get the node sequence of the Eulerian path/cycle.
Definition eulerian.H:835
DynList< typename GT::Arc * > find_path(GT &g)
Get only the arc list (convenience method).
Definition eulerian.H:824
DynList< typename GT::Arc * > hierholzer_undirected(GT &g, typename GT::Node *start)
Hierholzer's algorithm for undirected graphs.
Definition eulerian.H:631
DynList< typename GT::Arc * > hierholzer_directed(GT &g, typename GT::Node *start)
Hierholzer's algorithm for directed graphs.
Definition eulerian.H:693
Find_Eulerian_Path(SN &&__sn=SN(), SA &&__sa=SA())
Construct an Eulerian path finder with optional filters.
Definition eulerian.H:756
Result operator()(GT &g)
Find an Eulerian path or cycle in the graph.
Definition eulerian.H:789
constexpr bool is_empty() const noexcept
Definition htlist.H:419
Filtered iterator on the nodes of a graph.
Definition tpl_graph.H:1207
Path on a graph.
Definition tpl_graph.H:2772
Tests whether a graph or digraph is Eulerian.
Definition eulerian.H:173
Eulerian_Type compute(GT &g)
Compute detailed Eulerian classification.
Definition eulerian.H:438
bool is_strongly_connected(GT &g)
Check strong connectivity of digraph using Kosaraju-like approach.
Definition eulerian.H:246
bool is_connected(GT &g)
Check connectivity of undirected graph using DFS.
Definition eulerian.H:186
bool operator()(GT &g)
Test if a graph has an Eulerian cycle.
Definition eulerian.H:458
Eulerian_Type analyze_digraph(GT &g)
Analyze Eulerian properties of a directed graph.
Definition eulerian.H:351
Test_Eulerian(SN &&__sn=SN(), SA &&__sa=SA())
Construct an Eulerian tester with optional filters.
Definition eulerian.H:415
bool test_degree_only(GT &g)
Check only degree conditions (without connectivity check).
Definition eulerian.H:492
Eulerian_Type analyze_graph(GT &g)
Analyze Eulerian properties of an undirected graph.
Definition eulerian.H:309
bool has_eulerian_path(GT &g)
Test if a graph has an Eulerian path.
Definition eulerian.H:474
void reset_arcs() const
Reset all the arcs of graph (the control bits, the state, the counter and the cookie)
Definition graph-dry.H:975
Node * get_src_node(Arc *arc) const noexcept
Return the source node of arc (only for directed graphs)
Definition graph-dry.H:779
constexpr size_t get_num_nodes() const noexcept
Return the total of nodes of graph.
Definition graph-dry.H:737
bool is_digraph() const noexcept
Return true if the graph this is directed.
Definition graph-dry.H:699
void reset_counter_nodes() const noexcept
Reset all the counters to zero for all the nodes of graph.
Definition graph-dry.H:1112
Node * get_connected_node(Arc *arc, Node *node) const noexcept
Return the adjacent node to node through arc.
Definition graph-dry.H:820
constexpr size_t get_num_arcs() const noexcept
Definition graph-dry.H:826
void reset_nodes() const
Reset all the nodes of graph (the control bits, the state, the counter and the cookie)
Definition graph-dry.H:968
Node * get_tgt_node(Arc *arc) const noexcept
Return the target node of arc (only for directed graphs)
Definition graph-dry.H:785
DynArray< Graph::Node * > nodes
Definition graphpic.C:406
#define NODE_COUNTER(p)
Get the counter of a node.
#define ARC_BITS(p)
Return the control bits of arc p.
size_t blossom_maximum_cardinality_matching(const GT &g, DynDlist< typename GT::Arc * > &matching, SA sa=SA())
Alias of compute_maximum_cardinality_general_matching().
Definition Blossom.H:466
#define IS_ARC_VISITED(p, bit)
Determine whether the bit field is or not set to one.
#define NODE_BITS(p)
Get the control bits of a node.
@ Depth_First
Definition aleph-graph.H:73
Main namespace for Aleph-w library functions.
Definition ah-arena.H:89
Eulerian_Type
Enumeration for Eulerian graph classification.
Definition eulerian.H:114
@ Cycle
Graph has an Eulerian cycle.
@ None
Graph is not Eulerian.
@ Path
Graph has an Eulerian path but not a cycle.
bool diff(const C1 &c1, const C2 &c2, Eq e=Eq())
Check if two containers differ.
void next()
Advance all underlying iterators (bounds-checked).
Definition ah-zip.H:171
Filtered iterator on all the arcs of a graph.
Definition tpl_graph.H:1165
Default filter for filtered iterators on arcs.
Definition tpl_graph.H:1001
Default filter for the graph nodes.
Definition tpl_graph.H:1193
Result type: path and its classification.
Definition eulerian.H:766
Eulerian_Type type
Classification (Cycle, Path, or None)
Definition eulerian.H:768
DynList< typename GT::Arc * > path
The Eulerian path/cycle as arc list.
Definition eulerian.H:767
Filtered iterator of adjacent arcs of a node.
Definition tpl_graph.H:1120
Represents a missing value.
Generic graph and digraph implementations.
Utility algorithms and operations for graphs.