cgv
Loading...
Searching...
No Matches
adjacency_list.h
1#pragma once
2
3#include <algorithm>
4#include <numeric>
5#include <queue>
6#include <utility>
7#include <vector>
8
9#include <assert.h>
10
11namespace cgv{
12namespace math{
13
15struct edge
16{
18 size_t start = 0;
20 size_t end = 0;
21};
22
23template<typename T>
24struct weighted_edge : public edge
25{
26 using weight_type = T;
27
29 T weight = {};
30};
31
33template<typename EdgeT>
34struct vertex
35{
37 using edge_type = EdgeT;
38
40 std::vector<edge_type> edges;
41};
42
44enum class EdgeOrientation {
45 Undirected, // the graph contains only undirected edges
46 Directed, // the graph contains only directed edges; inverse directed edges are allowed
47};
48
76template<typename VertexT>
78public:
79 using vertex_type = VertexT;
80 using edge_type = typename VertexT::edge_type;
81
83 adjacency_list(EdgeOrientation edge_orientation = EdgeOrientation::Undirected) : _edge_orientation(edge_orientation) {}
84
86 adjacency_list(size_t vertex_count, EdgeOrientation edge_orientation = EdgeOrientation::Undirected) : _edge_orientation(edge_orientation) {
87 _vertices.resize(vertex_count);
88 }
89
91 void resize(size_t vertex_count) {
93 _vertices.resize(vertex_count);
94 }
95
97 bool is_directed() const {
98 return _edge_orientation == EdgeOrientation::Directed;
99 }
100
102 void clear() {
103 _vertices.clear();
104 }
105
107 bool empty() const {
108 return _vertices.empty();
109 }
110
112 size_t vertex_count() const {
113 return _vertices.size();
114 }
115
117 size_t edge_count() const {
118 size_t count = std::accumulate(_vertices.begin(), _vertices.end(), 0, [](const vertex_type& vertex, size_t count) { return count + vertex.edges.size(); });
119 // Undirected edges are stored twice so we need to divide the count by 2
120 if(!is_directed())
121 return count / 2;
122 return count;
123 }
124
127 std::for_each(_vertices.begin(), _vertices.end(), [](vertex_type& vertex) { vertex.edges.clear(); });
128 }
129
131 vertex_type& vertex(size_t i) {
132 return _vertices[i];
133 }
134
136 const vertex_type& vertex(size_t i) const {
137 return _vertices[i];
138 }
139
141 const std::vector<edge_type> to_edge_list() const {
142 std::vector<edge_type> edges;
143 for(const auto& vertex : _vertices)
144 std::copy(vertex.edges.begin(), vertex.edges.end(), std::inserter(edges, edges.end()));
145 return edges;
146 }
147
149 size_t add_vertex(const vertex_type& vertex) {
150 _vertices.push_back(vertex);
151 return _vertices.size() - 1;
152 }
153
155 bool add_edge(const edge_type& edge) {
157 return false;
158
159 if(edge.start < vertex_count() && edge.end < vertex_count()) {
160 vertex(edge.start).edges.push_back(edge);
161 if(!is_directed()) {
162 // build a reverse edge by copying the new edge and its properties and swapping its start and end indices
163 edge_type reverse_edge = edge;
164 std::swap(reverse_edge.start, reverse_edge.end);
165 vertex(edge.end).edges.push_back(reverse_edge);
166 }
167 return true;
168 }
169 return false;
170 }
171
173 bool add_edge(size_t start, size_t end) {
174 return add_edge({ start, end });
175 }
176
178 bool edge_exists(size_t start, size_t end) const {
179 for(const edge_type& edge : vertex(start).edges) {
180 if(edge.end == end)
181 return true;
182 }
183 return false;
184 }
185
187 bool is_cyclic() const {
188 if(is_directed()) {
189 // If a directed graph cannot be sorted topologically it contains a cycle
190 return !topological_sort_impl();
191 } else {
192 // Keep track of visited vertices
193 std::vector<bool> visited(vertex_count(), false);
194
195 // Perform BFS from every unvisited node
196 for(size_t i = 0; i < vertex_count(); ++i) {
197 if(!visited[i]) {
198 // If BFS finds a cycle
199 if(has_cycle_undirected_breadth_first(i, visited))
200 return true;
201 }
202 }
203
204 // If no cycle is found in any component
205 return false;
206 }
207 }
208
209 // return the graph vertex indices in topological order; only works for directed graphs; if the graph is undirected or contains a cycle an empty list is returned
210 std::vector<size_t> topological_sort() const {
211 std::vector<size_t> vertices;
212 if(!topological_sort_impl(&vertices))
213 return {};
214 return vertices;
215 }
216
217private:
218 // search for a cycle from a given start vertex in an undirected graph and return true if the graph contains a cycle
219 bool has_cycle_undirected_breadth_first(size_t start, std::vector<bool>& visited) const {
220 // Queue stores { current vertex, parent vertex }
221 std::queue<std::pair<size_t, size_t>> edge_queue;
222
223 // maker for invalid indices akin to std::string::npos
224 constexpr size_t nindex = static_cast<size_t>(-1);
225
226 // Start node has no parent
227 edge_queue.push({ start, nindex });
228 visited[start] = true;
229
230 while(!edge_queue.empty()) {
231 size_t node = edge_queue.front().first;
232 size_t parent = edge_queue.front().second;
233 edge_queue.pop();
234
235 // Traverse all neighbors of current node
236 for(const edge_type& edge : vertex(node).edges) {
237
238 // If neighbor is not visited, mark it visited and push to queue
239 if(!visited[edge.end]) {
240 visited[edge.end] = true;
241 edge_queue.push({ edge.end, node });
242 } else if(edge.end != parent) {
243 // If neighbor is visited and not parent, a cycle is detected
244 return true;
245 }
246 }
247 }
248
249 // No cycle found starting from this node
250 return false;
251 }
252
253 // return true if the graph vertices can be sorted in topological order; only works for directed graphs; if the graph is undirected or contains a cycle false is returned;
254 // if ordered_vertices is supplied it will be filled with the vertex indices in topological order if the function returned true
255 bool topological_sort_impl(std::vector<size_t>* ordered_vertices = nullptr) const {
256 if(!is_directed())
257 return false;
258
259 // Array to store in-degree of each vertex
260 std::vector<size_t> in_degree(vertex_count(), 0);
261
262 // Compute in-degrees of all vertices
263 for(const vertex_type& vertex : _vertices) {
264 for(const edge_type& edge : vertex.edges)
265 in_degree[edge.end]++;
266 }
267
268 std::queue<size_t> vertex_queue;
269
270 // Add all vertices with in-degree 0 to the queue
271 for(size_t i = 0; i < in_degree.size(); ++i) {
272 if(in_degree[i] == 0)
273 vertex_queue.push(i);
274 }
275
276 if(ordered_vertices)
277 ordered_vertices->reserve(vertex_count());
278
279 // Count of visited (processed) nodes
280 size_t visited_count = 0;
281
282 // Perform breadth first serach (Topological Sort)
283 while(!vertex_queue.empty()) {
284 size_t i = vertex_queue.front();
285 vertex_queue.pop();
286 visited_count++;
287 if(visited_count > vertex_count())
288 return false;
289
290 // Add the vertex to the output list
291 if(ordered_vertices)
292 ordered_vertices->push_back(i);
293
294 // Reduce in-degree of neighbors
295 for(const auto& edge : vertex(i).edges) {
296 in_degree[edge.end]--;
297 if(in_degree[edge.end] == 0) {
298 // Add to queue when in-degree becomes 0
299 vertex_queue.push(edge.end);
300 }
301 }
302 }
303
304 // If visited nodes != total nodes, a cycle exists
305 return visited_count == vertex_count();
306 }
307
309 std::vector<vertex_type> _vertices;
311 EdgeOrientation _edge_orientation = EdgeOrientation::Undirected;
312};
313
314using graph = adjacency_list<vertex<edge>>;
315template<typename T>
316using weighted_graph = adjacency_list<vertex<weighted_edge<T>>>;
317
318} // namespace math
319} // namespace cgv
A graph represented as an adjacency list.
adjacency_list(size_t vertex_count, EdgeOrientation edge_orientation=EdgeOrientation::Undirected)
create a graph with the given edge_orientation and vertex_count default-initialized vertices and zero...
size_t add_vertex(const vertex_type &vertex)
add a new vertex to graph and return its index
void remove_all_edges()
removes all edges
bool is_directed() const
return true if the graph is directed
vertex_type & vertex(size_t i)
access to vertex i
size_t vertex_count() const
return the number of vertices, i.e. the order of the graph
bool add_edge(size_t start, size_t end)
add a default-initialized edge definded by the start and end vertex to the list; return false if the ...
bool edge_exists(size_t start, size_t end) const
check if edge is already in list
void resize(size_t vertex_count)
resize number of vertices, all edge data is removed
size_t edge_count() const
return the number of edges, i.e. the size of the graph
bool empty() const
return true if the graph does not contain any vertices
bool add_edge(const edge_type &edge)
add an edge to the list; return false if the edge already exists, true otherwise
const std::vector< edge_type > to_edge_list() const
return a list of all edges in no particular order
bool is_cyclic() const
return true if the graph contains at least one cycle
const vertex_type & vertex(size_t i) const
const access to vertex i
adjacency_list(EdgeOrientation edge_orientation=EdgeOrientation::Undirected)
create a graph with the given edge_orientation
this header is dependency free
Definition print.h:11
a basic graph edge type
size_t end
the index of the end vertex
size_t start
the index of the start vertex
a basic graph node type
std::vector< edge_type > edges
incident edges
EdgeT edge_type
the used edge type
T weight
the edge weight