Skip to content

Commit c5fea87

Browse files
committed
Add FlowNetwork implementation and Ford-Fulkerson algorithm
1 parent 5577e16 commit c5fea87

14 files changed

+431
-4
lines changed

.travis.yml

+3-3
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@ script:
1212
- make
1313
- cd ..
1414
- echo "C++ tests"
15-
- build/general/test/general_gtest
16-
- build/unweighted_graph/test/unweighted_graph_gtest
15+
- find build/ -name "*_gtest" -exec {} \;
1716
- echo "C++ examples"
1817
- build/unweighted_graph/unweighted_graph_demo tinyG.txt
1918
- build/unweighted_digraph/unweighted_digraph_demo tinyDG.txt
2019
- build/weighted_graph/weighted_graph_demo tinyEWG.txt
2120
- build/weighted_digraph/weighted_digraph_demo tinyEWD.txt
22-
- build/weighted_digraph/weighted_digraph_demo tinyEWDAG.txt
21+
- build/weighted_digraph/weighted_digraph_demo tinyEWDAG.txt
22+
- build/flow_network/flow_network_demo flow_network/tinyFN.txt

CMakeLists.txt

+2-1
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,5 @@ include_directories(general/include)
1717
add_subdirectory(unweighted_graph)
1818
add_subdirectory(unweighted_digraph)
1919
add_subdirectory(weighted_graph)
20-
add_subdirectory(weighted_digraph)
20+
add_subdirectory(weighted_digraph)
21+
add_subdirectory(flow_network)

README.md

+14
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,19 @@
8181

8282
![](picTinyEWDAG.png)
8383

