Skip to content

Commit 037939d

Browse files
committed
Improve code structure (DFS/BFS as functions with result datatype)
1 parent abdc6be commit 037939d

File tree

2 files changed

+137
-116
lines changed

2 files changed

+137
-116
lines changed

Graph.h

+108-90
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#ifndef GRAPHS_CPP_GRAPH_H
22
#define GRAPHS_CPP_GRAPH_H
33

4+
#include <utility>
45
#include <vector>
56
#include <sstream>
67
#include <algorithm>
@@ -9,84 +10,92 @@
910

1011
namespace graph {
1112

13+
// Interface of a graph
1214
class Graph {
1315
public:
14-
virtual void addEdge(int v, int w) = 0; // add edge v-w
15-
[[nodiscard]] virtual const std::vector<int>& adj(int v) const = 0; // get edges adjacent to v
16+
virtual void addEdge(int v, int w) = 0; // add edge v-w to graph
17+
[[nodiscard]] virtual const std::vector<int>& adj(int v) const = 0; // get vertices adjacent to v
1618
[[nodiscard]] virtual int V() const = 0; // number of vertices
1719
[[nodiscard]] virtual int E() const = 0; // number of edges
1820
[[nodiscard]] virtual std::string toString() const = 0; // create string representation
1921
[[nodiscard]] virtual bool vertexValid(int v) const = 0; // whether a vertex exists
2022
virtual ~Graph() = default; // virtual destructor
2123
};
2224

25+
// Graph built with adjacency list
2326
class AdjacencyListGraph : public Graph {
2427
public:
2528
// create graph with V vertices
2629
explicit AdjacencyListGraph(int V) :
2730
numV(V),
2831
numE(0),
29-
vertices(V, std::vector<int>{}) {}
32+
adjacencies(V, std::vector<int>{}) {}
3033

3134
// create graph with input stream
3235
explicit AdjacencyListGraph(std::istream &is) {
3336
if (!(is >> numV) || numV < 0) throw std::invalid_argument("Invalid format (V)");
3437
if (!(is >> numE || numE < 0)) throw std::invalid_argument("Invalid format (E)");
35-
vertices.insert(vertices.end(), numV, {});
38+
adjacencies.insert(adjacencies.end(), numV, {});
3639
for (int i = 0; i < numE; ++i) {
3740
int v1, v2;
3841
if (!(is >> v1 >> v2)) throw std::invalid_argument("Invalid format (edges)");
3942
if (v1 < 0 || v1 >= numV || v2 < 0 || v2 >= numV) {
4043
throw std::invalid_argument("Cannot create edge v1-v2");
4144
}
42-
vertices[v1].push_back(v2);
43-
vertices[v2].push_back(v1);
45+
adjacencies[v1].push_back(v2);
46+
adjacencies[v2].push_back(v1);
4447
}
4548
}
4649

50+
// add edge v-w to graph
4751
void addEdge(int v, int w) override {
4852
if (!vertexValid(v) || !vertexValid(w)) {
4953
throw std::invalid_argument("Cannot create edge v-w");
5054
}
51-
vertices[v].push_back(w);
52-
vertices[w].push_back(v);
55+
adjacencies[v].push_back(w);
56+
adjacencies[w].push_back(v);
5357
++numE;
5458
}
5559

60+
// get vertices adjacent to v
5661
[[nodiscard]] const std::vector<int>& adj(int v) const override {
5762
if (!vertexValid(v)) {
5863
throw std::invalid_argument("Invalid vertex");
5964
}
60-
return vertices[v];
65+
return adjacencies[v];
6166
}
6267

68+
// number of vertices
6369
[[nodiscard]] int V() const override {
6470
return numV;
6571
}
6672

73+
// number of edges
6774
[[nodiscard]] int E() const override {
6875
return numE;
6976
}
7077

78+
// create string representation
7179
[[nodiscard]] std::string toString() const override {
7280
std::stringstream ss;
7381
ss << "[Graph with " << numV << " vertices and " << numE << " edges]\n";
74-
for (size_t i = 0; i < vertices.size(); ++i) {
75-
for (const auto edge : vertices[i]) {
82+
for (size_t i = 0; i < adjacencies.size(); ++i) {
83+
for (const auto edge : adjacencies[i]) {
7684
ss << i << "-" << edge << "\n";
7785
}
7886
}
7987
return ss.str();
8088
}
8189

90+
// whether a vertex exists
8291
[[nodiscard]] bool vertexValid(int v) const override {
8392
return v >= 0 && v < numV;
8493
}
8594

8695
private:
8796
int numV = 0; // vertices
8897
int numE = 0; // edges
89-
std::vector<std::vector<int>> vertices{}; // vector with V elements each of size degree(vertex) to represent edges
98+
std::vector<std::vector<int>> adjacencies{}; // vector with V elements each of size degree(vertex) to represent edges
9099

91100
};
92101

@@ -122,22 +131,47 @@ namespace graph {
122131
return num/2; // each self-loop is counted twice
123132
}
124133

125-
// Depth First Search for paths starting at a specific vertex
126-
class PathsFromDFS {
134+
namespace internal {
135+
// return whether element v exists in a collection col
136+
template<typename Col>
137+
[[nodiscard]] inline bool validIndex(const Col& col, const int v) {
138+
if(v < 0 || static_cast<size_t>(v) >= col.size()) {
139+
return false;
140+
} else {
141+
return true;
142+
}
143+
}
144+
145+
} // namespace internal
146+
147+
// Result of a search for all paths reachable from a specific vortex
148+
class PathsFromVertexResult {
127149
public:
128-
explicit PathsFromDFS(const Graph &g, const int _start) :
129-
start {_start}, marked(g.V(), false), edgeTo(g.V(), -1) {
130-
dfs(g, start);
150+
// Construct by copying fields
151+
explicit PathsFromVertexResult(int _start, const std::vector<int>& _distTo, const std::vector<int>& _edgeTo) // NOLINT
152+
:
153+
start(_start), distTo(_distTo), edgeTo(_edgeTo) {}
154+
155+
// Construct by moving fields
156+
explicit PathsFromVertexResult(int _start, std::vector<int>&& _distTo, std::vector<int>&& _edgeTo)
157+
:
158+
start(_start), distTo(std::move(_distTo)), edgeTo(std::move(_edgeTo)) {}
159+
160+
// from which vertex the search was started
161+
[[nodiscard]] int from() const {
162+
return start;
131163
}
132164

133-
[[nodiscard]] bool hasPathTo(int v) const {
134-
if(!validVertex(v)) {
165+
// whether a path from start to v exists
166+
[[nodiscard]] bool hasPathTo(const int v) const {
167+
if(!internal::validIndex(distTo, v)) {
135168
return false;
136169
} else {
137-
return marked[v];
170+
return distTo[v] != -1;
138171
}
139172
}
140173

174+
// the saved path from start to v or an empty result
141175
[[nodiscard]] std::vector<int> pathTo(const int v) const {
142176
if(!hasPathTo(v)) {
143177
return {};
@@ -155,98 +189,82 @@ namespace graph {
155189
return path;
156190
}
157191

192+
[[nodiscard]] int distanceTo(const int v) const {
193+
if(!internal::validIndex(distTo, v)) {
194+
return -1;
195+
} else {
196+
return distTo[v];
197+
}
198+
}
158199
private:
159200
const int start; // from which vertex the search started
160-
std::deque<bool> marked; // whether a vertex was already visited
161-
std::vector<int> edgeTo; // from which vertex another vertex was visited from the first time
162-
163-
void dfs(const Graph& g, const int v) { // NOLINT (recursion)
164-
marked[v] = true; // mark as visited
165-
for(const int other : g.adj(v)) {
166-
if(!marked[other]) {
167-
dfs(g, other);
168-
edgeTo[other] = v; // save that we got to other from v
201+
const std::vector<int> distTo; // number of steps from start to vertex
202+
const std::vector<int> edgeTo; // from which vertex another vertex was visited from the first time
203+
};
204+
205+
namespace depth_first_search {
206+
namespace internal {
207+
// Recursive function for depth first search to all vertices from v
208+
void fromVertexToAllRec(const Graph& g, const int v, // NOLINT
209+
std::vector<int>& distTo, std::vector<int>& edgeTo) {
210+
for(const int other : g.adj(v)) {
211+
if(distTo[other] == -1) {
212+
// not yet visited
213+
distTo[other] = distTo[v]+1;
214+
edgeTo[other] = v; // save that we got to other from v
215+
fromVertexToAllRec(g, other, distTo, edgeTo);
216+
}
169217
}
170218
}
171219
}
172220

173-
[[nodiscard]] bool validVertex(const int v) const {
174-
if(v < 0 || static_cast<size_t>(v) >= marked.size()) {
175-
return false;
176-
} else {
177-
return true;
178-
}
221+
// Depth-first search from vertex start to all reachable vertices
222+
PathsFromVertexResult fromVertexToAll(const Graph &g, const int start) {
223+
if(!g.vertexValid(start)) throw std::invalid_argument("Invalid start vertex");
224+
225+
std::vector<int> distTo(g.V(), -1); // number of steps from start to vertex
226+
std::vector<int> edgeTo(g.V(), -1); // from which vertex another vertex was visited from the first time
227+
228+
distTo[start] = 0;
229+
// start recursive depth-first start from start
230+
internal::fromVertexToAllRec(g, start, distTo, edgeTo);
231+
232+
// return queryable results
233+
return PathsFromVertexResult(start, std::move(distTo), std::move(edgeTo));
179234
}
180-
};
235+
} // depth_first_search
236+
237+
namespace breadth_first_search {
238+
// Breadth-first search from vertex start to all reachable vertices
239+
PathsFromVertexResult fromVertexToAll(const Graph &g, const int start) {
240+
if(start < 0 || start >= g.V()) throw std::invalid_argument("Invalid start vertex");
241+
242+
std::vector<int> distTo(g.V(), -1); // number of steps from start to vertex
243+
std::vector<int> edgeTo(g.V(), -1); // from which vertex another vertex was visited from the first time
181244

182-
// Breadth First Search for paths starting at a specific vertex
183-
class PathsFromBFS {
184-
public:
185-
explicit PathsFromBFS(const Graph &g, const int _start) :
186-
start {_start}, distTo(g.V(), -1), edgeTo(g.V(), -1) {
187245
std::queue<int> queue{};
188246
queue.push(start);
189247
distTo[start] = 0;
190-
while(!queue.empty()) {
248+
while (!queue.empty()) {
249+
// remove vertex from queue
191250
const int v = queue.front();
192251
queue.pop();
193-
for(const int other : g.adj(v)) {
194-
if(distTo[other] == -1) {
252+
// add all yet unvisited adjacent vertices to queue and mark them
253+
for (const int other : g.adj(v)) {
254+
if (distTo[other] == -1) {
195255
// yet unvisited
196256
queue.push(other);
197257
edgeTo[other] = v;
198-
distTo[other] = distTo[v]+1;
258+
distTo[other] = distTo[v] + 1;
199259
}
200260
}
201261
}
202-
}
203-
204-
[[nodiscard]] bool hasPathTo(int v) const {
205-
if(!validVertex(v)) {
206-
return false;
207-
} else {
208-
return distTo[v] != -1;
209-
}
210-
}
211-
212-
[[nodiscard]] int distanceTo(int v) const {
213-
if(!validVertex(v)) {
214-
return -1;
215-
} else {
216-
return distTo[v];
217-
}
218-
}
219-
220-
[[nodiscard]] std::vector<int> pathTo(const int v) const {
221-
if(!hasPathTo(v)) {
222-
return {};
223-
}
224-
225-
// build path in the reverse direction
226-
std::vector<int> path{v};
227-
int prev = edgeTo[v];
228-
while(prev != -1) {
229-
path.push_back(prev);
230-
prev = edgeTo[prev];
231-
}
232262

233-
std::reverse(path.begin(), path.end());
234-
return path;
263+
// return queryable results
264+
return PathsFromVertexResult(start, std::move(distTo), std::move(edgeTo));
235265
}
236266

237-
private:
238-
const int start; // from which vertex the search started
239-
std::vector<int> distTo; // number of steps from start to vertex
240-
std::vector<int> edgeTo; // from which vertex another vertex was visited from the first time
241-
242-
[[nodiscard]] bool validVertex(const int v) const {
243-
if(v < 0 || static_cast<size_t>(v) >= distTo.size()) {
244-
return false;
245-
} else {
246-
return true;
247-
}
248-
}
249-
};
267+
}
250268

251269
} // namespace graph
252270

0 commit comments

Comments
 (0)