Coverage Report

Created: 2025-11-14 06:33

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/igraph/src/properties/trees.c
Line
Count
Source
1
/*
2
   igraph library.
3
   Copyright (C) 2005-2021 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
22
#include "igraph_structural.h"
23
24
#include "igraph_bitset.h"
25
#include "igraph_constructors.h"
26
#include "igraph_cycles.h"
27
#include "igraph_dqueue.h"
28
#include "igraph_interface.h"
29
#include "igraph_stack.h"
30
31
/**
32
 * \function igraph_unfold_tree
33
 * \brief Unfolding a graph into a tree, by possibly multiplicating its vertices.
34
 *
35
 * A graph is converted into a tree (or forest, if it is unconnected),
36
 * by performing a breadth-first search on it, and replicating
37
 * vertices that were found a second, third, etc. time.
38
 *
39
 * \param graph The input graph, it can be either directed or
40
 *   undirected.
41
 * \param tree Pointer to an uninitialized graph object, the result is
42
 *   stored here.
43
 * \param mode For directed graphs; whether to follow paths along edge
44
 *    directions (\c IGRAPH_OUT), or the opposite (\c IGRAPH_IN), or
45
 *    ignore edge directions completely (\c IGRAPH_ALL). It is ignored
46
 *    for undirected graphs.
47
 * \param roots A numeric vector giving the root vertex, or vertices
48
 *   (if the graph is not connected), to start from.
49
 * \param vertex_index Pointer to an initialized vector, or a null
50
 *   pointer. If not a null pointer, then a mapping from the vertices
51
 *   in the new graph to the ones in the original is created here.
52
 * \return Error code.
53
 *
54
 * Time complexity: O(n+m), linear in the number vertices and edges.
55
 *
56
 */
57
igraph_error_t igraph_unfold_tree(const igraph_t *graph, igraph_t *tree,
58
                       igraph_neimode_t mode, const igraph_vector_int_t *roots,
59
0
                       igraph_vector_int_t *vertex_index) {
60
61
0
    igraph_int_t no_of_nodes = igraph_vcount(graph);
62
0
    igraph_int_t no_of_edges = igraph_ecount(graph);
63
0
    igraph_int_t no_of_roots = igraph_vector_int_size(roots);
64
0
    igraph_int_t tree_vertex_count = no_of_nodes;
65
66
0
    igraph_vector_int_t edges;
67
0
    igraph_bitset_t seen_vertices;
68
0
    igraph_bitset_t seen_edges;
69
70
0
    igraph_dqueue_int_t Q;
71
0
    igraph_vector_int_t neis;
72
73
0
    igraph_int_t v_ptr = no_of_nodes;
74
75
0
    if (! igraph_vector_int_isininterval(roots, 0, no_of_nodes-1)) {
76
0
        IGRAPH_ERROR("All roots should be vertices of the graph.", IGRAPH_EINVVID);
77
0
    }
78
79
0
    IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 0);
80
0
    IGRAPH_CHECK(igraph_vector_int_reserve(&edges, no_of_edges * 2));
81
0
    IGRAPH_DQUEUE_INT_INIT_FINALLY(&Q, 100);
82
0
    IGRAPH_VECTOR_INT_INIT_FINALLY(&neis, 0);
83
0
    IGRAPH_BITSET_INIT_FINALLY(&seen_vertices, no_of_nodes);
84
0
    IGRAPH_BITSET_INIT_FINALLY(&seen_edges, no_of_edges);
85
86
0
    if (vertex_index) {
87
0
        IGRAPH_CHECK(igraph_vector_int_range(vertex_index, 0, no_of_nodes));
88
0
    }
