Coverage Report

Created: 2026-04-12 06:26

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/igraph/src/paths/eulerian.c
Line
Count
Source
1
/*
2
   igraph library.
3
   Copyright (C) 2005-2020  The igraph development team
4
5
   This program is free software; you can redistribute it and/or modify
6
   it under the terms of the GNU General Public License as published by
7
   the Free Software Foundation; either version 2 of the License, or
8
   (at your option) any later version.
9
10
   This program is distributed in the hope that it will be useful,
11
   but WITHOUT ANY WARRANTY; without even the implied warranty of
12
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
   GNU General Public License for more details.
14
15
   You should have received a copy of the GNU General Public License
16
   along with this program; if not, write to the Free Software
17
   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18
   02110-1301 USA
19
*/
20
21
#include "igraph_eulerian.h"
22
23
#include "igraph_adjlist.h"
24
#include "igraph_bitset.h"
25
#include "igraph_interface.h"
26
#include "igraph_components.h"
27
#include "igraph_stack.h"
28
29
/**
30
 * \section about_eulerian
31
 *
32
 * <para>These functions calculate whether an Eulerian path or cycle exists
33
 * and if so, can find them.</para>
34
 */
35
36
37
/* solution adapted from https://www.geeksforgeeks.org/eulerian-path-and-circuit/
38
The function returns one of the following values
39
has_path is set to 1 if a path exists, 0 otherwise
40
has_cycle is set to 1 if a cycle exists, 0 otherwise
41
*/
42
static igraph_error_t igraph_i_is_eulerian_undirected(
43
0
        const igraph_t *graph, igraph_bool_t *has_path, igraph_bool_t *has_cycle, igraph_int_t *start_of_path) {
44
0
    igraph_int_t odd;
45
0
    igraph_vector_int_t degree;
46
0
    igraph_vector_int_t csize;
47
    /* boolean vector to mark singletons: */
48
0
    igraph_vector_int_t nonsingleton;
49
0
    igraph_int_t i, n, vsize;
50
0
    igraph_int_t cluster_count;
51
    /* number of self-looping singletons: */
52
0
    igraph_int_t es;
53
    /* will be set to 1 if there are non-isolated vertices, otherwise 0: */
54
0
    igraph_int_t ens;
55
56
0
    n = igraph_vcount(graph);
57
58
0
    if (igraph_ecount(graph) == 0 || n <= 1) {
59
0
        start_of_path = 0; /* in case the graph has one vertex with self-loops */
60
0
        *has_path = true;
61
0
        *has_cycle = true;
62
0
        return  IGRAPH_SUCCESS;
63
0
    }
64
65
    /* check for connectedness, but singletons are special since they affect
66
     * the Eulerian nature only if there is a self-loop AND another edge
67
     * somewhere else in the graph */
68
0
    IGRAPH_VECTOR_INT_INIT_FINALLY(&csize, 0);
69
0
    IGRAPH_CHECK(igraph_connected_components(graph, NULL, &csize, NULL, IGRAPH_WEAK));
70
0
    cluster_count = 0;
71
0
    vsize = igraph_vector_int_size(&csize);
72
0
    for (i = 0; i < vsize; i++) {
73
0
        if (VECTOR(csize)[i] > 1) {
74
0
            cluster_count++;
75
0
            if (cluster_count > 1) {
76
                /* disconnected edges, they'll never reach each other */
77
0
                *has_path = false;
78
0
                *has_cycle = false;
79
0
                igraph_vector_int_destroy(&csize);
80
0
                IGRAPH_FINALLY_CLEAN(1);
81
82
0
                return IGRAPH_SUCCESS;
83
0
            }
84
0
        }
85
0
    }
86
87
0
    igraph_vector_int_destroy(&csize);
88
0
    IGRAPH_FINALLY_CLEAN(1);
89
90
    /* the graph is connected except for singletons */
91
    /* find singletons (including those with self-loops) */
92
0
    IGRAPH_VECTOR_INT_INIT_FINALLY(&nonsingleton, 0);
93
0
    IGRAPH_CHECK(igraph_degree(graph, &nonsingleton, igraph_vss_all(), IGRAPH_ALL, IGRAPH_NO_LOOPS));
94
95
    /* check the degrees for odd/even:
96
     * - >= 2 odd means no cycle (1 odd is impossible)
97
     * - > 2 odd means no path
98
     * plus there are a few corner cases with singletons
99
     */
100
0
    IGRAPH_VECTOR_INT_INIT_FINALLY(&degree, 0);
101
0
    IGRAPH_CHECK(igraph_degree(graph, &degree, igraph_vss_all(), IGRAPH_ALL, IGRAPH_LOOPS));
102
0
    odd = 0;
103
0
    es = 0;
104
0
    ens = 0;
105
0
    for (i = 0; i < n; i++) {
106
0
        igraph_int_t deg = VECTOR(degree)[i];
107
        /* Eulerian is about edges, so skip free vertices */
108
0
        if (deg == 0) continue;
109
110
0
        if (!VECTOR(nonsingleton)[i]) {
111
            /* singleton with self loops */
112
0
            es++;
113
0
        } else {
114
            /* at least one non-singleton */
115
0
            ens = 1;
116
            /* note: self-loops count for two (in and out) */
117
0
            if (deg % 2) odd++;
118
0
        }
119
120
0
        if (es + ens > 1) {
121
            /* 2+ singletons with self loops or singleton with self-loops and
122
             * 1+ edges in the non-singleton part of the graph. */
123
0
            *has_path = false;
124
0
            *has_cycle = false;
125
0
            igraph_vector_int_destroy(&nonsingleton);
126
0
            igraph_vector_int_destroy(&degree);
127
0
            IGRAPH_FINALLY_CLEAN(2);
128
129
0
            return IGRAPH_SUCCESS;
130
0
        }
131
0
    }
132
133
0
    igraph_vector_int_destroy(&nonsingleton);
134
0
    IGRAPH_FINALLY_CLEAN(1);
135
136
    /* this is the usual algorithm on the connected part of the graph */
137
0
    if (odd > 2) {
138
0
        *has_path = false;
139
0
        *has_cycle = false;
140
0
    } else if (odd == 2) {
141
0
        *has_path = true;
142
0
        *has_cycle = false;
143
0
    } else {
144
0
        *has_path = true;
145
0
        *has_cycle = true;
146
0
    }
147
148
    /* set start of path if there is one but there is no cycle */
149
    /* note: we cannot do this in the previous loop because at that time we are
150
     * not sure yet if a path exists */
151
0
    for (i = 0; i < n; i++) {
152
0
        if ((*has_cycle && VECTOR(degree)[i] > 0) || (!*has_cycle && VECTOR(degree)[i] %2 == 1)) {
153
0
            *start_of_path = i;
154
0
            break;
155
0
        }
156
0
    }
157
158
0
    igraph_vector_int_destroy(&degree);
159
0
    IGRAPH_FINALLY_CLEAN(1);
160
161
0
    return IGRAPH_SUCCESS;
162
0
}
163
164
165
static igraph_error_t igraph_i_is_eulerian_directed(
166
3.41k
        const igraph_t *graph, igraph_bool_t *has_path, igraph_bool_t *has_cycle, igraph_int_t *start_of_path) {
167
3.41k
    igraph_int_t incoming_excess, outgoing_excess, n;
168
3.41k
    igraph_int_t i, vsize;
169
3.41k
    igraph_int_t cluster_count;
170
3.41k
    igraph_vector_int_t out_degree, in_degree;
171
3.41k
    igraph_vector_int_t csize;
172
    /* boolean vector to mark singletons: */
173
3.41k
    igraph_vector_int_t nonsingleton;
174
    /* number of self-looping singletons: */
175
3.41k
    igraph_int_t es;
176
    /* will be set to 1 if there are non-isolated vertices, otherwise 0: */
177
3.41k
    igraph_int_t ens;
178
179
3.41k
    n = igraph_vcount(graph);
180
181
3.41k
    if (igraph_ecount(graph) == 0 || n <= 1) {
182
426
        start_of_path = 0; /* in case the graph has one vertex with self-loops */
183
426
        *has_path = true;
184
426
        *has_cycle = true;
185
426
        return IGRAPH_SUCCESS;
186
426
    }
187
188
2.99k
    incoming_excess = 0;
189
2.99k
    outgoing_excess = 0;
190
191
    /* check for weak connectedness, but singletons are special since they affect
192
     * the Eulerian nature only if there is a self-loop AND another edge
193
     * somewhere else in the graph */
194
2.99k
    IGRAPH_VECTOR_INT_INIT_FINALLY(&csize, 0);
195
196
2.99k
    IGRAPH_CHECK(igraph_connected_components(graph, NULL, &csize, NULL, IGRAPH_WEAK));
197
2.99k
    cluster_count = 0;
198
2.99k
    vsize = igraph_vector_int_size(&csize);
199
364k
    for (i = 0; i < vsize; i++) {
200
362k
        if (VECTOR(csize)[i] > 1) {
201
3.37k
            cluster_count++;
202
3.37k
            if (cluster_count > 1) {
203
                /* weakly disconnected edges, they'll never reach each other */
204
560
                *has_path = false;
205
560
                *has_cycle = false;
206
560
                igraph_vector_int_destroy(&csize);
207
560
                IGRAPH_FINALLY_CLEAN(1);
208
209
560
                return IGRAPH_SUCCESS;
210
560
            }
211
3.37k
        }
212
362k
    }
213
214
2.43k
    igraph_vector_int_destroy(&csize);
215
2.43k
    IGRAPH_FINALLY_CLEAN(1);
216
217
    /* the graph is weakly connected except for singletons */
218
    /* find the singletons (including those with self-loops) */
219
2.43k
    IGRAPH_VECTOR_INT_INIT_FINALLY(&nonsingleton, 0);
220
2.43k
    IGRAPH_CHECK(igraph_degree(graph, &nonsingleton, igraph_vss_all(), IGRAPH_ALL, IGRAPH_NO_LOOPS));
221
222
223
    /* checking if no. of incoming edges == outgoing edges
224
     * plus there are a few corner cases with singletons */
225
2.43k
    IGRAPH_VECTOR_INT_INIT_FINALLY(&out_degree, 0);
226
2.43k
    IGRAPH_CHECK(igraph_degree(graph, &out_degree, igraph_vss_all(), IGRAPH_OUT, IGRAPH_LOOPS));
227
228
2.43k
    IGRAPH_VECTOR_INT_INIT_FINALLY(&in_degree, 0);
229
2.43k
    IGRAPH_CHECK(igraph_degree(graph, &in_degree, igraph_vss_all(), IGRAPH_IN, IGRAPH_LOOPS));
230
2.43k
    es = 0;
231
2.43k
    ens = 0;
232
2.43k
    *start_of_path = -1;
233
263k
    for (i = 0; i < n; i++) {
234
261k
        igraph_int_t degin = VECTOR(in_degree)[i];
235
261k
        igraph_int_t degout = VECTOR(out_degree)[i];
236
237
        /* Eulerian is about edges, so skip free vertices */
238
261k
        if (degin + degout == 0) continue;
239
240
17.1k
        if (!VECTOR(nonsingleton)[i]) {
241
            /* singleton with self loops */
242
227
            es++;
243
            /* if we ever want a path, it has to be this self-loop */
244
227
            *start_of_path = i;
245
16.8k
        } else {
246
            /* at least one non-singleton */
247
16.8k
            ens = 1;
248
16.8k
        }
249
250
17.1k
        if (es + ens > 1) {
251
            /* 2+ singletons with self loops or singleton with self-loops and
252
             * 1+ edges in the non-singleton part of the graph. */
253
50
            *has_path = false;
254
50
            *has_cycle = false;
255
50
            igraph_vector_int_destroy(&nonsingleton);
256
50
            igraph_vector_int_destroy(&in_degree);
257
50
            igraph_vector_int_destroy(&out_degree);
258
50
            IGRAPH_FINALLY_CLEAN(3);
259
260
50
            return IGRAPH_SUCCESS;
261
50
        }
262
263
        /* as long as we have perfect balance, you can start
264
         * anywhere with an edge */
265
17.0k
        if (*start_of_path == -1 && incoming_excess == 0 && outgoing_excess == 0) {
266
2.23k
            *start_of_path = i;
267
2.23k
        }
268
269
        /* same in and out (including self-loops, even in singletons) */
270
17.0k
        if (degin == degout) {
271
14.2k
            continue;
272
14.2k
        }
273
274
        /* non-singleton, in != out */
275
2.80k
        if (degin > degout) {
276
1.39k
            incoming_excess += degin - degout;
277
1.41k
        } else {
278
1.41k
            outgoing_excess += degout - degin;
279
1.41k
            if (outgoing_excess == 1) {
280
1.04k
                *start_of_path = i;
281
1.04k
            }
282
1.41k
        }
283
284
        /* too much imbalance, either of the following:
285
         * 1. 1+ vertices have 2+ in/out
286
         * 2. 2+ nodes have 1+ in/out */
287
2.80k
        if (incoming_excess > 1 || outgoing_excess > 1) {
288
728
            *has_path = false;
289
728
            *has_cycle = false;
290
728
            igraph_vector_int_destroy(&nonsingleton);
291
728
            igraph_vector_int_destroy(&in_degree);
292
728
            igraph_vector_int_destroy(&out_degree);
293
728
            IGRAPH_FINALLY_CLEAN(3);
294
295
728
            return IGRAPH_SUCCESS;
296
728
        }
297
2.80k
    }
298
299
1.65k
    *has_path = true;
300
    /* perfect edge balance -> strong connectivity */
301
1.65k
    *has_cycle = (incoming_excess == 0) && (outgoing_excess == 0);
302
    /* either way, the start was set already */
303
304
1.65k
    igraph_vector_int_destroy(&nonsingleton);
305
1.65k
    igraph_vector_int_destroy(&in_degree);
306
1.65k
    igraph_vector_int_destroy(&out_degree);
307
1.65k
    IGRAPH_FINALLY_CLEAN(3);
308
309
1.65k
    return IGRAPH_SUCCESS;
310
2.43k
}
311
312
313
/**
314
 * \ingroup Eulerian
315
 * \function igraph_is_eulerian
316
 * \brief Checks whether an Eulerian path or cycle exists.
317
 *
318
 * An Eulerian path traverses each edge of the graph precisely once. A closed
319
 * Eulerian path is referred to as an Eulerian cycle.
320
 *
321
 * \param graph The graph object.
322
 * \param has_path Pointer to a Boolean, will be set to true if an Eulerian path exists.
323
 *    Must not be \c NULL.
324
 * \param has_cycle Pointer to a Boolean, will be set to true if an Eulerian cycle exists.
325
 *    Must not be \c NULL.
326
 * \return Error code:
327
 *         \c IGRAPH_ENOMEM, not enough memory for temporary data.
328
 *
329
 * Time complexity: O(|V|+|E|), the number of vertices plus the number of edges.
330
 *
331
 */