84+
## Flow networks (`flow_network/`)
85+
86+
### `include/`
87+
- `FlowEdge` as an edge with capacity and flow in a [flow network](https://en.wikipedia.org/wiki/Flow_network)
88+
- `FlowNetwork` and `AdjancyListFlowNetwork` as interface and implementation of a flow network
89+
- `FordFulkerson` as an implementation of the [Ford-Fulkerson algorithm](https://en.wikipedia.org/wiki/Ford%E2%80%93Fulkerson_algorithm) to solve the min-cut and max-flow problems in flow networks
90+
91+
### `flow_network_demo.cpp`
92+
- Reads flow network from file or standard input and prints it
93+
- Calculate max-flow and min-cut
94+
95+
![](flow_network/picTinyFN.png)
96+
8497

8598
## Compilation and execution
8699
- Download submodules (for unit tests): `git submodule update --init --recursive`
@@ -99,6 +112,7 @@
99112
- `build/weighted_graph/weighted_graph_demo tinyEWG.txt`
100113
- `build/weighted_digraph/weighted_digraph_demo tinyEWD.txt`
101114
- `build/weighted_digraph/weighted_digraph_demo tinyEWDAG.txt`
115+
- `build/flow_network/flow_network_demo flow_network/tinyFN.txt`
102116

103117
## References
104118
- Introduction to Algorithms by Cormen et al.

flow_network/CMakeLists.txt

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
include_directories(include)
2+
3+
add_executable(flow_network_demo flow_network_demo.cpp)
4+
5+
add_subdirectory(test)

flow_network/flow_network_demo.cpp

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
#include <iostream>
2+
#include <fstream>
3+
#include <memory>
4+
#include "AdjacencyListFlowNetwork.h"
5+
#include "FordFulkerson.h"
6+
7+
int main(int argc, char* argv[]) {
8+
std::unique_ptr<FlowNetwork> fn;
9+
if(argc > 1) {
10+
std::cout<<"Reading flow network from file '"<<argv[1]<<"'...\n";
11+
std::ifstream ifs(argv[1]);
12+
if (!ifs) throw std::invalid_argument("Cannot open file");
13+
fn = std::make_unique<AdjacencyListFlowNetwork>(ifs);
14+
} else {
15+
std::cout<<"Reading flow network from standard input...\n";
16+
fn = std::make_unique<AdjacencyListFlowNetwork>(std::cin);
17+
}
18+
19+
std::cout << *fn << "\n";
20+
std::cout<<"---\n";
21+
22+
if (fn->V() > 1) {
23+
FordFulkerson ff(*fn, 0, fn->V()-1);
24+
std::cout<< "Max flow: "<<ff.getValue()<<"\n";
25+
std::cout<< "Source-side vertices in min cut (source="<<0<<", sink="<<fn->V()-1<<"): ";
26+
for (int i=0; i < fn->V(); ++i) {
27+
if (ff.isInCut(i)) {
28+
std::cout << i << " ";
29+
}
30+
}
31+
std::cout<<"\n";
32+
}
33+
34+
return 0;
35+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
#ifndef WEIGHTED_GRAPHS_CPP_ADJACENCYLISTFLOWNETWORK_H
2+
#define WEIGHTED_GRAPHS_CPP_ADJACENCYLISTFLOWNETWORK_H
3+
4+
#include <istream>
5+
#include "FlowNetwork.h"
6+
7+
class AdjacencyListFlowNetwork : public FlowNetwork {
8+
public:
9+
explicit AdjacencyListFlowNetwork(const int numVertices)
10+
: numV(numVertices), numE(0), edgesByVertex(numVertices, std::vector<std::shared_ptr<FlowEdge>>{}) {}
11+
12+
explicit AdjacencyListFlowNetwork(std::istream& is) {
13+
if (!(is >> numV) || numV < 0) throw std::invalid_argument("Invalid format (V)");
14+
if (!(is >> numE || numE < 0)) throw std::invalid_argument("Invalid format (E)");
15+
edgesByVertex.insert(edgesByVertex.end(), numV, {});
16+
17+
for (int i = 0; i < numE; ++i) {
18+
int fromVertex, toVertex;
19+
double capacity;
20+
if (!(is >> fromVertex >> toVertex >> capacity)) throw std::invalid_argument("Invalid format (edges)");
21+
if (fromVertex < 0 || fromVertex >= numV || toVertex < 0 || toVertex >= numV || capacity <= 0 || !std::isfinite(capacity)) {
22+
throw std::invalid_argument("Cannot create flow edge v->w");
23+
}
24+
edgesByVertex[fromVertex].emplace_back(std::make_shared<FlowEdge>(fromVertex, toVertex, capacity));
25+
edgesByVertex[toVertex].emplace_back(edgesByVertex[fromVertex].back());
26+
}
27+
}
28+
29+
void addEdge(const FlowEdge& e) override {
30+
const int fromVertex = e.from();
31+
const int toVertex = e.to();
32+
if(!validVertex(fromVertex) || !validVertex(toVertex)) {
33+
throw std::invalid_argument("Vertex IDs invalid");
34+
}
35+
if (e.capacity() < 0 || !std::isfinite(e.capacity())) {
36+
throw std::invalid_argument("Invalid capacity");
37+
}
38+
39+
edgesByVertex[fromVertex].emplace_back(std::make_shared<FlowEdge>(e));
40+
edgesByVertex[toVertex].emplace_back(edgesByVertex[fromVertex].back());
41+
++numE;
42+
}
43+
44+
[[nodiscard]]
45+
const std::vector<std::shared_ptr<FlowEdge>>& adj(int v) const override {
46+
if(!validVertex(v)) throw std::invalid_argument("Invalid vertex ID");
47+
return edgesByVertex[v];
48+
}
49+
50+
[[nodiscard]]
51+
std::vector<std::shared_ptr<FlowEdge>> edges() const override {
52+
std::vector<std::shared_ptr<FlowEdge>> result;
53+
result.reserve(numE);
54+
for (size_t i=0; i<edgesByVertex.size(); ++i) {
55+
const auto& edgesVec = edgesByVertex[i];
56+
57+
for(const auto& edge : edgesVec) {
58+
if (static_cast<int>(i) == edge->from()) {
59+
// only add edges once
60+
result.emplace_back(edge);
61+
}
62+
}
63+
}
64+
65+
return result;
66+
}
67+
68+
[[nodiscard]]
69+
int V() const override {
70+
return numV;
71+
}
72+
73+
[[nodiscard]]
74+
int E() const override {
75+
return numE;
76+
}
77+
78+
private:
79+
int numV {};
80+
int numE {};
81+
std::vector<std::vector<std::shared_ptr<FlowEdge>>> edgesByVertex {};
82+
83+
[[nodiscard]]
84+
bool validVertex(int v) const {
85+
return v >= 0 && static_cast<size_t>(v) < edgesByVertex.size();
86+
}
87+
};
88+
89+
#endif //WEIGHTED_GRAPHS_CPP_ADJACENCYLISTFLOWNETWORK_H

flow_network/include/FlowEdge.h

+84
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
#ifndef WEIGHTED_GRAPHS_CPP_FLOWEDGE_H
2+
#define WEIGHTED_GRAPHS_CPP_FLOWEDGE_H
3+
4+
#include <ostream>
5+
#include <unordered_set>
6+
7+
class FlowEdge {
8+
public:
9+
explicit FlowEdge(int _from, int _to, double _capacity) :
10+
vertexFrom{_from}, vertexTo{_to}, edgeCapacity{_capacity}
11+
{}
12+
13+
// return start vertex
14+
[[nodiscard]]
15+
int from() const {
16+
return vertexFrom;
17+
}
18+
19+
// return end vertex
20+
[[nodiscard]]
21+
int to() const {
22+
return vertexTo;
23+
}
24+
25+
// return other vertex
26+
[[nodiscard]]
27+
int other(const int v) const {
28+
if (v == vertexFrom) {
29+
return vertexTo;
30+
} else if (v == vertexTo) {
31+
return vertexFrom;
32+
} else {
33+
throw std::invalid_argument("Invalid vertex");
34+
}
35+
}
36+
37+
[[nodiscard]]
38+
double capacity() const {
39+
return edgeCapacity;
40+
}
41+
42+
[[nodiscard]]
43+
double flow() const {
44+
return edgeFlow;
45+
}
46+
47+
[[nodiscard]]
48+
double residualCapacityTo(const int v) const {
49+
if (v == vertexTo) {
50+
return edgeCapacity - edgeFlow;
51+
}
52+
else if (v == vertexFrom) {
53+
return edgeFlow;
54+
} else {
55+
throw std::invalid_argument("Invalid vertex");
56+
}
57+
}
58+
59+
void addResidualFlowTo(const int v, const double delta) {
60+
if (delta > residualCapacityTo(v)) {
61+
throw std::invalid_argument("Delta too high");
62+
}
63+
64+
if (v == vertexTo) {
65+
// forward
66+
edgeFlow += delta;
67+
} else { // validity already checked above
68+
// backward
69+
edgeFlow -= delta;
70+
}
71+
}
72+
73+
private:
74+
int vertexFrom;
75+
int vertexTo;
76+
double edgeCapacity;
77+
double edgeFlow = 0;
78+
};
79+
80+
inline std::ostream& operator<<(std::ostream& os, const FlowEdge& e) {
81+
return os << e.from() << "->"<< e.to()<<" "<< e.flow() << "/" << e.capacity();
82+
}
83+
84+
#endif //WEIGHTED_GRAPHS_CPP_FLOWEDGE_H

flow_network/include/FlowNetwork.h

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
#ifndef WEIGHTED_GRAPHS_CPP_FLOWNETWORK_H
2+
#define WEIGHTED_GRAPHS_CPP_FLOWNETWORK_H
3+
4+
#include <vector>
5+
#include <ostream>
6+
#include <cmath>
7+
#include "FlowEdge.h"
8+
#include <memory>
9+
10+
class FlowNetwork {
11+
public:
12+
// add edge to digraph
13+
virtual void addEdge(const FlowEdge& e) = 0;
14+
15+
// get all forward and backward edges from vertex v
16+
[[nodiscard]]
17+
virtual const std::vector<std::shared_ptr<FlowEdge>>& adj(int v) const = 0;
18+
19+
// get all edges in network
20+
[[nodiscard]]
21+
virtual std::vector<std::shared_ptr<FlowEdge>> edges() const = 0;
22+
23+
// get number of vertices
24+
[[nodiscard]]
25+
virtual int V() const = 0;
26+
27+
// get number of edges
28+
[[nodiscard]]
29+
virtual int E() const = 0;
30+
31+
// virtual destructor
32+
virtual ~FlowNetwork() = default;
33+
};
34+
35+
std::ostream& operator<<(std::ostream &os, const FlowNetwork& graph) {
36+
os<<"FlowNetwork (V="<<graph.V()<<", E="<<graph.E()<<")\n";
37+
for(int i=0; i<graph.V(); ++i) {
38+
os<<"Vertex "<<i<<": ";
39+
for(size_t idxEdge=0; idxEdge < graph.adj(i).size(); ++idxEdge) {
40+
const bool isLast = idxEdge == graph.adj(i).size() - 1;
41+
const auto& edge = graph.adj(i)[idxEdge];
42+
os<<*edge;
43+
if (!isLast) os << ", ";
44+
}
45+
os<<"\n";
46+
}
47+
os<<"\n";
48+
return os;
49+
}
50+
51+
52+
#endif //WEIGHTED_GRAPHS_CPP_FLOWNETWORK_H

flow_network/include/FordFulkerson.h

+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
#ifndef GRAPHS_CPP_FORDFULKERSON_H
2+
#define GRAPHS_CPP_FORDFULKERSON_H
3+
4+
#include <deque>
5+
#include <queue>
6+
#include "FlowNetwork.h"
7+
8+
// Ford-Fulkerson algorithm for min-cut/max-flow problem
9+
class FordFulkerson {
10+
public:
11+
12+
FordFulkerson(const FlowNetwork& fn, const int s, const int t) {
13+
value = 0.0;
14+
while (hasAugmentingPath(fn, s, t)) {
15+
double bottleneck = std::numeric_limits<double>::infinity();
16+
for (int v=t; v != s; v = edgeTo[v]->other(v)) {
17+
bottleneck = std::min(bottleneck, edgeTo[v]->residualCapacityTo(v));
18+
}
19+
for (int v=t; v!= s; v = edgeTo[v]->other(v)) {
20+
edgeTo[v]->addResidualFlowTo(v, bottleneck);
21+
}
22+
value += bottleneck;
23+
}
24+
}
25+
26+
[[nodiscard]]
27+
double getValue() const {
28+
return value;
29+
}
30+
31+
[[nodiscard]]
32+
bool isInCut(const int v) const {
33+
return v >= 0 && static_cast<size_t>(v) < marked.size() && marked[v];
34+
}
35+
36+
[[nodiscard]]
37+
bool hasAugmentingPath(const FlowNetwork& fn, const int s, const int t) {
38+
edgeTo.clear();
39+
edgeTo.insert(edgeTo.begin(), fn.V(), {});
40+
marked.clear();
41+
marked.insert(marked.begin(), fn.V(), false);
42+
43+
std::queue<int> queue{};
44+
queue.push(s);
45+
marked[s] = true;
46+
while (!queue.empty()) {
47+
const int v = queue.front();
48+
queue.pop();
49+
for (const auto& edge : fn.adj(v)) {
50+
const int w = edge->other(v);
51+
if (edge->residualCapacityTo(w) > 0 && !marked[w]) {
52+
edgeTo[w] = edge;
53+
marked[w] = true;
54+
queue.push(w);
55+
}
56+
}
57+
}
58+
return marked[t];
59+
}
60+
private:
61+
std::deque<bool> marked; // if path from start to v is in residual network
62+
std::vector<std::shared_ptr<FlowEdge>> edgeTo; // from which vertex to get to v
63+
double value; // max flow value
64+
};
65+
#endif //GRAPHS_CPP_FORDFULKERSON_H

flow_network/picTinyFN.png

23.7 KB
Loading

flow_network/test/CMakeLists.txt

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
set(BINARY flow_network_gtest)
2+
3+
add_executable(${BINARY} test_flow_edge.cpp test_flow_network_adj_list.cpp)
4+
5+
add_test(NAME ${BINARY} COMMAND ${BINARY})
6+
7+
target_link_libraries(${BINARY} PUBLIC gtest gtest_main)

0 commit comments

Comments
 (0)