89
90
0
    for (igraph_int_t r = 0; r < no_of_roots; r++) {
91
92
0
        igraph_int_t root = VECTOR(*roots)[r];
93
0
        IGRAPH_BIT_SET(seen_vertices, root);
94
0
        IGRAPH_CHECK(igraph_dqueue_int_push(&Q, root));
95
96
0
        while (!igraph_dqueue_int_empty(&Q)) {
97
0
            igraph_int_t actnode = igraph_dqueue_int_pop(&Q);
98
99
0
            IGRAPH_CHECK(igraph_incident(graph, &neis, actnode, mode, IGRAPH_LOOPS));
100
101
0
            igraph_int_t n = igraph_vector_int_size(&neis);
102
0
            for (igraph_int_t i = 0; i < n; i++) {
103
104
0
                igraph_int_t edge = VECTOR(neis)[i];
105
0
                igraph_int_t from = IGRAPH_FROM(graph, edge);
106
0
                igraph_int_t to = IGRAPH_TO(graph, edge);
107
0
                igraph_int_t nei = IGRAPH_OTHER(graph, edge, actnode);
108
109
0
                if (! IGRAPH_BIT_TEST(seen_edges, edge)) {
110
111
0
                    IGRAPH_BIT_SET(seen_edges, edge);
112
113
0
                    if (! IGRAPH_BIT_TEST(seen_vertices, nei)) {
114
115
0
                        IGRAPH_CHECK(igraph_vector_int_push_back(&edges, from));
116
0
                        IGRAPH_CHECK(igraph_vector_int_push_back(&edges, to));
117
118
0
                        IGRAPH_BIT_SET(seen_vertices, nei);
119
0
                        IGRAPH_CHECK(igraph_dqueue_int_push(&Q, nei));
120
121
0
                    } else {
122
123
0
                        tree_vertex_count++;
124
0
                        if (vertex_index) {
125
0
                            IGRAPH_CHECK(igraph_vector_int_push_back(vertex_index, nei));
126
0
                        }
127
128
0
                        if (from == nei) {
129
0
                            IGRAPH_CHECK(igraph_vector_int_push_back(&edges, v_ptr++));
130
0
                            IGRAPH_CHECK(igraph_vector_int_push_back(&edges, to));
131
0
                        } else {
132
0
                            IGRAPH_CHECK(igraph_vector_int_push_back(&edges, from));
133
0
                            IGRAPH_CHECK(igraph_vector_int_push_back(&edges, v_ptr++));
134
0
                        }
135
0
                    }
136
0
                }
137
138
0
            } /* for i<n */
139
140
0
        } /* ! igraph_dqueue_int_empty(&Q) */
141
142
0
    } /* r < igraph_vector_int_size(roots) */
143
144
0
    igraph_bitset_destroy(&seen_edges);
145
0
    igraph_bitset_destroy(&seen_vertices);
146
0
    igraph_vector_int_destroy(&neis);
147
0
    igraph_dqueue_int_destroy(&Q);
148
0
    IGRAPH_FINALLY_CLEAN(4);
149
150
0
    IGRAPH_CHECK(igraph_create(tree, &edges, tree_vertex_count, igraph_is_directed(graph)));
151
0
    igraph_vector_int_destroy(&edges);
152
0
    IGRAPH_FINALLY_CLEAN(1);
153
154
0
    return IGRAPH_SUCCESS;
