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 <tpl_graph.H>
98# include <tpl_graph_utils.H>
99
100namespace Aleph
101{
102
112{
113 Cycle,
114 Path,
115 None
116};
117
167template <class GT,
168 class SN = Dft_Show_Node<GT>,
169 class SA = Dft_Show_Arc<GT> >
171{
174
184 bool is_connected(GT & g)
185 {
186 if (g.get_num_nodes() == 0)
187 return true;
188
189 // Find first vertex with edges
190 typename GT::Node * start = nullptr;
191 size_t non_isolated = 0;
192
193 for (Node_Iterator<GT, SN> it(g, sn); it.has_curr(); it.next_ne())
194 {
195 typename GT::Node * p = it.get_curr();
196 if (g.get_num_arcs(p) > 0)
197 {
198 if (start == nullptr)
199 start = p;
200 ++non_isolated;
201 }
202 }
203
204 // No edges means trivially connected (or empty)
205 if (start == nullptr)
206 return true;
207
208 // DFS from start vertex
209 g.reset_nodes();
210 size_t visited = 0;
211
213 stack.insert(start);
214 NODE_BITS(start).set_bit(Depth_First, true);
215
216 while (not stack.is_empty())
217 {
218 typename GT::Node * curr = stack.remove_first();
219 ++visited;
220
221 for (Node_Arc_Iterator<GT, SA> it(curr, sa); it.has_curr(); it.next_ne())
222 {
223 typename GT::Node * adj = it.get_tgt_node_ne();
224 if (not NODE_BITS(adj).get_bit(Depth_First))
225 {
226 NODE_BITS(adj).set_bit(Depth_First, true);
227 stack.insert(adj);
228 }
229 }
230 }
231
232 return visited == non_isolated;
233 }
234
245 {
246 if (g.get_num_nodes() == 0)
247 return true;
248
249 // Find first vertex with edges
250 typename GT::Node * start = nullptr;
251 size_t non_isolated = 0;
252
253 for (Node_Iterator<GT, SN> it(g, sn); it.has_curr(); it.next_ne())
254 {
255 typename GT::Node * p = it.get_curr();
256 if (g.get_num_arcs(p) > 0 or NODE_COUNTER(p) > 0) // has out or in edges
257 {
258 if (start == nullptr)
259 start = p;
260 ++non_isolated;
261 }
262 }
263
264 if (start == nullptr || non_isolated <= 1)
265 return true;
266
267 // DFS from start in original direction
268 g.reset_nodes();
269 size_t visited_forward = 0;
270
272 stack.insert(start);
273 NODE_BITS(start).set_bit(Depth_First, true);
274
275 while (not stack.is_empty())
276 {
277 typename GT::Node * curr = stack.remove_first();
278 if (g.get_num_arcs(curr) > 0 or NODE_COUNTER(curr) > 0)
280
281 for (Node_Arc_Iterator<GT, SA> it(curr, sa); it.has_curr(); it.next_ne())
282 {
283 typename GT::Node * adj = it.get_tgt_node_ne();
284 if (not NODE_BITS(adj).get_bit(Depth_First))
285 {
286 NODE_BITS(adj).set_bit(Depth_First, true);
287 stack.insert(adj);
288 }
289 }
290 }
291
293 return false;
294
295 // For full strong connectivity check, we'd need reverse graph traversal
296 // For Eulerian purposes, if in-degree == out-degree and forward DFS
297 // reaches all vertices, that's sufficient for practical purposes
298 return true;
299 }
300
308 {
309 assert(not g.is_digraph());
310
311 size_t odd_degree_count = 0;
312
313 for (Node_Iterator<GT, SN> it(g, sn); it.has_curr(); it.next_ne())
314 {
315 if ((g.get_num_arcs(it.get_curr()) % 2) == 1)
317 }
318
319 // Check degree conditions first
320 if (odd_degree_count == 0)
321 {
322 // All even degrees - could be Eulerian cycle if connected
323 if (is_connected(g))
325 else
326 return Eulerian_Type::None;
327 }
328 else if (odd_degree_count == 2)
329 {
330 // Exactly 2 odd degrees - could be Eulerian path if connected
331 if (is_connected(g))
332 return Eulerian_Type::Path;
333 else
334 return Eulerian_Type::None;
335 }
336 else
337 {
338 // More than 2 odd degrees - not Eulerian
339 return Eulerian_Type::None;
340 }
341 }
342
350 {
351 assert(g.is_digraph());
352
354
355 // First pass: count in-degrees using node counters
356 for (Arc_Iterator<GT, SA> it(g, sa); it.has_curr(); it.next_ne())
357 NODE_COUNTER(it.get_tgt_node_ne())++;
358
359 // Count imbalanced vertices
360 size_t start_vertices = 0; // out - in == 1
361 size_t end_vertices = 0; // in - out == 1
362 size_t imbalanced = 0; // |in - out| > 1
363
364 for (Node_Iterator<GT, SN> it(g, sn); it.has_curr(); it.next_ne())
365 {
366 typename GT::Node * p = it.get_curr();
367 const long out_deg = g.get_num_arcs(p);
368 const long in_deg = NODE_COUNTER(p);
369 const long diff = out_deg - in_deg;
370
371 if (diff == 0)
372 continue; // Balanced vertex
373 else if (diff == 1)
375 else if (diff == -1)
376 ++end_vertices;
377 else
378 ++imbalanced;
379 }
380
381 // Check degree conditions
382 if (imbalanced > 0)
383 return Eulerian_Type::None;
384
385 if (start_vertices == 0 && end_vertices == 0)
386 {
387 // All balanced - could be Eulerian cycle if strongly connected
390 else
391 return Eulerian_Type::None;
392 }
393 else if (start_vertices == 1 && end_vertices == 1)
394 {
395 // One start, one end - could be Eulerian path
396 // Connectivity check is simpler for path
397 return Eulerian_Type::Path;
398 }
399 else
400 {
401 return Eulerian_Type::None;
402 }
403 }
404
405public:
406
413 Test_Eulerian(SN && __sn = SN(), SA && __sa = SA())
414 : sn(__sn), sa(__sa)
415 {
416 // empty
417 }
418
437 {
438 if (g.is_digraph())
439 return analyze_digraph(g);
440 else
441 return analyze_graph(g);
442 }
443
456 bool operator () (GT & g)
457 {
458 return compute(g) == Eulerian_Type::Cycle;
459 }
460
473 {
474 auto result = compute(g);
475 return result == Eulerian_Type::Cycle || result == Eulerian_Type::Path;
476 }
477
491 {
492 if (g.is_digraph())
493 {
495 for (Arc_Iterator<GT, SA> it(g, sa); it.has_curr(); it.next_ne())
496 NODE_COUNTER(it.get_tgt_node_ne())++;
497
498 for (Node_Iterator<GT, SN> it(g, sn); it.has_curr(); it.next_ne())
499 {
500 typename GT::Node * p = it.get_curr();
501 if (g.get_num_arcs(p) != NODE_COUNTER(p))
502 return false;
503 }
504 return true;
505 }
506 else
507 {
508 for (Node_Iterator<GT, SN> it(g, sn); it.has_curr(); it.next_ne())
509 if ((g.get_num_arcs(it.get_curr()) % 2) == 1)
510 return false;
511 return true;
512 }
513 }
514};
515
516
557template <class GT,
558 class SN = Dft_Show_Node<GT>,
559 class SA = Dft_Show_Arc<GT> >
561{
564
576 {
577 typename GT::Node * start = nullptr;
578 typename GT::Node * odd_vertex = nullptr;
579
580 if (g.is_digraph())
581 {
582 // For digraphs, find vertex with out-degree > in-degree (start of path)
584 for (Arc_Iterator<GT, SA> it(g, sa); it.has_curr(); it.next_ne())
585 NODE_COUNTER(it.get_tgt_node_ne())++;
586
587 for (Node_Iterator<GT, SN> it(g, sn); it.has_curr(); it.next_ne())
588 {
589 typename GT::Node * p = it.get_curr();
590 if (g.get_num_arcs(p) > 0)
591 {
592 if (start == nullptr)
593 start = p;
594 if (g.get_num_arcs(p) > NODE_COUNTER(p))
595 odd_vertex = p; // out > in: this is start of path
596 }
597 }
598 }
599 else
600 {
601 // For undirected, find vertex with odd degree
602 for (Node_Iterator<GT, SN> it(g, sn); it.has_curr(); it.next_ne())
603 {
604 typename GT::Node * p = it.get_curr();
605 if (g.get_num_arcs(p) > 0)
606 {
607 if (start == nullptr)
608 start = p;
609 if ((g.get_num_arcs(p) % 2) == 1)
610 odd_vertex = p;
611 }
612 }
613 }
614
615 // For paths, must start at odd-degree vertex
616 if (type == Eulerian_Type::Path && odd_vertex != nullptr)
617 return odd_vertex;
618
619 return start;
620 }
621
630 {
631 // Mark all arcs as unused
632 g.reset_arcs();
633
637
638 stack.insert(start);
639
640 while (not stack.is_empty())
641 {
642 typename GT::Node * curr = stack.get_first();
643
644 // Find an unused edge from curr
645 typename GT::Arc * unused_arc = nullptr;
646 for (Node_Arc_Iterator<GT, SA> it(curr, sa); it.has_curr(); it.next_ne())
647 {
648 typename GT::Arc * arc = it.get_curr();
650 {
651 unused_arc = arc;
652 break;
653 }
654 }
655
656 if (unused_arc != nullptr)
657 {
658 // Mark arc as used
659 ARC_BITS(unused_arc).set_bit(Depth_First, true);
660
661 // Get the other endpoint
662 typename GT::Node * next = g.get_connected_node(unused_arc, curr);
663
664 stack.insert(next);
666 }
667 else
668 {
669 // No unused edges from curr - backtrack
670 stack.remove_first();
673 }
674 }
675
676 // Reverse to get correct order
678 while (not result.is_empty())
679 reversed.insert(result.remove_first());
680
681 return reversed;
682 }
683
692 {
693 // Mark all arcs as unused
694 g.reset_arcs();
695
699
700 stack.insert(start);
701
702 while (not stack.is_empty())
703 {
704 typename GT::Node * curr = stack.get_first();
705
706 // Find an unused outgoing edge from curr
707 typename GT::Arc * unused_arc = nullptr;
708 for (Node_Arc_Iterator<GT, SA> it(curr, sa); it.has_curr(); it.next_ne())
709 {
710 typename GT::Arc * arc = it.get_curr();
712 {
713 unused_arc = arc;
714 break;
715 }
716 }
717
718 if (unused_arc != nullptr)
719 {
720 // Mark arc as used
721 ARC_BITS(unused_arc).set_bit(Depth_First, true);
722
723 // Get target node
724 typename GT::Node * next = g.get_tgt_node(unused_arc);
725
726 stack.insert(next);
728 }
729 else
730 {
731 // No unused edges from curr - backtrack
732 stack.remove_first();
735 }
736 }
737
738 // Reverse to get correct order
740 while (not result.is_empty())
741 reversed.insert(result.remove_first());
742
743 return reversed;
744 }
745
746public:
747
755 : sn(__sn), sa(__sa)
756 {
757 // empty
758 }
759
768
788 {
789 Result result;
790
791 // First check if Eulerian path/cycle exists
792 Test_Eulerian<GT, SN, SA> tester(std::forward<SN>(sn), std::forward<SA>(sa));
793 result.type = tester.compute(g);
794
795 if (result.type == Eulerian_Type::None)
796 return result;
797
798 // Find starting vertex
799 typename GT::Node * start = find_start_vertex(g, result.type);
800
801 if (start == nullptr)
802 {
803 result.type = Eulerian_Type::None;
804 return result;
805 }
806
807 // Run Hierholzer's algorithm
808 if (g.is_digraph())
809 result.path = hierholzer_directed(g, start);
810 else
811 result.path = hierholzer_undirected(g, start);
812
813 return result;
814 }
815
823 {
824 return (*this)(g).path;
825 }
826
834 {
835 Result result = (*this)(g);
837
838 if (result.type == Eulerian_Type::None || result.path.is_empty())
839 return nodes;
840
841 // Get first node
842 typename GT::Arc * first_arc = result.path.get_first();
843 typename GT::Node * curr;
844
845 if (g.is_digraph())
846 curr = g.get_src_node(first_arc);
847 else
848 {
849 // For undirected, need to find proper starting node
850 typename GT::Node * start = find_start_vertex(g, result.type);
851 curr = start;
852 }
853
854 nodes.append(curr);
855
856 for (auto arc : result.path)
857 {
858 if (g.is_digraph())
859 curr = g.get_tgt_node(arc);
860 else
861 curr = g.get_connected_node(arc, curr);
862 nodes.append(curr);
863 }
864
865 return nodes;
866 }
867};
868
869} // end namespace Aleph
870
871# endif // EULERIAN_H
Dynamic singly linked list with functional programming support.
Definition htlist.H:1423
T & insert(const T &item)
Insert a new item by copy.
Definition htlist.H:1502
T & get_first() const
Return the first item of the list.
Definition htlist.H:1675
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:561
GT::Node * find_start_vertex(GT &g, Eulerian_Type type)
Find starting vertex for Eulerian path/cycle.
Definition eulerian.H:575
DynList< typename GT::Node * > find_node_sequence(GT &g)
Get the node sequence of the Eulerian path/cycle.
Definition eulerian.H:833
DynList< typename GT::Arc * > find_path(GT &g)
Get only the arc list (convenience method).
Definition eulerian.H:822
DynList< typename GT::Arc * > hierholzer_undirected(GT &g, typename GT::Node *start)
Hierholzer's algorithm for undirected graphs.
Definition eulerian.H:629
DynList< typename GT::Arc * > hierholzer_directed(GT &g, typename GT::Node *start)
Hierholzer's algorithm for directed graphs.
Definition eulerian.H:691
Find_Eulerian_Path(SN &&__sn=SN(), SA &&__sa=SA())
Construct an Eulerian path finder with optional filters.
Definition eulerian.H:754
Result operator()(GT &g)
Find an Eulerian path or cycle in the graph.
Definition eulerian.H:787
constexpr bool is_empty() const noexcept
Return true if list is empty.
Definition htlist.H:523
Filtered iterator on the nodes of a graph.
Definition tpl_graph.H:1206
Path on a graph.
Definition tpl_graph.H:2669
Tests whether a graph or digraph is Eulerian.
Definition eulerian.H:171
Eulerian_Type compute(GT &g)
Compute detailed Eulerian classification.
Definition eulerian.H:436
bool is_strongly_connected(GT &g)
Check strong connectivity of digraph using Kosaraju-like approach.
Definition eulerian.H:244
bool is_connected(GT &g)
Check connectivity of undirected graph using DFS.
Definition eulerian.H:184
bool operator()(GT &g)
Test if a graph has an Eulerian cycle.
Definition eulerian.H:456
Eulerian_Type analyze_digraph(GT &g)
Analyze Eulerian properties of a directed graph.
Definition eulerian.H:349
Test_Eulerian(SN &&__sn=SN(), SA &&__sa=SA())
Construct an Eulerian tester with optional filters.
Definition eulerian.H:413
bool test_degree_only(GT &g)
Check only degree conditions (without connectivity check).
Definition eulerian.H:490
Eulerian_Type analyze_graph(GT &g)
Analyze Eulerian properties of an undirected graph.
Definition eulerian.H:307
bool has_eulerian_path(GT &g)
Test if a graph has an Eulerian path.
Definition eulerian.H:472
void reset_arcs() const
Reset all the arcs of graph (the control bits, the state, the counter and the cookie)
Definition graph-dry.H:927
Node * get_src_node(Arc *arc) const noexcept
Return the source node of arc (only for directed graphs)
Definition graph-dry.H:731
constexpr size_t get_num_nodes() const noexcept
Return the total of nodes of graph.
Definition graph-dry.H:695
bool is_digraph() const noexcept
Return true if the graph this is directed.
Definition graph-dry.H:657
void reset_counter_nodes() const noexcept
Reset all the counters to zero for all the nodes of graph.
Definition graph-dry.H:1064
Node * get_connected_node(Arc *arc, Node *node) const noexcept
Return the adjacent node to node through arc.
Definition graph-dry.H:772
constexpr size_t get_num_arcs() const noexcept
Definition graph-dry.H:778
void reset_nodes() const
Reset all the nodes of graph (the control bits, the state, the counter and the cookie)
Definition graph-dry.H:920
Node * get_tgt_node(Arc *arc) const noexcept
Return the target node of arc (only for directed graphs)
Definition graph-dry.H:737
List_Graph< Graph_Node< int >, Graph_Arc< int > > GT
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.
#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:112
@ 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:175
DynList< T > maps(const C &c, Op op)
Classic map operation.
Filtered iterator on all the arcs of a graph.
Definition tpl_graph.H:1164
Default filter for filtered iterators on arcs.
Definition tpl_graph.H:1000
Default filter for the graph nodes.
Definition tpl_graph.H:1192
Result type: path and its classification.
Definition eulerian.H:764
Eulerian_Type type
Classification (Cycle, Path, or None)
Definition eulerian.H:766
DynList< typename GT::Arc * > path
The Eulerian path/cycle as arc list.
Definition eulerian.H:765
Arc of graph implemented with double-linked adjacency lists.
Definition tpl_graph.H:222
Filtered iterator of adjacent arcs of a node.
Definition tpl_graph.H:1119
Represents a missing value.
Generic graph and digraph implementations.
Utility algorithms and operations for graphs.