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