155
0
}
156
157
/* igraph_is_tree -- check if a graph is a tree */
158
159
/* count the number of vertices reachable from the root */
160
93
static igraph_error_t igraph_i_is_tree_visitor(const igraph_t *graph, igraph_int_t root, igraph_neimode_t mode, igraph_int_t *visited_count) {
161
93
    igraph_stack_int_t stack;
162
93
    igraph_bitset_t visited;
163
93
    igraph_vector_int_t neighbors;
164
93
    igraph_int_t i;
165
166
93
    IGRAPH_VECTOR_INT_INIT_FINALLY(&neighbors, 0);
167
168
93
    IGRAPH_BITSET_INIT_FINALLY(&visited, igraph_vcount(graph));
169
170
93
    IGRAPH_CHECK(igraph_stack_int_init(&stack, 0));
171
93
    IGRAPH_FINALLY(igraph_stack_int_destroy, &stack);
172
173
93
    *visited_count = 0;
174
175
    /* push the root into the stack */
176
93
    IGRAPH_CHECK(igraph_stack_int_push(&stack, root));
177
178
4.06k
    while (! igraph_stack_int_empty(&stack)) {
179
3.97k
        igraph_int_t u;
180
3.97k
        igraph_int_t ncount;
181
182
        /* take a vertex from the stack, mark it as visited */
183
3.97k
        u = igraph_stack_int_pop(&stack);
184
3.97k
        if (IGRAPH_LIKELY(! IGRAPH_BIT_TEST(visited, u))) {
185
2.89k
            IGRAPH_BIT_SET(visited, u);
186
2.89k
            *visited_count += 1;
187
2.89k
        }
188
189
        /* register all its yet-unvisited neighbours for future processing */
190
3.97k
        IGRAPH_CHECK(igraph_neighbors(graph, &neighbors, u, mode, IGRAPH_LOOPS, IGRAPH_MULTIPLE));
191
3.97k
        ncount = igraph_vector_int_size(&neighbors);
192
66.5k
        for (i = 0; i < ncount; ++i) {
193
62.5k
            igraph_int_t v = VECTOR(neighbors)[i];
194
62.5k
            if (! IGRAPH_BIT_TEST(visited, v)) {
195
3.88k
                IGRAPH_CHECK(igraph_stack_int_push(&stack, v));
196
3.88k
            }
197
62.5k
        }
198
3.97k
    }
199
200
93
    igraph_vector_int_destroy(&neighbors);
201
93
    igraph_stack_int_destroy(&stack);
202
93
    igraph_bitset_destroy(&visited);
203
93
    IGRAPH_FINALLY_CLEAN(3);
204
205
93
    return IGRAPH_SUCCESS;
206
93
}
207
208
209
/**
210
 * \ingroup structural
211
 * \function igraph_is_tree
212
 * \brief Decides whether the graph is a tree.
213
 *
214
 * An undirected graph is a tree if it is connected and has no cycles.
215
 *
216
 * </para><para>
217
 * In the directed case, an additional requirement is that all edges
218
 * are oriented away from a root (out-tree or arborescence) or all edges
219
 * are oriented towards a root (in-tree or anti-arborescence).
220
 * This test can be controlled using the \p mode parameter.
221
 *
222
 * </para><para>
223
 * By convention, the null graph (i.e. the graph with no vertices) is considered
224
 * not to be connected, and therefore not a tree.
225
 *
226
 * \param graph The graph object to analyze.
227
 * \param res Pointer to a Boolean variable, the result will be stored
228
 *        here.
229
 * \param root If not \c NULL, the root node will be stored here. When \p mode
230
 *        is \c IGRAPH_ALL or the graph is undirected, any vertex can be the root
231
 *        and \p root is set to 0 (the first vertex). When \p mode is \c IGRAPH_OUT
232
 *        or \c IGRAPH_IN, the root is set to the vertex with zero in- or out-degree,
233
 *        respectively.
234
 * \param mode For a directed graph this specifies whether to test for an
235
 *        out-tree, an in-tree or ignore edge directions. The respective
236
 *        possible values are:
237
 *        \c IGRAPH_OUT, \c IGRAPH_IN, \c IGRAPH_ALL. This argument is
238
 *        ignored for undirected graphs.
239
 * \return Error code:
240
 *        \c IGRAPH_EINVAL: invalid mode argument.
241
 *
242
 * Time complexity: At most O(|V|+|E|), the
243
 * number of vertices plus the number of edges in the graph.
244
 *
245
 * \sa \ref igraph_is_forest() to check if all components are trees,
246
 * which is equivalent to the graph lacking undirected cycles;
247
 * \ref igraph_is_connected(), \ref igraph_is_acyclic()
248
 *
249
 * \example examples/simple/igraph_kary_tree.c
250
 */
