Coverage Report

Created: 2025-07-18 07:08

/src/igraph/src/paths/eulerian.c
Line
Count
Source (jump to first uncovered line)
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
3.70k
        const igraph_t *graph, igraph_bool_t *has_path, igraph_bool_t *has_cycle, igraph_integer_t *start_of_path) {
44
3.70k
    igraph_integer_t odd;
45
3.70k
    igraph_vector_int_t degree;
46
3.70k
    igraph_vector_int_t csize;
47
    /* boolean vector to mark singletons: */
48
3.70k
    igraph_vector_int_t nonsingleton;
49
3.70k
    igraph_integer_t i, n, vsize;
50
3.70k
    igraph_integer_t cluster_count;
51
    /* number of self-looping singletons: */
52
3.70k
    igraph_integer_t es;
53
    /* will be set to 1 if there are non-isolated vertices, otherwise 0: */
54
3.70k
    igraph_integer_t ens;
55
56
3.70k
    n = igraph_vcount(graph);
57
58
3.70k
    if (igraph_ecount(graph) == 0 || n <= 1) {
59
366
        start_of_path = 0; /* in case the graph has one vertex with self-loops */
60
366
        *has_path = true;
61
366
        *has_cycle = true;
62
366
        return  IGRAPH_SUCCESS;
63
366
    }
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
3.34k
    IGRAPH_VECTOR_INT_INIT_FINALLY(&csize, 0);
69
3.34k
    IGRAPH_CHECK(igraph_connected_components(graph, NULL, &csize, NULL, IGRAPH_WEAK));
70
3.34k
    cluster_count = 0;
71
3.34k
    vsize = igraph_vector_int_size(&csize);
72
384k
    for (i = 0; i < vsize; i++) {
73
381k
        if (VECTOR(csize)[i] > 1) {
74
3.43k
            cluster_count++;
75
3.43k
            if (cluster_count > 1) {
76
                /* disconnected edges, they'll never reach each other */
77
526
                *has_path = false;
78
526
                *has_cycle = false;
79
526
                igraph_vector_int_destroy(&csize);
80
526
                IGRAPH_FINALLY_CLEAN(1);
81
82
526
                return IGRAPH_SUCCESS;
83
526
            }
84
3.43k
        }
85
381k
    }
86
87
2.81k
    igraph_vector_int_destroy(&csize);
88
2.81k
    IGRAPH_FINALLY_CLEAN(1);
89
90
    /* the graph is connected except for singletons */
91
    /* find singletons (including those with self-loops) */
92
2.81k
    IGRAPH_VECTOR_INT_INIT_FINALLY(&nonsingleton, 0);
93
2.81k
    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
2.81k
    IGRAPH_VECTOR_INT_INIT_FINALLY(&degree, 0);
101
2.81k
    IGRAPH_CHECK(igraph_degree(graph, &degree, igraph_vss_all(), IGRAPH_ALL, IGRAPH_LOOPS));
102
2.81k
    odd = 0;
103
2.81k
    es = 0;
104
2.81k
    ens = 0;
105
393k
    for (i = 0; i < n; i++) {
106
390k
        igraph_integer_t deg = VECTOR(degree)[i];
107
        /* Eulerian is about edges, so skip free vertices */
108
390k
        if (deg == 0) continue;
109
110
36.8k
        if (!VECTOR(nonsingleton)[i]) {
111
            /* singleton with self loops */
112
520
            es++;
113
36.3k
        } else {
114
            /* at least one non-singleton */
115
36.3k
            ens = 1;
116
            /* note: self-loops count for two (in and out) */
117
36.3k
            if (deg % 2) odd++;
118
36.3k
        }
119
120
36.8k
        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
86
            *has_path = false;
124
86
            *has_cycle = false;
125
86
            igraph_vector_int_destroy(&nonsingleton);
126
86
            igraph_vector_int_destroy(&degree);
127
86
            IGRAPH_FINALLY_CLEAN(2);
128
129
86
            return IGRAPH_SUCCESS;
130
86
        }
131
36.8k
    }
132
133
2.72k
    igraph_vector_int_destroy(&nonsingleton);
134
2.72k
    IGRAPH_FINALLY_CLEAN(1);
135
136
    /* this is the usual algorithm on the connected part of the graph */