332
333
2.16k
igraph_error_t igraph_is_eulerian(const igraph_t *graph, igraph_bool_t *has_path, igraph_bool_t *has_cycle) {
334
2.16k
    igraph_int_t start_of_path = 0;
335
336
2.16k
    if (igraph_is_directed(graph)) {
337
2.16k
        IGRAPH_CHECK(igraph_i_is_eulerian_directed(graph, has_path, has_cycle, &start_of_path));
338
2.16k
    } else {
339
0
        IGRAPH_CHECK(igraph_i_is_eulerian_undirected(graph, has_path, has_cycle, &start_of_path));
340
0
    }
341
2.16k
    return IGRAPH_SUCCESS;
342
2.16k
}
343
344
345
static igraph_error_t igraph_i_eulerian_path_undirected(
346
        const igraph_t *graph, igraph_vector_int_t *edge_res, igraph_vector_int_t *vertex_res,
347
0
        igraph_int_t start_of_path) {
348
349
0
    igraph_int_t curr;
350
0
    igraph_int_t n, m;
351
0
    igraph_inclist_t il;
352
0
    igraph_stack_int_t path, tracker, edge_tracker, edge_path;
353
0
    igraph_bitset_t visited_list;
354
0
    igraph_vector_int_t degree;
355
356
0
    n = igraph_vcount(graph);
357
0
    m = igraph_ecount(graph);
358
359
0
    if (edge_res) {
360
0
        igraph_vector_int_clear(edge_res);
361
0
    }
362
363
0
    if (vertex_res) {
364
0
        igraph_vector_int_clear(vertex_res);
365
0
    }
366
367
0
    if (m == 0 || n == 0) {
368
0
        return IGRAPH_SUCCESS;
369
0
    }
370
371
0
    IGRAPH_VECTOR_INT_INIT_FINALLY(&degree, 0);
372
0
    IGRAPH_CHECK(igraph_degree(graph, &degree, igraph_vss_all(), IGRAPH_ALL, IGRAPH_LOOPS));
373
374
0
    IGRAPH_STACK_INT_INIT_FINALLY(&path, n);
375
0
    IGRAPH_STACK_INT_INIT_FINALLY(&tracker, n);
376
0
    IGRAPH_STACK_INT_INIT_FINALLY(&edge_path, n);
377
0
    IGRAPH_STACK_INT_INIT_FINALLY(&edge_tracker, n);
378
0
    IGRAPH_BITSET_INIT_FINALLY(&visited_list, m);
379
380
0
    IGRAPH_CHECK(igraph_stack_int_push(&tracker, start_of_path));
381
382
0
    IGRAPH_CHECK(igraph_inclist_init(graph, &il, IGRAPH_OUT, IGRAPH_LOOPS_ONCE));
383
0
    IGRAPH_FINALLY(igraph_inclist_destroy, &il);
384
385
0
    curr = start_of_path;
386
387
0
    while (!igraph_stack_int_empty(&tracker)) {
388
389
0
        if (VECTOR(degree)[curr] != 0) {
390
0
            igraph_vector_int_t *incedges;
391
0
            igraph_int_t nc, edge = -1;
392
0
            igraph_int_t j, next;
393
0
            IGRAPH_CHECK(igraph_stack_int_push(&tracker, curr));
394
395
0
            incedges = igraph_inclist_get(&il, curr);
396
0
            nc = igraph_vector_int_size(incedges);
397
0
            IGRAPH_ASSERT(nc > 0);
398
399
0
            for (j = 0; j < nc; j++) {
400
0
                edge = VECTOR(*incedges)[j];
401
0
                if (!IGRAPH_BIT_TEST(visited_list, edge)) {
402
0
                    break;
403
0
                }
404
0
            }
405
406
0
            next = IGRAPH_OTHER(graph, edge, curr);
407
408
0
            IGRAPH_CHECK(igraph_stack_int_push(&edge_tracker, edge));
409
410
            /* remove edge here */
411
0
            VECTOR(degree)[curr]--;
412
0
            VECTOR(degree)[next]--;
413
0
            IGRAPH_BIT_SET(visited_list, edge);
414
415
0
            curr = next;
416
0
        } else { /* back track to find remaining circuit */
417
0
            igraph_int_t curr_e;
418
0
            IGRAPH_CHECK(igraph_stack_int_push(&path, curr));
419
0
            curr = igraph_stack_int_pop(&tracker);
420
0
            if (!igraph_stack_int_empty(&edge_tracker)) {
421
0
                curr_e = igraph_stack_int_pop(&edge_tracker);
422
0
                IGRAPH_CHECK(igraph_stack_int_push(&edge_path, curr_e));
423
0
            }
424
0
        }
425
0
    }
426
427
0
    if (edge_res) {
428
0
        IGRAPH_CHECK(igraph_vector_int_reserve(edge_res, m));
429
0
        while (!igraph_stack_int_empty(&edge_path)) {
430
0
            IGRAPH_CHECK(igraph_vector_int_push_back(edge_res, igraph_stack_int_pop(&edge_path)));
431
0
        }
432
0
    }
433
0
    if (vertex_res) {
434
0
        IGRAPH_CHECK(igraph_vector_int_reserve(vertex_res, m+1));
435
0
        while (!igraph_stack_int_empty(&path)) {
436
0
            IGRAPH_CHECK(igraph_vector_int_push_back(vertex_res, igraph_stack_int_pop(&path)));
437
0
        }
438
0
    }
439
440
0
    igraph_stack_int_destroy(&path);
441
0
    igraph_stack_int_destroy(&tracker);
442
0
    igraph_stack_int_destroy(&edge_path);
443
0
    igraph_stack_int_destroy(&edge_tracker);
444
0
    igraph_bitset_destroy(&visited_list);
445
0
    igraph_inclist_destroy(&il);
446
0
    igraph_vector_int_destroy(&degree);
447
0
    IGRAPH_FINALLY_CLEAN(7);
448
449
0
    return IGRAPH_SUCCESS;
450
0
}
451
452
/* solution adapted from https://www.geeksforgeeks.org/hierholzers-algorithm-directed-graph/ */
453
static igraph_error_t igraph_i_eulerian_path_directed(
454
        const igraph_t *graph, igraph_vector_int_t *edge_res, igraph_vector_int_t *vertex_res,
455
1.24k
        igraph_int_t start_of_path) {
456
457
1.24k
    igraph_int_t curr;
458
1.24k
    igraph_int_t n, m;
459
1.24k
    igraph_inclist_t il;
460
1.24k
    igraph_stack_int_t path, tracker, edge_tracker, edge_path;
461
1.24k
    igraph_bitset_t visited_list;
462
1.24k
    igraph_vector_int_t remaining_out_edges;
463
464
1.24k
    n = igraph_vcount(graph);
465
1.24k
    m = igraph_ecount(graph);
466
467
1.24k
    if (edge_res) {
468
1.24k
        igraph_vector_int_clear(edge_res);
469
1.24k
    }
470
471
1.24k
    if (vertex_res) {
472
1.24k
        igraph_vector_int_clear(vertex_res);
473
1.24k
    }
474
475
1.24k
    if (m == 0 || n == 0) {
476
236
        return IGRAPH_SUCCESS;
477
236
    }
478
479
1.01k
    IGRAPH_STACK_INT_INIT_FINALLY(&path, n);
480
1.01k
    IGRAPH_STACK_INT_INIT_FINALLY(&tracker, n);
481
1.01k
    IGRAPH_STACK_INT_INIT_FINALLY(&edge_path, n);
482
1.01k
    IGRAPH_STACK_INT_INIT_FINALLY(&edge_tracker, n);
483
1.01k
    IGRAPH_BITSET_INIT_FINALLY(&visited_list, m);
484
485
1.01k
    IGRAPH_CHECK(igraph_stack_int_push(&tracker, start_of_path));
486
487
1.01k
    IGRAPH_CHECK(igraph_inclist_init(graph, &il, IGRAPH_OUT, IGRAPH_LOOPS_ONCE));
488
1.01k
    IGRAPH_FINALLY(igraph_inclist_destroy, &il);
489
490
1.01k
    IGRAPH_VECTOR_INT_INIT_FINALLY(&remaining_out_edges, 0);
491
1.01k
    IGRAPH_CHECK(igraph_degree(graph, &remaining_out_edges, igraph_vss_all(), IGRAPH_OUT, IGRAPH_LOOPS));
492
493
1.01k
    curr = start_of_path;
494
495
48.4k
    while (!igraph_stack_int_empty(&tracker)) {
496
497
47.4k
        if (VECTOR(remaining_out_edges)[curr] != 0) {
498
23.2k
            igraph_vector_int_t *incedges;
499
23.2k
            igraph_int_t nc, edge = -1;
500
23.2k
            igraph_int_t j, next;
501
23.2k
            IGRAPH_CHECK(igraph_stack_int_push(&tracker, curr));
502
503
23.2k
            incedges = igraph_inclist_get(&il, curr);
504
23.2k
            nc = igraph_vector_int_size(incedges);
505
23.2k
            IGRAPH_ASSERT(nc > 0);
506
507
952k
            for (j = 0; j < nc; j++) {
508
952k
                edge = VECTOR(*incedges)[j];
509
952k
                if (!IGRAPH_BIT_TEST(visited_list, edge)) {
510
23.2k
                    break;
511
23.2k
                }
512
952k
            }
513
514
23.2k
            next = IGRAPH_TO(graph, edge);
515
516
23.2k
            IGRAPH_CHECK(igraph_stack_int_push(&edge_tracker, edge));
517
518
            /* remove edge here */
519
23.2k
            VECTOR(remaining_out_edges)[curr]--;
520
23.2k
            IGRAPH_BIT_SET(visited_list, edge);
521
522
23.2k
            curr = next;
523
24.2k
        } else { /* back track to find remaining circuit */
524
24.2k
            igraph_int_t curr_e;
525
24.2k
            IGRAPH_CHECK(igraph_stack_int_push(&path, curr));
526
24.2k
            curr = igraph_stack_int_pop(&tracker);
527
24.2k
            if (!igraph_stack_int_empty(&edge_tracker)) {
528
23.2k
                curr_e = igraph_stack_int_pop(&edge_tracker);
529
23.2k
                IGRAPH_CHECK(igraph_stack_int_push(&edge_path, curr_e));
530
23.2k
            }
531
24.2k
        }
532
47.4k
    }
533
534
1.01k
    if (edge_res) {
535
1.01k
        IGRAPH_CHECK(igraph_vector_int_reserve(edge_res, m));
536
24.2k
        while (!igraph_stack_int_empty(&edge_path)) {
537
23.2k
            IGRAPH_CHECK(igraph_vector_int_push_back(edge_res, igraph_stack_int_pop(&edge_path)));
538
23.2k
        }
539
1.01k
    }
540
1.01k
    if (vertex_res) {
541
1.01k
        IGRAPH_CHECK(igraph_vector_int_reserve(vertex_res, m+1));
542
25.2k
        while (!igraph_stack_int_empty(&path)) {
543
24.2k
            IGRAPH_CHECK(igraph_vector_int_push_back(vertex_res, igraph_stack_int_pop(&path)));
544
24.2k
        }
545
1.01k
    }
546
547
1.01k
    igraph_stack_int_destroy(&path);
548
1.01k
    igraph_stack_int_destroy(&tracker);
549
1.01k
    igraph_stack_int_destroy(&edge_path);
550
1.01k
    igraph_stack_int_destroy(&edge_tracker);
551
1.01k
    igraph_bitset_destroy(&visited_list);
552
1.01k
    igraph_inclist_destroy(&il);
553
1.01k
    igraph_vector_int_destroy(&remaining_out_edges);
554
1.01k
    IGRAPH_FINALLY_CLEAN(7);
555
556
1.01k
    return IGRAPH_SUCCESS;
557
1.01k
}
558
559
560
/**
561
 * \ingroup Eulerian
562
 * \function igraph_eulerian_cycle
563
 * \brief Finds an Eulerian cycle.
564
 *
565
 * Finds an Eulerian cycle, if it exists. An Eulerian cycle is a closed path
566
 * that traverses each edge precisely once.
567
 *
568
 * </para><para>
569
 * If the graph has no edges, a zero-length cycle is returned.
570
 *
571
 * </para><para>
572
 * This function uses Hierholzer's algorithm.
573
 *
574
 * \param graph The graph object.
575
 * \param edge_res Pointer to an initialised vector. The indices of edges
576
 *                 belonging to the cycle will be stored here. May be \c NULL
577
 *                 if it is not needed by the caller.
578
 * \param vertex_res Pointer to an initialised vector. The indices of vertices
579
 *                   belonging to the cycle will be stored here. The first and
580
 *                   last vertex in the vector will be the same. May be \c NULL
581
 *                   if it is not needed by the caller.
582
 * \return Error code:
583
 *        \clist
584
 *        \cli IGRAPH_ENOMEM
585
 *           not enough memory for temporary data.
586
 *        \cli IGRAPH_ENOSOL
587
 *           graph does not have an Eulerian cycle.
588
 *        \endclist
589
 *
590
 * Time complexity: O(|V|+|E|), the number of vertices plus the number of edges.
591
 *
592
 */