251
612
igraph_error_t igraph_is_tree(const igraph_t *graph, igraph_bool_t *res, igraph_int_t *root, igraph_neimode_t mode) {
252
612
    igraph_bool_t is_tree = false;
253
612
    igraph_bool_t treat_as_undirected = !igraph_is_directed(graph) || mode == IGRAPH_ALL;
254
612
    igraph_int_t iroot = 0;
255
612
    igraph_int_t visited_count;
256
612
    igraph_int_t vcount, ecount;
257
258
612
    vcount = igraph_vcount(graph);
259
612
    ecount = igraph_ecount(graph);
260
261
612
    if (igraph_i_property_cache_has(graph, IGRAPH_PROP_IS_WEAKLY_CONNECTED)) {
262
0
        igraph_bool_t weakly_connected = igraph_i_property_cache_get_bool(graph, IGRAPH_PROP_IS_WEAKLY_CONNECTED);
263
264
0
        if (weakly_connected) {
265
            /* For undirected graphs and for directed graphs with mode == IGRAPH_ALL,
266
             * we can return early if we know from the cache that the graph is weakly
267
             * connected and is a forest. We can do this even if the user wants the
268
             * root vertex because we always return zero as the root vertex for
269
             * undirected graphs */
270
0
            if (treat_as_undirected &&
271
0
                igraph_i_property_cache_has(graph, IGRAPH_PROP_IS_FOREST) &&
272
0
                igraph_i_property_cache_get_bool(graph, IGRAPH_PROP_IS_FOREST)
273
0
            ) {
274
0
                is_tree = true;
275
0
                iroot = 0;
276
0
                goto success;
277
0
            }
278
0
        } else /* ! weakly_connected */ {
279
            /* If the graph is not weakly connected, then it is neither an undirected
280
             * not a directed tree. There is no root, so we can return early. */
281
0
            is_tree = false;
282
0
            goto success;
283
0
        }
284
0
    }
285
286
    /* A tree must have precisely vcount-1 edges. */
287
    /* By convention, the zero-vertex graph will not be considered a tree. */
288
612
    if (ecount != vcount - 1) {
289
518
        is_tree = false;
290
518
        goto success;
291
518
    }
292
293
    /* The single-vertex graph is a tree, provided it has no edges (checked in the previous if (..)) */
294
94
    if (vcount == 1) {
295
1
        is_tree = true;
296
1
        iroot = 0;
297
1
        goto success;
298
1
    }
299
300
    /* For higher vertex counts we cannot short-circuit due to the possibility
301
     * of loops or multi-edges even when the edge count is correct. */
302
303
    /* Ignore mode for undirected graphs. */
304
93
    if (! igraph_is_directed(graph)) {
305
93
        mode = IGRAPH_ALL;
306
93
    }
307
308
    /* The main algorithm:
309
     * We find a root and check that all other vertices are reachable from it.
310
     * We have already checked the number of edges, so with the additional
311
     * reachability condition we can verify if the graph is a tree.
312
     *
313
     * For directed graphs, the root is the node with no incoming/outgoing
314
     * connections, depending on 'mode'. For undirected, it is arbitrary, so
315
     * we choose 0.
316
     */
317
318
93
    is_tree = true; /* assume success */
319
320
93
    switch (mode) {
321
93
    case IGRAPH_ALL:
322
93
        iroot = 0;
323
93
        break;
324
325
0
    case IGRAPH_IN:
326
0
    case IGRAPH_OUT: {
327
0
        igraph_vector_int_t degree;
328
0
        igraph_int_t i;
329
330
0
        IGRAPH_CHECK(igraph_vector_int_init(&degree, 0));
331
0
        IGRAPH_FINALLY(igraph_vector_int_destroy, &degree);
332
333
0
        IGRAPH_CHECK(igraph_degree(graph, &degree, igraph_vss_all(), IGRAPH_REVERSE_MODE(mode), IGRAPH_LOOPS));
334
335
0
        for (i = 0; i < vcount; ++i) {
336
0
            if (VECTOR(degree)[i] == 0) {
337
0
                break;
338
0
            }
339
0
            if (VECTOR(degree)[i] > 1) {
340
                /* In an out-tree, all vertices have in-degree 1, except for the root,
341
                 * which has in-degree 0. Thus, if we encounter a larger in-degree,
342
                 * the graph cannot be an out-tree.
343
                 * We could perform this check for all degrees, but that would not
344
                 * improve performance when the graph is indeed a tree, persumably
345
                 * the most common case. Thus we only check until finding the root.
346
                 */
347
0
                is_tree = false;
348
0
                break;
349
0
            }
350
0
        }
351
352
        /* If no suitable root is found, the graph is not a tree. */
353
0
        if (is_tree && i == vcount) {
354
0
            is_tree = false;
355
0
        } else {
356
0
            iroot = i;
357
0
        }
358
359
0
        igraph_vector_int_destroy(&degree);
360
0
        IGRAPH_FINALLY_CLEAN(1);
361
0
    }
362
363
0
    break;
364
0
    default:
365
0
        IGRAPH_ERROR("Invalid mode.", IGRAPH_EINVMODE);
366
93
    }
367
368
    /* if no suitable root was found, skip visiting vertices */
369
93
    if (is_tree) {
370
93
        IGRAPH_CHECK(igraph_i_is_tree_visitor(graph, iroot, mode, &visited_count));
371
93
        is_tree = visited_count == vcount;
372
93
    }
373
374
612
success:
375
612
    if (res) {
376
612
        *res = is_tree;
377
612
    }
378
379
612
    if (root) {
380
0
        *root = iroot;
381
0
    }
382
383
612
    if (is_tree) {
384
        /* A graph that is a directed tree is also an undirected tree.
385
         * An undirected tree is weakly connected and is a forest,
386
         * so we can cache this. */
387
19
        igraph_i_property_cache_set_bool_checked(graph, IGRAPH_PROP_IS_FOREST, true);
388
19
        igraph_i_property_cache_set_bool_checked(graph, IGRAPH_PROP_IS_WEAKLY_CONNECTED, true);
389
19
    }
390
391
612
    return IGRAPH_SUCCESS;
392
93
}
393
394
/* igraph_is_forest() -- check if a graph is a forest */
395
396
/* Verify that the graph has no cycles and count the number of reachable vertices.
397
 * This function performs a DFS starting from 'root'.
398
 * If it finds a cycle, it sets *res to false, otherwise it does not change it.
399
 * *visited_count will be incremented by the number of vertices reachable from 'root',
400
 * including 'root' itself.
401
 */
