/src/igraph/src/properties/girth.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_adjlist.h" |
25 | | #include "igraph_components.h" |
26 | | #include "igraph_dqueue.h" |
27 | | #include "igraph_interface.h" |
28 | | |
29 | | #include "core/interruption.h" |
30 | | |
31 | | /** |
32 | | * \function igraph_girth |
33 | | * \brief The girth of a graph is the length of the shortest cycle in it. |
34 | | * |
35 | | * The current implementation works for undirected graphs only, |
36 | | * directed graphs are treated as undirected graphs. Self-loops and |
37 | | * multiple edges are ignored, i.e. cycles of length 1 or 2 are |
38 | | * not considered. |
39 | | * |
40 | | * </para><para> |
41 | | * For graphs that contain no cycles, and only for such graphs, |
42 | | * infinity is returned. |
43 | | * |
44 | | * </para><para> |
45 | | * The first implementation of this function was done by Keith Briggs, |
46 | | * thanks Keith. |
47 | | * |
48 | | * </para><para> |
49 | | * Reference: |
50 | | * |
51 | | * </para><para> |
52 | | * Alon Itai and Michael Rodeh: |
53 | | * Finding a minimum circuit in a graph |
54 | | * \emb Proceedings of the ninth annual ACM symposium on Theory of |
55 | | * computing \eme, 1-10, 1977. |
56 | | * https://doi.org/10.1145/800105.803390 |
57 | | * |
58 | | * \param graph The input graph. Edge directions will be ignored. |
59 | | * \param girth Pointer to an \c igraph_real_t, if not \c NULL then the result |
60 | | * will be stored here. |
61 | | * \param cycle Pointer to an initialized vector, the vertex IDs in |
62 | | * the shortest cycle of length at least 3 will be stored here. |
63 | | * If \c NULL then it is ignored. |
64 | | * \return Error code. |
65 | | * |
66 | | * Time complexity: O((|V|+|E|)^2), |V| is the number of vertices, |E| |
67 | | * is the number of edges in the general case. If the graph has no |
68 | | * cycles at all then the function needs O(|V|+|E|) time to realize |
69 | | * this and then it stops. |
70 | | * |
71 | | * \example examples/simple/igraph_girth.c |
72 | | */ |
73 | | igraph_error_t igraph_girth(const igraph_t *graph, |
74 | | igraph_real_t *girth, |
75 | 1.88k | igraph_vector_int_t *cycle) { |
76 | | |
77 | 1.88k | igraph_int_t no_of_nodes = igraph_vcount(graph); |
78 | 1.88k | igraph_dqueue_int_t q; |
79 | 1.88k | igraph_lazy_adjlist_t adjlist; |
80 | 1.88k | igraph_int_t mincirc = IGRAPH_INTEGER_MAX, minvertex = 0; |
81 | 1.88k | igraph_int_t node; |
82 | 1.88k | igraph_bool_t triangle = false; |
83 | 1.88k | igraph_vector_int_t *neis; |
84 | 1.88k | igraph_vector_int_t level; |
85 | 1.88k | igraph_int_t stoplevel = no_of_nodes + 1; |
86 | 1.88k | igraph_bool_t anycircle = false; |
87 | 1.88k | igraph_int_t t1 = 0, t2 = 0; |
88 | | |
89 | 1.88k | IGRAPH_CHECK(igraph_lazy_adjlist_init(graph, &adjlist, IGRAPH_ALL, IGRAPH_NO_LOOPS, IGRAPH_NO_MULTIPLE)); |
90 | 1.88k | IGRAPH_FINALLY(igraph_lazy_adjlist_destroy, &adjlist); |
91 | 1.88k | IGRAPH_DQUEUE_INT_INIT_FINALLY(&q, 100); |
92 | 1.88k | IGRAPH_VECTOR_INT_INIT_FINALLY(&level, no_of_nodes); |
93 | | |
94 | 283k | for (node = 0; !triangle && node < no_of_nodes; node++) { |
95 | | |
96 | | /* Are there circles in this graph at all? */ |
97 | 282k | if (node == 1 && anycircle == 0) { |
98 | 1.53k | igraph_bool_t conn; |
99 | 1.53k | IGRAPH_CHECK(igraph_is_connected(graph, &conn, IGRAPH_WEAK)); |
100 | 1.53k | if (conn) { |
101 | | /* No, there are none */ |
102 | 105 | break; |
103 | 105 | } |
104 | 1.53k | } |
105 | | |
106 | 282k | anycircle = 0; |
107 | 282k | igraph_dqueue_int_clear(&q); |
108 | 282k | igraph_vector_int_null(&level); |
109 | 282k | IGRAPH_CHECK(igraph_dqueue_int_push(&q, node)); |
110 | 282k | VECTOR(level)[node] = 1; |
111 | | |
112 | 282k | IGRAPH_ALLOW_INTERRUPTION(); |
113 | | |
114 | 2.23M | while (!igraph_dqueue_int_empty(&q)) { |
115 | 1.96M | igraph_int_t actnode = igraph_dqueue_int_pop(&q); |
116 | 1.96M | igraph_int_t actlevel = VECTOR(level)[actnode]; |
117 | 1.96M | igraph_int_t i, n; |
118 | | |
119 | 1.96M | if (actlevel >= stoplevel) { |
120 | 8.75k | break; |
121 | 8.75k | } |
122 | | |
123 | 1.95M | neis = igraph_lazy_adjlist_get(&adjlist, actnode); |
124 | 1.95M | IGRAPH_CHECK_OOM(neis, "Failed to query neighbors."); |
125 | | |
126 | 1.95M | n = igraph_vector_int_size(neis); |
127 | 5.44M | for (i = 0; i < n; i++) { |
128 | 3.49M | igraph_int_t nei = VECTOR(*neis)[i]; |
129 | 3.49M | igraph_int_t neilevel = VECTOR(level)[nei]; |
130 | 3.49M | if (neilevel != 0) { |
131 | 1.68M | if (neilevel == actlevel - 1) { |
132 | 1.67M | continue; |
133 | 1.67M | } else { |
134 | | /* found circle */ |
135 | 6.40k | stoplevel = neilevel; |
136 | 6.40k | anycircle = 1; |
137 | 6.40k | if (actlevel < mincirc) { |
138 | | /* Is it a minimum circle? */ |
139 | 6.40k | mincirc = actlevel + neilevel - 1; |
140 | 6.40k | minvertex = node; |
141 | 6.40k | t1 = actnode; t2 = nei; |
142 | 6.40k | if (neilevel == 2) { |
143 | | /* Is it a triangle? */ |
144 | 285 | triangle = 1; |
145 | 285 | } |
146 | 6.40k | } |
147 | 6.40k | if (neilevel == actlevel) { |
148 | 485 | break; |
149 | 485 | } |
150 | 6.40k | } |
151 | 1.81M | } else { |
152 | 1.81M | IGRAPH_CHECK(igraph_dqueue_int_push(&q, nei)); |
153 | 1.81M | VECTOR(level)[nei] = actlevel + 1; |
154 | 1.81M | } |
155 | 3.49M | } |
156 | | |
157 | 1.95M | } /* while q !empty */ |
158 | 282k | } /* node */ |
159 | | |
160 | 1.88k | if (girth) { |
161 | 1.88k | if (mincirc == IGRAPH_INTEGER_MAX) { |
162 | 1.40k | *girth = IGRAPH_INFINITY; |
163 | 1.40k | } else { |
164 | 476 | *girth = mincirc; |
165 | 476 | } |
166 | 1.88k | } |
167 | | |
168 | 1.88k | if (mincirc == IGRAPH_INTEGER_MAX) { |
169 | 1.40k | mincirc = 0; |
170 | 1.40k | } |
171 | | |
172 | | /* Store the actual circle, if needed */ |
173 | 1.88k | if (cycle) { |
174 | 1.88k | IGRAPH_CHECK(igraph_vector_int_resize(cycle, mincirc)); |
175 | 1.88k | if (mincirc != 0) { |
176 | 476 | igraph_int_t i, n, idx = 0; |
177 | 476 | igraph_dqueue_int_clear(&q); |
178 | 476 | igraph_vector_int_null(&level); /* used for parent pointers */ |
179 | 25.7k | #define PARENT(x) (VECTOR(level)[(x)]) |
180 | 476 | IGRAPH_CHECK(igraph_dqueue_int_push(&q, minvertex)); |
181 | 476 | PARENT(minvertex) = minvertex; |
182 | 3.73k | while (PARENT(t1) == 0 || PARENT(t2) == 0) { |
183 | 3.25k | igraph_int_t actnode = igraph_dqueue_int_pop(&q); |
184 | 3.25k | neis = igraph_lazy_adjlist_get(&adjlist, actnode); |
185 | 3.25k | IGRAPH_CHECK_OOM(neis, "Failed to query neighbors."); |
186 | 3.25k | n = igraph_vector_int_size(neis); |
187 | 13.5k | for (i = 0; i < n; i++) { |
188 | 10.3k | igraph_int_t nei = VECTOR(*neis)[i]; |
189 | 10.3k | if (PARENT(nei) == 0) { |
190 | 7.39k | PARENT(nei) = actnode + 1; |
191 | 7.39k | IGRAPH_CHECK(igraph_dqueue_int_push(&q, nei)); |
192 | 7.39k | } |
193 | 10.3k | } |
194 | 3.25k | } /* while q !empty */ |
195 | | /* Ok, now use PARENT to create the path */ |
196 | 1.75k | while (t1 != minvertex) { |
197 | 1.28k | VECTOR(*cycle)[idx++] = t1; |
198 | 1.28k | t1 = PARENT(t1) - 1; |
199 | 1.28k | } |
200 | 476 | VECTOR(*cycle)[idx] = minvertex; |
201 | 476 | idx = mincirc - 1; |
202 | 1.89k | while (t2 != minvertex) { |
203 | 1.41k | VECTOR(*cycle)[idx--] = t2; |
204 | 1.41k | t2 = PARENT(t2) - 1; |
205 | 1.41k | } |
206 | 476 | } /* anycircle */ |
207 | 1.88k | } /* circle */ |
208 | 1.88k | #undef PARENT |
209 | | |
210 | 1.88k | igraph_vector_int_destroy(&level); |
211 | 1.88k | igraph_dqueue_int_destroy(&q); |
212 | 1.88k | igraph_lazy_adjlist_destroy(&adjlist); |
213 | 1.88k | IGRAPH_FINALLY_CLEAN(3); |
214 | | |
215 | 1.88k | return IGRAPH_SUCCESS; |
216 | 1.88k | } |