593
594
igraph_error_t igraph_eulerian_cycle(
595
418
        const igraph_t *graph, igraph_vector_int_t *edge_res, igraph_vector_int_t *vertex_res) {
596
597
418
    igraph_bool_t has_cycle;
598
418
    igraph_bool_t has_path;
599
418
    igraph_int_t start_of_path = 0;
600
601
418
    if (igraph_is_directed(graph)) {
602
418
        IGRAPH_CHECK(igraph_i_is_eulerian_directed(graph, &has_path, &has_cycle, &start_of_path));
603
604
418
        if (!has_cycle) {
605
0
            IGRAPH_ERROR("The graph does not have an Eulerian cycle.", IGRAPH_ENOSOL);
606
0
        }
607
608
418
        IGRAPH_CHECK(igraph_i_eulerian_path_directed(graph, edge_res, vertex_res, start_of_path));
609
418
    } else {
610
0
        IGRAPH_CHECK(igraph_i_is_eulerian_undirected(graph, &has_path, &has_cycle, &start_of_path));
611
612
0
        if (!has_cycle) {
613
0
            IGRAPH_ERROR("The graph does not have an Eulerian cycle.", IGRAPH_ENOSOL);
614
0
        }
615
616
0
        IGRAPH_CHECK(igraph_i_eulerian_path_undirected(graph, edge_res, vertex_res, start_of_path));
617
0
    }
618
619
418
    return IGRAPH_SUCCESS;
620
418
}
621
622
623
/**
624
 * \ingroup Eulerian
625
 * \function igraph_eulerian_path
626
 * \brief Finds an Eulerian path.
627
 *
628
 * Finds an Eulerian path, if it exists. An Eulerian path traverses
629
 * each edge precisely once.
630
 *
631
 * </para><para>
632
 * If the graph has no edges, a zero-length path is returned.
633
 *
634
 * </para><para>
635
 * This function uses Hierholzer's algorithm.
636
 *
637
 * \param graph The graph object.
638
 * \param edge_res Pointer to an initialised vector. The indices of edges
639
 *                 belonging to the path will be stored here. May be \c NULL
640
 *                 if it is not needed by the caller.
641
 * \param vertex_res Pointer to an initialised vector. The indices of vertices
642
 *                   belonging to the path will be stored here. May be \c NULL
643
 *                   if it is not needed by the caller.
644
 * \return Error code:
645
 *        \clist
646
 *        \cli IGRAPH_ENOMEM
647
 *           not enough memory for temporary data.
648
 *        \cli IGRAPH_ENOSOL
649
 *           graph does not have an Eulerian path.
650
 *        \endclist
651
 *
652
 * Time complexity: O(|V|+|E|), the number of vertices plus the number of edges.
653
 *
654
 */