402
static igraph_error_t igraph_i_is_forest_visitor(
403
        const igraph_t *graph, igraph_int_t root, igraph_neimode_t mode,
404
        igraph_bitset_t *visited, igraph_stack_int_t *stack, igraph_vector_int_t *neis,
405
        igraph_int_t *visited_count, igraph_bool_t *res)
406
32.9k
{
407
32.9k
    igraph_int_t i;
408
409
32.9k
    igraph_stack_int_clear(stack);
410
411
    /* push the root onto the stack */
412
32.9k
    IGRAPH_CHECK(igraph_stack_int_push(stack, root));
413
414
73.4k
    while (! igraph_stack_int_empty(stack)) {
415
40.6k
        igraph_int_t u;
416
40.6k
        igraph_int_t ncount;
417
418
        /* Take a vertex from stack and check if it is already visited.
419
         * If yes, then we found a cycle: the graph is not a forest.
420
         * Otherwise mark it as visited and continue.
421
         */
422
40.6k
        u = igraph_stack_int_pop(stack);
423
40.6k
        if (IGRAPH_LIKELY(! IGRAPH_BIT_TEST(*visited, u))) {
424
40.5k
            IGRAPH_BIT_SET(*visited, u);
425
40.5k
            *visited_count += 1;
426
40.5k
        }
427
157
        else {
428
157
            *res = false;
429
157
            break;
430
157
        }
431
432
        /* Vertex discovery: Register all its neighbours for future processing */
433
40.5k
        IGRAPH_CHECK(igraph_neighbors(graph, neis, u, mode, IGRAPH_LOOPS, IGRAPH_MULTIPLE));
434
40.5k
        ncount = igraph_vector_int_size(neis);
435
436
58.6k
        for (i = 0; i < ncount; ++i) {
437
18.3k
            igraph_int_t v = VECTOR(*neis)[i];
438
439
18.3k
            if (mode == IGRAPH_ALL) {
440
                /* In the undirected case, we avoid returning to the predecessor
441
                 * vertex of 'v' in the DFS tree by skipping visited vertices.
442
                 *
443
                 * Note that in order to succcessfully detect a cycle, a vertex
444
                 * within that cycle must end up on the stack more than once.
445
                 * Does skipping visited vertices preclude this sometimes?
446
                 * No, because any visited vertex can only be accessed through
447
                 * an already discovered vertex (i.e. one that has already been
448
                 * pushed onto the stack).
449
                 */
450
18.3k
                if (IGRAPH_LIKELY(! IGRAPH_BIT_TEST(*visited, v))) {
451
9.60k
                    IGRAPH_CHECK(igraph_stack_int_push(stack, v));
452
9.60k
                }
453
                /* To check for a self-loop in undirected graph */
454
8.78k
                else if (v == u) {
455
243
                    *res = false;
456
243
                    break;
457
243
                }
458
18.3k
            }
459
0
            else {
460
0
                IGRAPH_CHECK(igraph_stack_int_push(stack, v));
461
0
            }
462
18.3k
        }
463
40.5k
    }
464
465
32.9k
    return IGRAPH_SUCCESS;
466
32.9k
}
467
468
static igraph_error_t igraph_i_is_forest(
469
    const igraph_t *graph, igraph_bool_t *res,
470
    igraph_vector_int_t *roots, igraph_neimode_t mode
471
);
472
473
/**
474
 * \ingroup structural
475
 * \function igraph_is_forest
476
 * \brief Decides whether the graph is a forest.
477
 *
478
 * An undirected graph is a forest if it has no cycles. Equivalently,
479
 * a graph is a forest if all connected components are trees.
480
 *
481
 * </para><para>
482
 * In the directed case, an additional requirement is that edges in each
483
 * tree are oriented away from the root (out-trees or arborescences) or all edges
484
 * are oriented towards the root (in-trees or anti-arborescences).
485
 * This test can be controlled using the \p mode parameter.
486
 *
487
 * </para><para>
488
 * By convention, the null graph (i.e. the graph with no vertices) is considered
489
 * to be a forest.
490
 *
491
 * </para><para>
492
 * The \p res return value of this function is cached in the graph itself if
493
 * \p mode is set to \c IGRAPH_ALL or if the graph is undirected. Calling the
494
 * function multiple times with no modifications to the graph in between
495
 * will return a cached value in O(1) time if the roots are not requested.
496
 *
497
 * \param graph The graph object to analyze.
498
 * \param res Pointer to a Boolean variable. If not \c NULL, then the result will
499
 *        be stored here.
500
 * \param roots If not \c NULL, the root nodes will be stored here. When \p mode
501
 *        is \c IGRAPH_ALL or the graph is undirected, any one vertex from each
502
 *        component can be the root. When \p mode is \c IGRAPH_OUT
503
 *        or \c IGRAPH_IN, all the vertices with zero in- or out-degree,
504
 *        respectively are considered as root nodes.
505
 * \param mode For a directed graph this specifies whether to test for an
506
 *        out-forest, an in-forest or ignore edge directions. The respective
507
 *        possible values are:
508
 *        \c IGRAPH_OUT, \c IGRAPH_IN, \c IGRAPH_ALL. This argument is
509
 *        ignored for undirected graphs.
510
 * \return Error code:
511
 *        \c IGRAPH_EINVMODE: invalid mode argument.
512
 *
513
 * Time complexity: At most O(|V|+|E|), the
514
 * number of vertices plus the number of edges in the graph.
515
 *
516
 * \sa \ref igraph_is_tree() to check if a graph is a tree, i.e. a forest with
517
 * a single component; \ref igraph_is_acyclic() to check if a graph lacks
518
 * (undirected or directed) cycles.
519
 */