137
2.72k
    if (odd > 2) {
138
670
        *has_path = false;
139
670
        *has_cycle = false;
140
2.05k
    } else if (odd == 2) {
141
1.05k
        *has_path = true;
142
1.05k
        *has_cycle = false;
143
1.05k
    } else {
144
1.00k
        *has_path = true;
145
1.00k
        *has_cycle = true;
146
1.00k
    }
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
72.7k
    for (i = 0; i < n; i++) {
152
72.7k
        if ((*has_cycle && VECTOR(degree)[i] > 0) || (!*has_cycle && VECTOR(degree)[i] %2 == 1)) {
153
2.72k
            *start_of_path = i;
154
2.72k
            break;
155
2.72k
        }
156
72.7k
    }
157
158
2.72k
    igraph_vector_int_destroy(&degree);
159
2.72k
    IGRAPH_FINALLY_CLEAN(1);
160
161
2.72k
    return IGRAPH_SUCCESS;
162
2.81k
}
163
164
165
static igraph_error_t igraph_i_is_eulerian_directed(
166
3.39k
        const igraph_t *graph, igraph_bool_t *has_path, igraph_bool_t *has_cycle, igraph_integer_t *start_of_path) {
167
3.39k
    igraph_integer_t incoming_excess, outgoing_excess, n;
168
3.39k
    igraph_integer_t i, vsize;
169
3.39k
    igraph_integer_t cluster_count;
170
3.39k
    igraph_vector_int_t out_degree, in_degree;
171
3.39k
    igraph_vector_int_t csize;
172
    /* boolean vector to mark singletons: */
173
3.39k
    igraph_vector_int_t nonsingleton;
174
    /* number of self-looping singletons: */
175
3.39k
    igraph_integer_t es;
176
    /* will be set to 1 if there are non-isolated vertices, otherwise 0: */
177
3.39k
    igraph_integer_t ens;
178
179
3.39k
    n = igraph_vcount(graph);
180
181
3.39k
    if (igraph_ecount(graph) == 0 || n <= 1) {
182
427
        start_of_path = 0; /* in case the graph has one vertex with self-loops */
183
427
        *has_path = true;
184
427
        *has_cycle = true;
185
427
        return IGRAPH_SUCCESS;
186
427
    }
187
188
2.96k
    incoming_excess = 0;
189
2.96k
    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.96k
    IGRAPH_VECTOR_INT_INIT_FINALLY(&csize, 0);
195
196
2.96k
    IGRAPH_CHECK(igraph_connected_components(graph, NULL, &csize, NULL, IGRAPH_WEAK));
197
2.96k
    cluster_count = 0;
198
2.96k
    vsize = igraph_vector_int_size(&csize);
199
351k
    for (i = 0; i < vsize; i++) {
200
349k
        if (VECTOR(csize)[i] > 1) {
201
3.28k
            cluster_count++;
202
3.28k
            if (cluster_count > 1) {
203
                /* weakly disconnected edges, they'll never reach each other */
204
501
                *has_path = false;
205
501
                *has_cycle = false;
206
501
                igraph_vector_int_destroy(&csize);
207
501
                IGRAPH_FINALLY_CLEAN(1);
208
209
501
                return IGRAPH_SUCCESS;
210
501
            }
211
3.28k
        }
212
349k
    }
213
214
2.46k
    igraph_vector_int_destroy(&csize);
215
2.46k
    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.46k
    IGRAPH_VECTOR_INT_INIT_FINALLY(&nonsingleton, 0);
220
2.46k
    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.46k
    IGRAPH_VECTOR_INT_INIT_FINALLY(&out_degree, 0);
226
2.46k
    IGRAPH_CHECK(igraph_degree(graph, &out_degree, igraph_vss_all(), IGRAPH_OUT, IGRAPH_LOOPS));
227
228
2.46k
    IGRAPH_VECTOR_INT_INIT_FINALLY(&in_degree, 0);
229
2.46k
    IGRAPH_CHECK(igraph_degree(graph, &in_degree, igraph_vss_all(), IGRAPH_IN, IGRAPH_LOOPS));
230
2.46k
    es = 0;
231
2.46k
    ens = 0;
232
2.46k
    *start_of_path = -1;
233
246k
    for (i = 0; i < n; i++) {
234
244k
        igraph_integer_t degin = VECTOR(in_degree)[i];
235
244k
        igraph_integer_t degout = VECTOR(out_degree)[i];
236
237
        /* Eulerian is about edges, so skip free vertices */
238
244k
        if (degin + degout == 0) continue;
239
240
17.4k
        if (!VECTOR(nonsingleton)[i]) {
241
            /* singleton with self loops */
242
233
            es++;
243
            /* if we ever want a path, it has to be this self-loop */
244
233
            *start_of_path = i;
245
17.2k
        } else {
246
            /* at least one non-singleton */
247
17.2k
            ens = 1;
248
17.2k
        }
249
250
17.4k
        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
52
            *has_path = false;
254
52
            *has_cycle = false;
255
52
            igraph_vector_int_destroy(&nonsingleton);
256
52
            igraph_vector_int_destroy(&in_degree);
257
52
            igraph_vector_int_destroy(&out_degree);
258
52
            IGRAPH_FINALLY_CLEAN(3);
259
260
52
            return IGRAPH_SUCCESS;
261
52
        }
262
263
        /* as long as we have perfect balance, you can start
264
         * anywhere with an edge */
265
17.4k
        if (*start_of_path == -1 && incoming_excess == 0 && outgoing_excess == 0) {
266
2.26k
            *start_of_path = i;
267
2.26k
        }
268
269
        /* same in and out (including self-loops, even in singletons) */
270
17.4k
        if (degin == degout) {
271
14.7k
            continue;
272
14.7k
        }
273
274
        /* non-singleton, in != out */
275
2.70k
        if (degin > degout) {
276
1.30k
            incoming_excess += degin - degout;
277
1.39k
        } else {
278
1.39k
            outgoing_excess += degout - degin;
279
1.39k
            if (outgoing_excess == 1) {
280
910
                *start_of_path = i;
281
910
            }
282
1.39k
        }
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.70k
        if (incoming_excess > 1 || outgoing_excess > 1) {
288
865
            *has_path = false;
289
865
            *has_cycle = false;
290
865
            igraph_vector_int_destroy(&nonsingleton);
291
865
            igraph_vector_int_destroy(&in_degree);
292
865
            igraph_vector_int_destroy(&out_degree);
293
865
            IGRAPH_FINALLY_CLEAN(3);
294
295
865
            return IGRAPH_SUCCESS;
296
865
        }
297
2.70k
    }
