/src/igraph/src/paths/all_shortest_paths.c
Line | Count | Source |
1 | | /* |
2 | | igraph library. |
3 | | Copyright (C) 2005-2025 The igraph development team <igraph@igraph.org> |
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, see <https://www.gnu.org/licenses/>. |
17 | | */ |
18 | | |
19 | | #include "igraph_paths.h" |
20 | | |
21 | | #include "igraph_dqueue.h" |
22 | | #include "igraph_interface.h" |
23 | | #include "igraph_memory.h" |
24 | | |
25 | | #include "core/interruption.h" |
26 | | #include "paths/paths_internal.h" |
27 | | |
28 | | #include <string.h> /* memset */ |
29 | | |
30 | | /** |
31 | | * \function igraph_get_all_shortest_paths |
32 | | * \brief All shortest paths (geodesics) from a vertex. |
33 | | * |
34 | | * When there is more than one shortest path between two vertices, |
35 | | * all of them will be returned. Every edge is considered separately, |
36 | | * therefore in graphs with multi-edges, this function may produce |
37 | | * a very large number of results. |
38 | | * |
39 | | * \param graph The graph object. |
40 | | * \param vertices The result, the IDs of the vertices along the paths. |
41 | | * This is a list of integer vectors where each element is an |
42 | | * \ref igraph_vector_int_t object. Each vector object contains the vertices |
43 | | * along a shortest path from \p from to another vertex. The vectors are |
44 | | * ordered according to their target vertex: first the shortest paths to |
45 | | * vertex 0, then to vertex 1, etc. No data is included for unreachable |
46 | | * vertices. The list will be resized as needed. Supply a null pointer here |
47 | | * if you don't need these vectors. |
48 | | * \param edges The result, the IDs of the edges along the paths. |
49 | | * This is a list of integer vectors where each element is an |
50 | | * \ref igraph_vector_int_t object. Each vector object contains the edges |
51 | | * along a shortest path from \p from to another vertex. The vectors are |
52 | | * ordered according to their target vertex: first the shortest paths to |
53 | | * vertex 0, then to vertex 1, etc. No data is included for unreachable |
54 | | * vertices. The list will be resized as needed. Supply a null pointer here |
55 | | * if you don't need these vectors. |
56 | | * \param nrgeo Pointer to an initialized \ref igraph_vector_int_t object or |
57 | | * \c NULL. If not \c NULL the number of shortest paths from \p from are |
58 | | * stored here for every vertex in the graph. Note that the values |
59 | | * will be accurate only for those vertices that are in the target |
60 | | * vertex sequence (see \p to), since the search terminates as soon |
61 | | * as all the target vertices have been found. |
62 | | * \param from The id of the vertex from/to which the geodesics are |
63 | | * calculated. |
64 | | * \param to Vertex sequence with the IDs of the vertices to/from which the |
65 | | * shortest paths will be calculated. A vertex might be given multiple |
66 | | * times. |
67 | | * \param mode The type of shortest paths to be use for the |
68 | | * calculation in directed graphs. Possible values: |
69 | | * \clist |
70 | | * \cli IGRAPH_OUT |
71 | | * the lengths of the outgoing paths are calculated. |
72 | | * \cli IGRAPH_IN |
73 | | * the lengths of the incoming paths are calculated. |
74 | | * \cli IGRAPH_ALL |
75 | | * the directed graph is considered as an |
76 | | * undirected one for the computation. |
77 | | * \endclist |
78 | | * \return Error code: |
79 | | * \clist |
80 | | * \cli IGRAPH_ENOMEM |
81 | | * not enough memory for temporary data. |
82 | | * \cli IGRAPH_EINVVID |
83 | | * \p from is invalid vertex ID. |
84 | | * \cli IGRAPH_EINVMODE |
85 | | * invalid mode argument. |
86 | | * \endclist |
87 | | * |
88 | | * Added in version 0.2.</para><para> |
89 | | * |
90 | | * Time complexity: O(|V|+|E|) for most graphs, O(|V|^2) in the worst |
91 | | * case. |
92 | | */ |
93 | | igraph_error_t igraph_get_all_shortest_paths( |
94 | | const igraph_t *graph, |
95 | | const igraph_vector_t *weights, |
96 | | igraph_vector_int_list_t *vertices, |
97 | | igraph_vector_int_list_t *edges, |
98 | | igraph_vector_int_t *nrgeo, |
99 | | igraph_int_t from, const igraph_vs_t to, |
100 | 3.62k | igraph_neimode_t mode) { |
101 | | |
102 | 3.62k | if (weights == NULL) { |
103 | | /* Unweighted version */ |
104 | 3.62k | return igraph_i_get_all_shortest_paths_unweighted( |
105 | 3.62k | graph, vertices, edges, nrgeo, from, to, mode |
106 | 3.62k | ); |
107 | 3.62k | } else { |
108 | | /* Weighted version */ |
109 | 0 | return igraph_get_all_shortest_paths_dijkstra( |
110 | 0 | graph, vertices, edges, nrgeo, from, to, weights, mode |
111 | 0 | ); |
112 | 0 | } |
113 | 3.62k | } |
114 | | |
115 | | igraph_error_t igraph_i_get_all_shortest_paths_unweighted( |
116 | | const igraph_t *graph, |
117 | | igraph_vector_int_list_t *vertices, |
118 | | igraph_vector_int_list_t *edges, |
119 | | igraph_vector_int_t *nrgeo, |
120 | | igraph_int_t from, const igraph_vs_t to, |
121 | 3.62k | igraph_neimode_t mode) { |
122 | | |
123 | 3.62k | const igraph_int_t no_of_nodes = igraph_vcount(graph); |
124 | 3.62k | igraph_int_t *geodist; |
125 | 3.62k | igraph_vector_int_list_t paths; |
126 | 3.62k | igraph_vector_int_list_t path_edge; |
127 | 3.62k | igraph_dqueue_int_t q; |
128 | 3.62k | igraph_vector_int_t *vptr; |
129 | 3.62k | igraph_vector_int_t *vptr_e; |
130 | 3.62k | igraph_vector_int_t neis; |
131 | 3.62k | igraph_vector_int_t ptrlist; |
132 | 3.62k | igraph_vector_int_t ptrhead; |
133 | 3.62k | igraph_int_t n; |
134 | 3.62k | igraph_int_t to_reach, reached = 0, maxdist = 0; |
135 | | |
136 | 3.62k | igraph_vit_t vit; |
137 | | |
138 | 3.62k | if (from < 0 || from >= no_of_nodes) { |
139 | 0 | IGRAPH_ERROR("Index of source vertex is out of range.", IGRAPH_EINVVID); |
140 | 0 | } |
141 | 3.62k | if (mode != IGRAPH_OUT && mode != IGRAPH_IN && |
142 | 3.62k | mode != IGRAPH_ALL) { |
143 | 0 | IGRAPH_ERROR("Invalid mode argument.", IGRAPH_EINVMODE); |
144 | 0 | } |
145 | | |
146 | 3.62k | IGRAPH_CHECK(igraph_vit_create(graph, to, &vit)); |
147 | 3.62k | IGRAPH_FINALLY(igraph_vit_destroy, &vit); |
148 | | |
149 | | /* paths will store the shortest paths during the search */ |
150 | 3.62k | IGRAPH_VECTOR_INT_LIST_INIT_FINALLY(&paths, 0); |
151 | | /* path_edge will store the shortest paths during the search */ |
152 | 3.62k | IGRAPH_VECTOR_INT_LIST_INIT_FINALLY(&path_edge, 0); |
153 | | /* neis is a temporary vector holding the neighbors of the |
154 | | * node being examined */ |
155 | 3.62k | IGRAPH_VECTOR_INT_INIT_FINALLY(&neis, 0); |
156 | | /* ptrlist stores indices into the paths vector, in the order |
157 | | * of how they were found. ptrhead is a second-level index that |
158 | | * will be used to find paths that terminate in a given vertex */ |
159 | 3.62k | IGRAPH_VECTOR_INT_INIT_FINALLY(&ptrlist, 0); |
160 | | /* ptrhead contains indices into ptrlist. |
161 | | * ptrhead[i] = j means that element #j-1 in ptrlist contains |
162 | | * the shortest path from the root to node i. ptrhead[i] = 0 |
163 | | * means that node i was not reached so far */ |
164 | 3.62k | IGRAPH_VECTOR_INT_INIT_FINALLY(&ptrhead, no_of_nodes); |
165 | | /* geodist[i] == 0 if i was not reached yet and it is not in the |
166 | | * target vertex sequence, or -1 if i was not reached yet and it |
167 | | * is in the target vertex sequence. Otherwise it is |
168 | | * one larger than the length of the shortest path from the |
169 | | * source */ |
170 | 3.62k | geodist = IGRAPH_CALLOC(no_of_nodes, igraph_int_t); |
171 | 3.62k | IGRAPH_CHECK_OOM(geodist, "Insufficient memory for calculating shortest paths."); |
172 | 3.62k | IGRAPH_FINALLY(igraph_free, geodist); |
173 | | /* dequeue to store the BFS queue -- odd elements are the vertex indices, |
174 | | * even elements are the distances from the root */ |
175 | 3.62k | IGRAPH_DQUEUE_INT_INIT_FINALLY(&q, 100); |
176 | | |
177 | 3.62k | if (nrgeo) { |
178 | 3.62k | IGRAPH_CHECK(igraph_vector_int_resize(nrgeo, no_of_nodes)); |
179 | 3.62k | igraph_vector_int_null(nrgeo); |
180 | 3.62k | } |
181 | | |
182 | | /* use geodist to count how many vertices we have to reach */ |
183 | 3.62k | to_reach = IGRAPH_VIT_SIZE(vit); |
184 | 630k | for (IGRAPH_VIT_RESET(vit); !IGRAPH_VIT_END(vit); IGRAPH_VIT_NEXT(vit)) { |
185 | 627k | if (geodist[ IGRAPH_VIT_GET(vit) ] == 0) { |
186 | 627k | geodist[ IGRAPH_VIT_GET(vit) ] = -1; |
187 | 627k | } else { |
188 | 0 | to_reach--; /* this node was given multiple times */ |
189 | 0 | } |
190 | 627k | } |
191 | | |
192 | 3.62k | if (geodist[ from ] < 0) { |
193 | 3.62k | reached++; |
194 | 3.62k | } |
195 | | |
196 | | /* from -> from */ |
197 | 3.62k | IGRAPH_CHECK(igraph_vector_int_list_push_back_new(&paths, &vptr)); |
198 | 3.62k | IGRAPH_CHECK(igraph_vector_int_push_back(vptr, from)); |
199 | 3.62k | IGRAPH_CHECK(igraph_vector_int_list_push_back_new(&path_edge, &vptr_e)); |
200 | | |
201 | 3.62k | geodist[from] = 1; |
202 | 3.62k | VECTOR(ptrhead)[from] = 1; |
203 | 3.62k | IGRAPH_CHECK(igraph_vector_int_push_back(&ptrlist, 0)); |
204 | 3.62k | if (nrgeo) { |
205 | 3.62k | VECTOR(*nrgeo)[from] = 1; |
206 | 3.62k | } |
207 | | |
208 | | /* Init queue */ |
209 | 3.62k | IGRAPH_CHECK(igraph_dqueue_int_push(&q, from)); |
210 | 3.62k | IGRAPH_CHECK(igraph_dqueue_int_push(&q, 0)); |
211 | 63.5k | while (!igraph_dqueue_int_empty(&q)) { |
212 | 60.2k | igraph_int_t actnode = igraph_dqueue_int_pop(&q); |
213 | 60.2k | igraph_int_t actdist = igraph_dqueue_int_pop(&q); |
214 | | |
215 | 60.2k | IGRAPH_ALLOW_INTERRUPTION(); |
216 | | |
217 | 60.2k | if (reached >= to_reach) { |
218 | | /* all nodes were reached. Since we need all the shortest paths |
219 | | * to all these nodes, we can stop the search only if the distance |
220 | | * of the current node to the root is larger than the distance of |
221 | | * any of the nodes we wanted to reach */ |
222 | 827 | if (actdist > maxdist) { |
223 | | /* safety check, maxdist should have been set when we reached the last node */ |
224 | 281 | IGRAPH_ASSERT(maxdist >= 0); |
225 | 281 | break; |
226 | 281 | } |
227 | 827 | } |
228 | | |
229 | | /* If we need the edge-paths, we need to use igraph_incident() followed by an |
230 | | * IGRAPH_OTHER() macro in the main loop. This is going to be slower than |
231 | | * using igraph_neighbors() due to branch mispredictions in IGRAPH_OTHER(), so we |
232 | | * use igraph_incident() only if the user needs the edge-paths */ |
233 | 59.9k | if (edges) { |
234 | 59.9k | IGRAPH_CHECK(igraph_incident(graph, &neis, actnode, mode, IGRAPH_LOOPS)); |
235 | 59.9k | } else { |
236 | 0 | IGRAPH_CHECK(igraph_neighbors(graph, &neis, actnode, mode, IGRAPH_LOOPS, IGRAPH_MULTIPLE)); |
237 | 0 | } |
238 | | |
239 | 59.9k | n = igraph_vector_int_size(&neis); |
240 | 190k | for (igraph_int_t j = 0; j < n; j++) { |
241 | 130k | igraph_int_t neighbor; |
242 | 130k | igraph_int_t parentptr; |
243 | | |
244 | 130k | if (edges) { |
245 | | /* user needs the edge-paths, so 'neis' contains edge IDs, we need to resolve |
246 | | * the next edge ID into a vertex ID */ |
247 | 130k | neighbor = IGRAPH_OTHER(graph, VECTOR(neis)[j], actnode); |
248 | 130k | } else { |
249 | | /* user does not need the edge-paths, so 'neis' contains vertex IDs */ |
250 | 0 | neighbor = VECTOR(neis)[j]; |
251 | 0 | } |
252 | | |
253 | 130k | if (geodist[neighbor] > 0 && |
254 | 73.1k | geodist[neighbor] - 1 < actdist + 1) { |
255 | | /* this node was reached via a shorter path before */ |
256 | 68.0k | continue; |
257 | 68.0k | } |
258 | | |
259 | | /* yay, found another shortest path to neighbor */ |
260 | | |
261 | 61.9k | if (nrgeo) { |
262 | | /* the number of geodesics leading to neighbor must be |
263 | | * increased by the number of geodesics leading to actnode */ |
264 | 61.9k | VECTOR(*nrgeo)[neighbor] += VECTOR(*nrgeo)[actnode]; |
265 | 61.9k | } |
266 | 61.9k | if (geodist[neighbor] <= 0) { |
267 | | /* this node was not reached yet, push it into the queue */ |
268 | 56.9k | IGRAPH_CHECK(igraph_dqueue_int_push(&q, neighbor)); |
269 | 56.9k | IGRAPH_CHECK(igraph_dqueue_int_push(&q, actdist + 1)); |
270 | 56.9k | if (geodist[neighbor] < 0) { |
271 | 56.9k | reached++; |
272 | 56.9k | } |
273 | 56.9k | if (reached == to_reach) { |
274 | 281 | maxdist = actdist; |
275 | 281 | } |
276 | 56.9k | } |
277 | 61.9k | geodist[neighbor] = actdist + 2; |
278 | | |
279 | | /* copy all existing paths to the parent */ |
280 | 61.9k | parentptr = VECTOR(ptrhead)[actnode]; |
281 | 686k | while (parentptr != 0) { |
282 | | /* allocate a new igraph_vector_int_t at the end of paths */ |
283 | 624k | IGRAPH_CHECK(igraph_vector_int_list_push_back_new(&paths, &vptr)); |
284 | 624k | IGRAPH_CHECK(igraph_vector_int_update(vptr, igraph_vector_int_list_get_ptr(&paths, parentptr - 1))); |
285 | 624k | IGRAPH_CHECK(igraph_vector_int_push_back(vptr, neighbor)); |
286 | | |
287 | 624k | IGRAPH_CHECK(igraph_vector_int_list_push_back_new(&path_edge, &vptr_e)); |
288 | 624k | if (actnode != from) { |
289 | | /* If the previous vertex was the source then there is no edge to add*/ |
290 | 615k | IGRAPH_CHECK(igraph_vector_int_update(vptr_e, igraph_vector_int_list_get_ptr(&path_edge, parentptr - 1))); |
291 | 615k | } |
292 | 624k | IGRAPH_CHECK(igraph_vector_int_push_back(vptr_e, VECTOR(neis)[j])); |
293 | | |
294 | 624k | IGRAPH_CHECK(igraph_vector_int_push_back(&ptrlist, VECTOR(ptrhead)[neighbor])); |
295 | 624k | VECTOR(ptrhead)[neighbor] = igraph_vector_int_size(&ptrlist); |
296 | | |
297 | 624k | parentptr = VECTOR(ptrlist)[parentptr - 1]; |
298 | 624k | } |
299 | 61.9k | } |
300 | 59.9k | } |
301 | | |
302 | 3.62k | igraph_dqueue_int_destroy(&q); |
303 | 3.62k | IGRAPH_FINALLY_CLEAN(1); |
304 | | |
305 | | /* mark the nodes for which we need the result */ |
306 | 3.62k | memset(geodist, 0, sizeof(geodist[0]) * (size_t) no_of_nodes); |
307 | 630k | for (IGRAPH_VIT_RESET(vit); !IGRAPH_VIT_END(vit); IGRAPH_VIT_NEXT(vit)) { |
308 | 627k | geodist[ IGRAPH_VIT_GET(vit) ] = 1; |
309 | 627k | } |
310 | | |
311 | 3.62k | if (vertices) { |
312 | 3.62k | igraph_vector_int_list_clear(vertices); |
313 | 3.62k | } |
314 | 3.62k | if (edges) { |
315 | 3.62k | igraph_vector_int_list_clear(edges); |
316 | 3.62k | } |
317 | | |
318 | 630k | for (igraph_int_t i = 0; i < no_of_nodes; i++) { |
319 | 627k | igraph_int_t parentptr = VECTOR(ptrhead)[i]; |
320 | | |
321 | 627k | IGRAPH_ALLOW_INTERRUPTION(); |
322 | | |
323 | | /* do we need the paths leading to vertex i? */ |
324 | 627k | if (geodist[i] > 0) { |
325 | | /* yes, transfer them to the result vector */ |
326 | 1.25M | while (parentptr != 0) { |
327 | | /* Given two vector lists, list1 and list2, an efficient way to transfer |
328 | | * a vector from list1 to the end of list2 is to extend list2 with an |
329 | | * empty vector, then swap that empty vector with the given element of |
330 | | * list1. This approach avoids creating a full copy of the vector. */ |
331 | 627k | if (vertices) { |
332 | 627k | igraph_vector_int_t *p; |
333 | 627k | IGRAPH_CHECK(igraph_vector_int_list_push_back_new(vertices, &p)); |
334 | 627k | igraph_vector_int_swap(p, igraph_vector_int_list_get_ptr(&paths, parentptr - 1)); |
335 | 627k | } |
336 | 627k | if (edges) { |
337 | 627k | igraph_vector_int_t *p; |
338 | 627k | IGRAPH_CHECK(igraph_vector_int_list_push_back_new(edges, &p)); |
339 | 627k | igraph_vector_int_swap(p, igraph_vector_int_list_get_ptr(&path_edge, parentptr - 1)); |
340 | 627k | } |
341 | 627k | parentptr = VECTOR(ptrlist)[parentptr - 1]; |
342 | 627k | } |
343 | 627k | } |
344 | 627k | } |
345 | | |
346 | 3.62k | IGRAPH_FREE(geodist); |
347 | 3.62k | igraph_vector_int_destroy(&ptrlist); |
348 | 3.62k | igraph_vector_int_destroy(&ptrhead); |
349 | 3.62k | igraph_vector_int_destroy(&neis); |
350 | 3.62k | igraph_vector_int_list_destroy(&paths); |
351 | 3.62k | igraph_vector_int_list_destroy(&path_edge); |
352 | 3.62k | igraph_vit_destroy(&vit); |
353 | 3.62k | IGRAPH_FINALLY_CLEAN(7); |
354 | | |
355 | 3.62k | return IGRAPH_SUCCESS; |
356 | 3.62k | } |