520
igraph_error_t igraph_is_forest(const igraph_t *graph, igraph_bool_t *res,
521
612
                                igraph_vector_int_t *roots, igraph_neimode_t mode) {
522
523
612
    const igraph_bool_t treat_as_undirected = !igraph_is_directed(graph) || mode == IGRAPH_ALL;
524
525
612
    if (!roots && !res) {
526
0
        return IGRAPH_SUCCESS;
527
0
    }
528
529
    /* Note on cache use:
530
     *
531
     * The IGRAPH_PROP_IS_FOREST cached property is equivalent to this function's
532
     * result ONLY in the undirected case. Keep in mind that a graph that is not
533
     * a directed forest may still be an undirected forest, i.e. may still be free
534
     * of undirected cycles. Example: 1->2<-3->4.
535
     */
536
537
612
    if (igraph_i_property_cache_has(graph, IGRAPH_PROP_IS_FOREST)) {
538
0
        const igraph_bool_t no_undirected_cycles = igraph_i_property_cache_get_bool(graph, IGRAPH_PROP_IS_FOREST);
539
0
        if (treat_as_undirected && res && ! roots) {
540
            /* If the graph is treated as undirected and no roots are requested,
541
             * we can directly use the cached IGRAPH_PROP_IS_FOREST value. */
542
0
            *res = no_undirected_cycles;
543
0
            return IGRAPH_SUCCESS;
544
0
        } else {
545
            /* Otherwise we can use negative cached values (i.e. "false"):
546
             *  - A graph with undirected cycles cannot be a directed forest.
547
             *  - If the graph is not a forest, we don't need to look for roots.
548
             */
549
0
            if (! no_undirected_cycles) {
550
0
                if (res) { *res = false; }
551
0
                if (roots) { igraph_vector_int_clear(roots); }
552
0
                return IGRAPH_SUCCESS;
553
0
            }
554
0
        }
555
0
    }
556
557
612
    IGRAPH_CHECK(igraph_i_is_forest(graph, res, roots, mode));
558
559
    /* At this point we know whether the graph is an (undirected or directed) forest
560
     * as we have at least one of 'res' or 'roots'. The case when both are NULL was
561
     * caught above. */
562
612
    igraph_bool_t is_forest;
563
612
    if (res != NULL) {
564
612
        is_forest = *res;
565
612
    } else /* roots != NULL */ {
566
0
        is_forest = igraph_vcount(graph) == 0 || !igraph_vector_int_empty(roots);
567
0
    }
568
612
    if (is_forest) {
569
        /* If the graph is a directed forest, then it has no undirected cycles.
570
         * We can enter positive results in the cache unconditionally. */
571
226
        igraph_i_property_cache_set_bool_checked(graph, IGRAPH_PROP_IS_FOREST, true);
572
386
    } else if (treat_as_undirected) {
573
        /* However, if the graph is not a directed forest, it might still be
574
         * an undirected forest. We can only enter negative results in the cache
575
         * when edge directions were ignored, but NOT in the directed case. */
576
386
        igraph_i_property_cache_set_bool_checked(graph, IGRAPH_PROP_IS_FOREST, false);
577
386
    }
578
579
612
    return IGRAPH_SUCCESS;
580
612
}
581
582
static igraph_error_t igraph_i_is_forest(
583
    const igraph_t *graph, igraph_bool_t *res,
584
    igraph_vector_int_t *roots, igraph_neimode_t mode
585
612
) {
586
612
    igraph_bitset_t visited;
587
612
    igraph_vector_int_t neis;
588
612
    igraph_stack_int_t stack;
589
612
    igraph_int_t visited_count = 0;
590
612
    igraph_int_t vcount, ecount;
591
612
    igraph_int_t v;
592
612
    igraph_bool_t result;
593
594
612
    vcount = igraph_vcount(graph);
595
612
    ecount = igraph_ecount(graph);
596
597
612
    if (roots) {
598
0
        igraph_vector_int_clear(roots);
599
0
    }
600
601
    /* Any graph with 0 edges is a forest. */
602
612
    if (ecount == 0) {
603
37
        if (res) {
604
37
            *res = true;
605
37
        }
606
37
        if (roots) {
607
0
            for (v = 0; v < vcount; v++) {
608
0
                IGRAPH_CHECK(igraph_vector_int_push_back(roots, v));
609
0
            }
610
0
        }
611
37
        return IGRAPH_SUCCESS;
612
37
    }
613
614
    /* A forest can have at most vcount-1 edges. */
615
575
    if (ecount > vcount - 1) {
616
93
        if (res) {
617
93
            *res = false;
618
93
        }
619
93
        return IGRAPH_SUCCESS;
620
93
    }
621
622
    /* Ignore mode for undirected graphs. */
623
482
    if (! igraph_is_directed(graph)) {
624
482
        mode = IGRAPH_ALL;
625
482
    }
626
627
482
    result = true; /* assume success */
628
629
482
    IGRAPH_BITSET_INIT_FINALLY(&visited, vcount);
630
631
482
    IGRAPH_CHECK(igraph_stack_int_init(&stack, 0));
632
482
    IGRAPH_FINALLY(igraph_stack_int_destroy, &stack);
633
634
482
    IGRAPH_VECTOR_INT_INIT_FINALLY(&neis, 0);
635
636
    /* The main algorithm:
637
     *
638
     * Undirected Graph:- We add each unvisited vertex to the roots vector, and
639
     * mark all other vertices that are reachable from it as visited.
640
     *
641
     * Directed Graph:- For each tree, the root is the node with no
642
     * incoming/outgoing connections, depending on 'mode'. We add each vertex
643
     * with zero degree to the roots vector and mark all other vertices that are
644
     * reachable from it as visited.
645
     *
646
     * If all the vertices are visited exactly once, then the graph is a forest.
647
     */
648
649
482
    switch (mode) {
650
482
        case IGRAPH_ALL:
651
482
        {
652
37.8k
            for (v = 0; v < vcount; ++v) {
653
37.6k
                if (!result) {
654
281
                    break;
655
281
                }
656
37.3k
                if (! IGRAPH_BIT_TEST(visited, v)) {
657
32.9k
                    if (roots) {
658
0
                        IGRAPH_CHECK(igraph_vector_int_push_back(roots, v));
659
0
                    }
660
32.9k
                    IGRAPH_CHECK(igraph_i_is_forest_visitor(
661
32.9k
                                     graph, v, mode,
662
32.9k
                                     &visited, &stack, &neis,
663
32.9k
                                     &visited_count, &result));
664
32.9k
                }
665
37.3k
            }
666
482
            break;
667
482
        }
668
669
482
        case IGRAPH_IN:
670
0
        case IGRAPH_OUT:
671
0
        {
672
0
            igraph_vector_int_t degree;
673
674
0
            IGRAPH_VECTOR_INT_INIT_FINALLY(&degree, 0);
675
0
            IGRAPH_CHECK(igraph_degree(graph, &degree, igraph_vss_all(),
676
0
                            IGRAPH_REVERSE_MODE(mode), IGRAPH_LOOPS));
677
678
0
            for (v = 0; v < vcount; ++v) {
679
                /* In an out-tree, roots have in-degree 0,
680
                 * and all other vertices have in-degree 1. */
681
0
                if (VECTOR(degree)[v] > 1 || !result) {
682
0
                    result = false;
683
0
                    break;
684
0
                }
685
0
                if (VECTOR(degree)[v] == 0) {
686
0
                    if (roots) {
687
0
                        IGRAPH_CHECK(igraph_vector_int_push_back(roots, v));
688
0
                    }
689
0
                    IGRAPH_CHECK(igraph_i_is_forest_visitor(
690
0
                                     graph, v, mode,
691
0
                                     &visited, &stack, &neis,
692
0
                                     &visited_count, &result));
693
0
                }
694
0
            }
695
696
0
            igraph_vector_int_destroy(&degree);
697
0
            IGRAPH_FINALLY_CLEAN(1);
698
0
            break;
699
0
        }
700
701
0
        default:
702
0
            IGRAPH_ERROR("Invalid mode.", IGRAPH_EINVMODE);
703
482
    }
704
705
482
    if (result) {
706
        /* In a forest, all vertices are reachable from the roots. */
707
189
        result = (visited_count == vcount);
708
189
    }
709
710
482
    if (res) {
711
482
        *res = result;
712
482
    }
713
714
    /* If the graph is not a forest then the root vector will be empty. */
715
482
    if (!result && roots) {
716
0
        igraph_vector_int_clear(roots);
717
0
    }
718
719
482
    igraph_vector_int_destroy(&neis);
720
482
    igraph_stack_int_destroy(&stack);
721
482
    igraph_bitset_destroy(&visited);
722
482
    IGRAPH_FINALLY_CLEAN(3);
723
724
482
    return IGRAPH_SUCCESS;
725
482
}
726
727
/**
728
 * \ingroup structural
729
 * \function igraph_is_acyclic
730
 * \brief Checks whether a graph is acyclic or not.
731
 *
732
 * This function checks whether a graph has any cycles. Edge directions are
733
 * considered, i.e. in directed graphs, only directed cycles are searched for.
734
 *
735
 * </para><para>
736
 * The result of this function is cached in the graph itself; calling
737
 * the function multiple times with no modifications to the graph in between
738
 * will return a cached value in O(1) time.
739
 *
740
 * \param graph The input graph.
741
 * \param res Pointer to a boolean constant, the result
742
        is stored here.
743
 * \return Error code.
744
 *
745
 * \sa \ref igraph_find_cycle() to find a cycle that demonstrates
746
 * that the graph is not acyclic; \ref igraph_is_forest() to look
747
 * for undirected cycles even in directed graphs; \ref igraph_is_dag()
748
 * to test specifically for directed acyclic graphs.
749
 *
750
 * Time complexity: O(|V|+|E|), where |V| and |E| are the number of
751
 * vertices and edges in the original input graph.
752
 */
753
612
igraph_error_t igraph_is_acyclic(const igraph_t *graph, igraph_bool_t *res) {
754
612
    if (igraph_is_directed(graph)) {
755
        /* igraph_is_dag is cached */
756
0
        return igraph_is_dag(graph, res);
757
612
    } else {
758
        /* igraph_is_forest is cached if mode == IGRAPH_ALL and we don't need
759
         * the roots */
760
        return igraph_is_forest(graph, res, NULL, IGRAPH_ALL);
761
612
    }
762
612
}