655
656
igraph_error_t igraph_eulerian_path(
657
831
        const igraph_t *graph, igraph_vector_int_t *edge_res, igraph_vector_int_t *vertex_res) {
658
659
831
    igraph_bool_t has_cycle;
660
831
    igraph_bool_t has_path;
661
831
    igraph_int_t start_of_path = 0;
662
663
831
    if (igraph_is_directed(graph)) {
664
831
        IGRAPH_CHECK(igraph_i_is_eulerian_directed(graph, &has_path, &has_cycle, &start_of_path));
665
666
831
        if (!has_path) {
667
0
            IGRAPH_ERROR("The graph does not have an Eulerian path.", IGRAPH_ENOSOL);
668
0
        }
669
831
        IGRAPH_CHECK(igraph_i_eulerian_path_directed(graph, edge_res, vertex_res, start_of_path));
670
831
    } else {
671
0
        IGRAPH_CHECK(igraph_i_is_eulerian_undirected(graph, &has_path, &has_cycle, &start_of_path));
672
673
0
        if (!has_path) {
674
0
            IGRAPH_ERROR("The graph does not have an Eulerian path.", IGRAPH_ENOSOL);
675
0
        }
676
677
0
        IGRAPH_CHECK(igraph_i_eulerian_path_undirected(graph, edge_res, vertex_res, start_of_path));
678
0
    }
679
680
831
    return IGRAPH_SUCCESS;
681
831
}