298
299
1.54k
    *has_path = true;
300
    /* perfect edge balance -> strong connectivity */
301
1.54k
    *has_cycle = (incoming_excess == 0) && (outgoing_excess == 0);
302
    /* either way, the start was set already */
303
304
1.54k
    igraph_vector_int_destroy(&nonsingleton);
305
1.54k
    igraph_vector_int_destroy(&in_degree);
306
1.54k
    igraph_vector_int_destroy(&out_degree);
307
1.54k
    IGRAPH_FINALLY_CLEAN(3);
308
309
1.54k
    return IGRAPH_SUCCESS;
310
2.46k
}
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
4.78k
igraph_error_t igraph_is_eulerian(const igraph_t *graph, igraph_bool_t *has_path, igraph_bool_t *has_cycle) {
334
4.78k
    igraph_integer_t start_of_path = 0;
335
336
4.78k
    if (igraph_is_directed(graph)) {
337
2.32k
        IGRAPH_CHECK(igraph_i_is_eulerian_directed(graph, has_path, has_cycle, &start_of_path));
338
2.45k
    } else {
339
2.45k
        IGRAPH_CHECK(igraph_i_is_eulerian_undirected(graph, has_path, has_cycle, &start_of_path));
340
2.45k
    }
341
4.78k
    return IGRAPH_SUCCESS;
342
4.78k
}
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
1.25k
        igraph_integer_t start_of_path) {
348
349
1.25k
    igraph_integer_t curr;
350
1.25k
    igraph_integer_t n, m;
351
1.25k
    igraph_inclist_t il;
352
1.25k
    igraph_stack_int_t path, tracker, edge_tracker, edge_path;
353
1.25k
    igraph_bitset_t visited_list;
354
1.25k
    igraph_vector_int_t degree;
355
356
1.25k
    n = igraph_vcount(graph);
357
1.25k
    m = igraph_ecount(graph);
358
359
1.25k
    if (edge_res) {
360
1.25k
        igraph_vector_int_clear(edge_res);
361
1.25k
    }
362
363
1.25k
    if (vertex_res) {
364
1.25k
        igraph_vector_int_clear(vertex_res);
365
1.25k
    }
366
367
1.25k
    if (m == 0 || n == 0) {
368
182
        return IGRAPH_SUCCESS;
369
182
    }
370
371
1.06k
    IGRAPH_VECTOR_INT_INIT_FINALLY(&degree, 0);
372
1.06k
    IGRAPH_CHECK(igraph_degree(graph, &degree, igraph_vss_all(), IGRAPH_ALL, IGRAPH_LOOPS));
373
374
1.06k
    IGRAPH_STACK_INT_INIT_FINALLY(&path, n);
375
1.06k
    IGRAPH_STACK_INT_INIT_FINALLY(&tracker, n);
376
1.06k
    IGRAPH_STACK_INT_INIT_FINALLY(&edge_path, n);
377
1.06k
    IGRAPH_STACK_INT_INIT_FINALLY(&edge_tracker, n);
378
1.06k
    IGRAPH_BITSET_INIT_FINALLY(&visited_list, m);
379
380
1.06k
    IGRAPH_CHECK(igraph_stack_int_push(&tracker, start_of_path));
381
382
1.06k
    IGRAPH_CHECK(igraph_inclist_init(graph, &il, IGRAPH_OUT, IGRAPH_LOOPS_ONCE));
383
1.06k
    IGRAPH_FINALLY(igraph_inclist_destroy, &il);
384
385
1.06k
    curr = start_of_path;
386
387
53.9k
    while (!igraph_stack_int_empty(&tracker)) {
388
389
52.9k
        if (VECTOR(degree)[curr] != 0) {
390
25.9k
            igraph_vector_int_t *incedges;
391
25.9k
            igraph_integer_t nc, edge = -1;
392
25.9k
            igraph_integer_t j, next;
393
25.9k
            IGRAPH_CHECK(igraph_stack_int_push(&tracker, curr));
394
395
25.9k
            incedges = igraph_inclist_get(&il, curr);
396
25.9k
            nc = igraph_vector_int_size(incedges);
397
25.9k
            IGRAPH_ASSERT(nc > 0);
398
399
1.24M
            for (j = 0; j < nc; j++) {
400
1.24M
                edge = VECTOR(*incedges)[j];
401
1.24M
                if (!IGRAPH_BIT_TEST(visited_list, edge)) {
402
25.9k
                    break;
403
25.9k
                }
404
1.24M
            }
405
406
25.9k
            next = IGRAPH_OTHER(graph, edge, curr);
407
408
25.9k
            IGRAPH_CHECK(igraph_stack_int_push(&edge_tracker, edge));
409
410
            /* remove edge here */
411
25.9k
            VECTOR(degree)[curr]--;
412
25.9k
            VECTOR(degree)[next]--;
413
25.9k
            IGRAPH_BIT_SET(visited_list, edge);
414
415
25.9k
            curr = next;
416
26.9k
        } else { /* back track to find remaining circuit */
417
26.9k
            igraph_integer_t curr_e;
418
26.9k
            IGRAPH_CHECK(igraph_stack_int_push(&path, curr));
419
26.9k
            curr = igraph_stack_int_pop(&tracker);
420
26.9k
            if (!igraph_stack_int_empty(&edge_tracker)) {
421
25.9k
                curr_e = igraph_stack_int_pop(&edge_tracker);
422
25.9k
                IGRAPH_CHECK(igraph_stack_int_push(&edge_path, curr_e));
423
25.9k
            }
424
26.9k
        }
425
52.9k
    }
426
427
1.06k
    if (edge_res) {
428
1.06k
        IGRAPH_CHECK(igraph_vector_int_reserve(edge_res, m));
429
26.9k
        while (!igraph_stack_int_empty(&edge_path)) {
430
25.9k
            IGRAPH_CHECK(igraph_vector_int_push_back(edge_res, igraph_stack_int_pop(&edge_path)));
431
25.9k
        }
432
1.06k
    }
433
1.06k
    if (vertex_res) {
434
1.06k
        IGRAPH_CHECK(igraph_vector_int_reserve(vertex_res, m+1));
435
28.0k
        while (!igraph_stack_int_empty(&path)) {
436
26.9k
            IGRAPH_CHECK(igraph_vector_int_push_back(vertex_res, igraph_stack_int_pop(&path)));
437
26.9k
        }
438
1.06k
    }
439
440
1.06k
    igraph_stack_int_destroy(&path);
441
1.06k
    igraph_stack_int_destroy(&tracker);
442
1.06k
    igraph_stack_int_destroy(&edge_path);
443
1.06k
    igraph_stack_int_destroy(&edge_tracker);
444
1.06k
    igraph_bitset_destroy(&visited_list);
445
1.06k
    igraph_inclist_destroy(&il);
446
1.06k
    igraph_vector_int_destroy(&degree);
447
1.06k
    IGRAPH_FINALLY_CLEAN(7);
448
449
1.06k
    return IGRAPH_SUCCESS;
450
1.06k
}
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.06k
        igraph_integer_t start_of_path) {
456
457
1.06k
    igraph_integer_t curr;
458
1.06k
    igraph_integer_t n, m;
459
1.06k
    igraph_inclist_t il;
460
1.06k
    igraph_stack_int_t path, tracker, edge_tracker, edge_path;
461
1.06k
    igraph_bitset_t visited_list;
462
1.06k
    igraph_vector_int_t remaining_out_edges;
463
464
1.06k
    n = igraph_vcount(graph);
465
1.06k
    m = igraph_ecount(graph);
466
467
1.06k
    if (edge_res) {
468
1.06k
        igraph_vector_int_clear(edge_res);
469
1.06k
    }
470
471
1.06k
    if (vertex_res) {
472
1.06k
        igraph_vector_int_clear(vertex_res);
473
1.06k
    }
474
475
1.06k
    if (m == 0 || n == 0) {
476
210
        return IGRAPH_SUCCESS;
477
210
    }
478
479
851
    IGRAPH_STACK_INT_INIT_FINALLY(&path, n);
480
851
    IGRAPH_STACK_INT_INIT_FINALLY(&tracker, n);
481
851
    IGRAPH_STACK_INT_INIT_FINALLY(&edge_path, n);
482
851
    IGRAPH_STACK_INT_INIT_FINALLY(&edge_tracker, n);
483
851
    IGRAPH_BITSET_INIT_FINALLY(&visited_list, m);
484
485
851
    IGRAPH_CHECK(igraph_stack_int_push(&tracker, start_of_path));
486
487
851
    IGRAPH_CHECK(igraph_inclist_init(graph, &il, IGRAPH_OUT, IGRAPH_LOOPS_ONCE));
488
851
    IGRAPH_FINALLY(igraph_inclist_destroy, &il);
489
490
851
    IGRAPH_VECTOR_INT_INIT_FINALLY(&remaining_out_edges, 0);
491
851
    IGRAPH_CHECK(igraph_degree(graph, &remaining_out_edges, igraph_vss_all(), IGRAPH_OUT, IGRAPH_LOOPS));
492
493
851
    curr = start_of_path;
494
495
43.0k
    while (!igraph_stack_int_empty(&tracker)) {
496
497
42.2k
        if (VECTOR(remaining_out_edges)[curr] != 0) {
498
20.6k
            igraph_vector_int_t *incedges;
499
20.6k
            igraph_integer_t nc, edge = -1;
500
20.6k
            igraph_integer_t j, next;
501
20.6k
            IGRAPH_CHECK(igraph_stack_int_push(&tracker, curr));
502
503
20.6k
            incedges = igraph_inclist_get(&il, curr);
504
20.6k
            nc = igraph_vector_int_size(incedges);
505
20.6k
            IGRAPH_ASSERT(nc > 0);
506
507
791k
            for (j = 0; j < nc; j++) {
508
791k
                edge = VECTOR(*incedges)[j];
509
791k
                if (!IGRAPH_BIT_TEST(visited_list, edge)) {
510
20.6k
                    break;
511
20.6k
                }
512
791k
            }
513
514
20.6k
            next = IGRAPH_TO(graph, edge);
515
516
20.6k
            IGRAPH_CHECK(igraph_stack_int_push(&edge_tracker, edge));
517
518
            /* remove edge here */
519
20.6k
            VECTOR(remaining_out_edges)[curr]--;
520
20.6k
            IGRAPH_BIT_SET(visited_list, edge);
521
522
20.6k
            curr = next;
523
21.5k
        } else { /* back track to find remaining circuit */
524
21.5k
            igraph_integer_t curr_e;
525
21.5k
            IGRAPH_CHECK(igraph_stack_int_push(&path, curr));
526
21.5k
            curr = igraph_stack_int_pop(&tracker);
527
21.5k
            if (!igraph_stack_int_empty(&edge_tracker)) {
528
20.6k
                curr_e = igraph_stack_int_pop(&edge_tracker);
529
20.6k
                IGRAPH_CHECK(igraph_stack_int_push(&edge_path, curr_e));
530
20.6k
            }
531
21.5k
        }
532
42.2k
    }
533
534
851
    if (edge_res) {
535
851
        IGRAPH_CHECK(igraph_vector_int_reserve(edge_res, m));
536
21.5k
        while (!igraph_stack_int_empty(&edge_path)) {
537
20.6k
            IGRAPH_CHECK(igraph_vector_int_push_back(edge_res, igraph_stack_int_pop(&edge_path)));
538
20.6k
        }
539
851
    }
540
851
    if (vertex_res) {
541
851
        IGRAPH_CHECK(igraph_vector_int_reserve(vertex_res, m+1));
542
22.3k
        while (!igraph_stack_int_empty(&path)) {
543
21.5k
            IGRAPH_CHECK(igraph_vector_int_push_back(vertex_res, igraph_stack_int_pop(&path)));
544
21.5k
        }
545
851
    }
546
547
851
    igraph_stack_int_destroy(&path);
548
851
    igraph_stack_int_destroy(&tracker);
549
851
    igraph_stack_int_destroy(&edge_path);
550
851
    igraph_stack_int_destroy(&edge_tracker);
551
851
    igraph_bitset_destroy(&visited_list);
552
851
    igraph_inclist_destroy(&il);
553
851
    igraph_vector_int_destroy(&remaining_out_edges);
554
851
    IGRAPH_FINALLY_CLEAN(7);
555
556
851
    return IGRAPH_SUCCESS;
557
851
}
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
789
        const igraph_t *graph, igraph_vector_int_t *edge_res, igraph_vector_int_t *vertex_res) {
596
597
789
    igraph_bool_t has_cycle;
598
789
    igraph_bool_t has_path;
599
789
    igraph_integer_t start_of_path = 0;
600
601
789
    if (igraph_is_directed(graph)) {
602
381
        IGRAPH_CHECK(igraph_i_is_eulerian_directed(graph, &has_path, &has_cycle, &start_of_path));
603
604
381
        if (!has_cycle) {
605
0
            IGRAPH_ERROR("The graph does not have an Eulerian cycle.", IGRAPH_ENOSOL);
606
0
        }
607
608
381
        IGRAPH_CHECK(igraph_i_eulerian_path_directed(graph, edge_res, vertex_res, start_of_path));
609
408
    } else {
610
408
        IGRAPH_CHECK(igraph_i_is_eulerian_undirected(graph, &has_path, &has_cycle, &start_of_path));
611
612
408
        if (!has_cycle) {
613
0
            IGRAPH_ERROR("The graph does not have an Eulerian cycle.", IGRAPH_ENOSOL);
614
0
        }
615
616
408
        IGRAPH_CHECK(igraph_i_eulerian_path_undirected(graph, edge_res, vertex_res, start_of_path));
617
408
    }
618
619
789
    return IGRAPH_SUCCESS;
620
789
}
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
1.52k
        const igraph_t *graph, igraph_vector_int_t *edge_res, igraph_vector_int_t *vertex_res) {
658
659
1.52k
    igraph_bool_t has_cycle;
660
1.52k
    igraph_bool_t has_path;
661
1.52k
    igraph_integer_t start_of_path = 0;
662
663
1.52k
    if (igraph_is_directed(graph)) {
664
680
        IGRAPH_CHECK(igraph_i_is_eulerian_directed(graph, &has_path, &has_cycle, &start_of_path));
665
666
680
        if (!has_path) {
667
0
            IGRAPH_ERROR("The graph does not have an Eulerian path.", IGRAPH_ENOSOL);
668
0
        }
669
680
        IGRAPH_CHECK(igraph_i_eulerian_path_directed(graph, edge_res, vertex_res, start_of_path));
670
842
    } else {
671
842
        IGRAPH_CHECK(igraph_i_is_eulerian_undirected(graph, &has_path, &has_cycle, &start_of_path));
672
673
842
        if (!has_path) {
674
0
            IGRAPH_ERROR("The graph does not have an Eulerian path.", IGRAPH_ENOSOL);
675
0
        }
676
677
842
        IGRAPH_CHECK(igraph_i_eulerian_path_undirected(graph, edge_res, vertex_res, start_of_path));
678
842
    }
679
680
1.52k
    return IGRAPH_SUCCESS;
681
1.52k
}