/src/igraph/src/properties/dag.c
Line | Count | Source |
1 | | /* |
2 | | igraph library. |
3 | | Copyright (C) 2005-2024 The igraph development team <igraph@igraph.org> |
4 | | |
5 | | This program is free software; you can redistribute it and/or modify it under |
6 | | the terms of the GNU General Public License as published by the Free Software |
7 | | Foundation; either version 2 of the License, or (at your option) any later |
8 | | version. |
9 | | |
10 | | This program is distributed in the hope that it will be useful, but WITHOUT |
11 | | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
12 | | FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. |
13 | | |
14 | | You should have received a copy of the GNU General Public License along with |
15 | | this program. If not, see <https://www.gnu.org/licenses/>. |
16 | | */ |
17 | | |
18 | | #include "igraph_cycles.h" |
19 | | |
20 | | #include "igraph_dqueue.h" |
21 | | #include "igraph_interface.h" |
22 | | |
23 | | /** |
24 | | * \function igraph_topological_sorting |
25 | | * \brief Calculate a possible topological sorting of the graph. |
26 | | * |
27 | | * </para><para> |
28 | | * A topological sorting of a directed acyclic graph (DAG) is a linear ordering |
29 | | * of its vertices where each vertex comes before all nodes to which it has |
30 | | * edges. Every DAG has at least one topological sort, and may have many. |
31 | | * This function returns one possible topological sort among them. If the |
32 | | * graph contains any cycles that are not self-loops, an error is raised. |
33 | | * |
34 | | * \param graph The input graph. |
35 | | * \param res Pointer to a vector, the result will be stored here. |
36 | | * It will be resized if needed. |
37 | | * \param mode Specifies how to use the direction of the edges. |
38 | | * For \c IGRAPH_OUT, the sorting order ensures that each vertex comes |
39 | | * before all vertices to which it has edges, so vertices with no incoming |
40 | | * edges go first. For \c IGRAPH_IN, it is quite the opposite: each |
41 | | * vertex comes before all vertices from which it receives edges. Vertices |
42 | | * with no outgoing edges go first. |
43 | | * \return Error code. |
44 | | * |
45 | | * Time complexity: O(|V|+|E|), where |V| and |E| are the number of |
46 | | * vertices and edges in the original input graph. |
47 | | * |
48 | | * \sa \ref igraph_is_dag() if you are only interested in whether a given |
49 | | * graph is a DAG or not, or \ref igraph_feedback_arc_set() to find a |
50 | | * set of edges whose removal makes the graph acyclic. |
51 | | * |
52 | | * \example examples/simple/igraph_topological_sorting.c |
53 | | */ |
54 | | igraph_error_t igraph_topological_sorting( |
55 | 0 | const igraph_t* graph, igraph_vector_int_t *res, igraph_neimode_t mode) { |
56 | | |
57 | | /* Note: This function ignores self-loops, there it cannot |
58 | | * use the IGRAPH_PROP_IS_DAG property cache entry. */ |
59 | |
|
60 | 0 | igraph_int_t no_of_nodes = igraph_vcount(graph); |
61 | 0 | igraph_vector_int_t degrees; |
62 | 0 | igraph_vector_int_t neis; |
63 | 0 | igraph_dqueue_int_t sources; |
64 | 0 | igraph_neimode_t deg_mode; |
65 | 0 | igraph_int_t node, i, j; |
66 | |
|
67 | 0 | if (mode == IGRAPH_ALL || !igraph_is_directed(graph)) { |
68 | 0 | IGRAPH_ERROR("Topological sorting does not make sense for undirected graphs.", |
69 | 0 | IGRAPH_EINVAL); |
70 | 0 | } else if (mode == IGRAPH_OUT) { |
71 | 0 | deg_mode = IGRAPH_IN; |
72 | 0 | } else if (mode == IGRAPH_IN) { |
73 | 0 | deg_mode = IGRAPH_OUT; |
74 | 0 | } else { |
75 | 0 | IGRAPH_ERROR("Invalid mode for topological sorting.", IGRAPH_EINVMODE); |
76 | 0 | } |
77 | | |
78 | 0 | IGRAPH_VECTOR_INT_INIT_FINALLY(°rees, no_of_nodes); |
79 | 0 | IGRAPH_VECTOR_INT_INIT_FINALLY(&neis, 0); |
80 | 0 | IGRAPH_CHECK(igraph_dqueue_int_init(&sources, 0)); |
81 | 0 | IGRAPH_FINALLY(igraph_dqueue_int_destroy, &sources); |
82 | 0 | IGRAPH_CHECK(igraph_degree(graph, °rees, igraph_vss_all(), deg_mode, IGRAPH_NO_LOOPS)); |
83 | | |
84 | 0 | igraph_vector_int_clear(res); |
85 | | |
86 | | /* Do we have nodes with no incoming vertices? */ |
87 | 0 | for (i = 0; i < no_of_nodes; i++) { |
88 | 0 | if (VECTOR(degrees)[i] == 0) { |
89 | 0 | IGRAPH_CHECK(igraph_dqueue_int_push(&sources, i)); |
90 | 0 | } |
91 | 0 | } |
92 | | |
93 | | /* Take all nodes with no incoming vertices and remove them */ |
94 | 0 | while (!igraph_dqueue_int_empty(&sources)) { |
95 | 0 | node = igraph_dqueue_int_pop(&sources); |
96 | | /* Add the node to the result vector */ |
97 | 0 | IGRAPH_CHECK(igraph_vector_int_push_back(res, node)); |
98 | | /* Exclude the node from further source searches */ |
99 | 0 | VECTOR(degrees)[node] = -1; |
100 | | /* Get the neighbors and decrease their degrees by one */ |
101 | 0 | IGRAPH_CHECK(igraph_neighbors(graph, &neis, node, mode, IGRAPH_NO_LOOPS, IGRAPH_MULTIPLE)); |
102 | 0 | j = igraph_vector_int_size(&neis); |
103 | 0 | for (i = 0; i < j; i++) { |
104 | 0 | VECTOR(degrees)[ VECTOR(neis)[i] ]--; |
105 | 0 | if (VECTOR(degrees)[ VECTOR(neis)[i] ] == 0) { |
106 | 0 | IGRAPH_CHECK(igraph_dqueue_int_push(&sources, VECTOR(neis)[i])); |
107 | 0 | } |
108 | 0 | } |
109 | 0 | } |
110 | | |
111 | 0 | igraph_vector_int_destroy(°rees); |
112 | 0 | igraph_vector_int_destroy(&neis); |
113 | 0 | igraph_dqueue_int_destroy(&sources); |
114 | 0 | IGRAPH_FINALLY_CLEAN(3); |
115 | |
|
116 | 0 | if (igraph_vector_int_size(res) < no_of_nodes) { |
117 | 0 | IGRAPH_ERROR("The graph has cycles; " |
118 | 0 | "topological sorting is only possible in acyclic graphs.", |
119 | 0 | IGRAPH_EINVAL); |
120 | 0 | } |
121 | | |
122 | 0 | return IGRAPH_SUCCESS; |
123 | 0 | } |
124 | | |
125 | | /** |
126 | | * \function igraph_is_dag |
127 | | * \brief Checks whether a graph is a directed acyclic graph (DAG). |
128 | | * |
129 | | * </para><para> |
130 | | * A directed acyclic graph (DAG) is a directed graph with no cycles. |
131 | | * |
132 | | * </para><para> |
133 | | * This function returns \c false for undirected graphs. |
134 | | * |
135 | | * </para><para> |
136 | | * The return value of this function is cached in the graph itself; calling |
137 | | * the function multiple times with no modifications to the graph in between |
138 | | * will return a cached value in O(1) time. |
139 | | * |
140 | | * \param graph The input graph. |
141 | | * \param res Pointer to a boolean constant, the result |
142 | | * is stored here. |
143 | | * \return Error code. |
144 | | * |
145 | | * Time complexity: O(|V|+|E|), where |V| and |E| are the number of |
146 | | * vertices and edges in the original input graph. |
147 | | * |
148 | | * \sa \ref igraph_topological_sorting() to get a possible topological |
149 | | * sorting of a DAG. |
150 | | */ |
151 | 0 | igraph_error_t igraph_is_dag(const igraph_t* graph, igraph_bool_t *res) { |
152 | 0 | igraph_int_t no_of_nodes = igraph_vcount(graph); |
153 | 0 | igraph_vector_int_t degrees; |
154 | 0 | igraph_vector_int_t neis; |
155 | 0 | igraph_dqueue_int_t sources; |
156 | |
|
157 | 0 | if (!igraph_is_directed(graph)) { |
158 | 0 | *res = false; |
159 | 0 | return IGRAPH_SUCCESS; |
160 | 0 | } |
161 | | |
162 | 0 | IGRAPH_RETURN_IF_CACHED_BOOL(graph, IGRAPH_PROP_IS_DAG, res); |
163 | | |
164 | 0 | IGRAPH_VECTOR_INT_INIT_FINALLY(°rees, no_of_nodes); |
165 | 0 | IGRAPH_VECTOR_INT_INIT_FINALLY(&neis, 0); |
166 | 0 | IGRAPH_DQUEUE_INT_INIT_FINALLY(&sources, 0); |
167 | | |
168 | 0 | IGRAPH_CHECK(igraph_degree(graph, °rees, igraph_vss_all(), IGRAPH_IN, IGRAPH_LOOPS)); |
169 | | |
170 | 0 | igraph_int_t vertices_left = no_of_nodes; |
171 | | |
172 | | /* Do we have nodes with no incoming edges? */ |
173 | 0 | for (igraph_int_t i = 0; i < no_of_nodes; i++) { |
174 | 0 | if (VECTOR(degrees)[i] == 0) { |
175 | 0 | IGRAPH_CHECK(igraph_dqueue_int_push(&sources, i)); |
176 | 0 | } |
177 | 0 | } |
178 | | |
179 | | /* Take all nodes with no incoming edges and remove them */ |
180 | 0 | while (!igraph_dqueue_int_empty(&sources)) { |
181 | 0 | igraph_int_t node = igraph_dqueue_int_pop(&sources); |
182 | | /* Exclude the node from further source searches */ |
183 | 0 | VECTOR(degrees)[node] = -1; |
184 | 0 | vertices_left--; |
185 | | /* Get the neighbors and decrease their degrees by one */ |
186 | 0 | IGRAPH_CHECK(igraph_neighbors(graph, &neis, node, IGRAPH_OUT, IGRAPH_LOOPS_ONCE, IGRAPH_MULTIPLE)); |
187 | 0 | igraph_int_t n = igraph_vector_int_size(&neis); |
188 | 0 | for (igraph_int_t i = 0; i < n; i++) { |
189 | 0 | igraph_int_t nei = VECTOR(neis)[i]; |
190 | 0 | if (nei == node) { |
191 | | /* Found a self-loop, graph is not a DAG */ |
192 | 0 | *res = false; |
193 | 0 | goto finalize; |
194 | 0 | } |
195 | 0 | VECTOR(degrees)[nei]--; |
196 | 0 | if (VECTOR(degrees)[nei] == 0) { |
197 | 0 | IGRAPH_CHECK(igraph_dqueue_int_push(&sources, nei)); |
198 | 0 | } |
199 | 0 | } |
200 | 0 | } |
201 | | |
202 | 0 | IGRAPH_ASSERT(vertices_left >= 0); |
203 | 0 | *res = (vertices_left == 0); |
204 | |
|
205 | 0 | finalize: |
206 | 0 | igraph_vector_int_destroy(°rees); |
207 | 0 | igraph_vector_int_destroy(&neis); |
208 | 0 | igraph_dqueue_int_destroy(&sources); |
209 | 0 | IGRAPH_FINALLY_CLEAN(3); |
210 | |
|
211 | 0 | igraph_i_property_cache_set_bool_checked(graph, IGRAPH_PROP_IS_DAG, *res); |
212 | |
|
213 | 0 | return IGRAPH_SUCCESS; |
214 | 0 | } |