2

There is a C++ implementation of Dinic’s algorithm for Maximum Flow Problem that I was trying to use. In that C++ code, it is assumed that all parameters are integer. I tryied to convert the code to an equivalent one in which capacity of edges can be continuous (non-integer). Here is my code:

// C++ implementation of Dinic's Algorithm 
// #include "HEADERS.h"

#include<bits/stdc++.h> 



using namespace std; 

double EPSILON = 1e-6 ;
// A structure to represent a edge between 
// two vertex 
struct Edge 
{ 
    int v ; // Vertex v (or "to" vertex) 
            // of a directed edge u-v. "From" 
            // vertex u can be obtained using 
            // index in adjacent array. 

    double flow ; // flow of data in edge 

    double C; // capacity 

    int rev ; // To store index of reverse 
            // edge in adjacency list so that 
            // we can quickly find it. 
}; 

// Residual Graph 
class Graph 
{ 
    int V; // number of vertex 
    int *level ; // stores level of a node 
    vector< Edge > *adj; 
public : 
    Graph(int V) 
    { 
        adj = new vector<Edge>[V]; 
        this->V = V; 
        level = new int[V]; 
    } 

    // add edge to the graph 
    void addEdge(int u, int v, double C) 
    { 
        // Forward edge : 0 flow and C capacity 
        Edge a{v, 0, C, static_cast<int> (adj[v].size()) }; 

        // Back edge : 0 flow and 0 capacity 
        Edge b{u, 0, 0, static_cast<int> (adj[u].size()) }; 

        adj[u].push_back(a); 
        adj[v].push_back(b); // reverse edge 
    } 

    bool BFS(int s, int t); 
    int sendFlow(int s, double flow, int t, int ptr[]); 
    double DinicMaxflow(int s, int t); 
}; 

// Finds if more flow can be sent from s to t. 
// Also assigns levels to nodes. 
bool Graph::BFS(int s, int t) 
{ 
    for (int i = 0 ; i < V ; i++) 
        level[i] = -1; 

    level[s] = 0; // Level of source vertex 

    // Create a queue, enqueue source vertex 
    // and mark source vertex as visited here 
    // level[] array works as visited array also. 
    list< int > q; 
    q.push_back(s); 

    vector<Edge>::iterator i ; 
    while (!q.empty()) 
    { 
        int u = q.front(); 
        q.pop_front(); 
        for (i = adj[u].begin(); i != adj[u].end(); i++) 
        { 
            Edge &e = *i; 
            if (level[e.v] < 0 && e.flow < e.C) 
            { 
                // Level of current vertex is, 
                // level of parent + 1 
                level[e.v] = level[u] + 1; 

                q.push_back(e.v); 
            } 
        } 
    } 

    // IF we can not reach to the sink we 
    // return false else true 
    return level[t] < 0 ? false : true ; 
} 

// A DFS based function to send flow after BFS has 
// figured out that there is a possible flow and 
// constructed levels. This function called multiple 
// times for a single call of BFS. 
// flow : Current flow send by parent function call 
// start[] : To keep track of next edge to be explored. 
//       start[i] stores count of edges explored 
//       from i. 
// u : Current vertex 
// t : Sink 
int Graph::sendFlow(int u, double flow, int t, int start[]) 
{ 
    // Sink reached 
    if (u == t) 
        return flow; 

    // Traverse all adjacent edges one -by - one. 
    for ( ; start[u] < static_cast <int> (adj[u].size()) ; start[u]++) 
    { 
        // Pick next edge from adjacency list of u 
        Edge &e = adj[u][start[u]]; 

        if (level[e.v] == level[u]+1 && e.flow < e.C) 
        { 
            // find minimum flow from u to t 
            double curr_flow = min(flow, e.C - e.flow); 

            double temp_flow = sendFlow(e.v, curr_flow, t, start); 

            // flow is greater than zero 
            if (temp_flow > 0) 
            { 
                // add flow to current edge 
                e.flow += temp_flow; 

                // subtract flow from reverse edge 
                // of current edge 
                adj[e.v][e.rev].flow -= temp_flow; 
                return temp_flow; 
            } 
        } 
    } 

    return 0; 
} 
// Returns maximum flow in graph 
double Graph::DinicMaxflow(int s, int t) 
{ 
    // Corner case 
    if (s == t) 
        return -1; 

    double total = 0; // Initialize result 

    // Augment the flow while there is path 
    // from source to sink 
    while (BFS(s, t) == true) 
    { 
        // store how many edges are visited 
        // from V { 0 to V } 
        int *start = new int[V+1]; 
        double flow = sendFlow(s, INT_MAX, t, start) ;
        // while flow is not zero in graph from S to D 
        while (flow > EPSILON )
        {
            // Add path flow to overall flow 
            total += flow;
            flow = sendFlow(s, INT_MAX, t, start) ;
        }
    } 

    // return maximum flow 
    return total; 
} 

// Driver program to test above functions 
int main() 
{ 
    Graph g(6); 
    g.addEdge(0, 1, 16 ); 
    g.addEdge(0, 2, 13 ); 
    g.addEdge(1, 2, 10 ); 
    g.addEdge(1, 3, 12 ); 
    g.addEdge(2, 1, 4 ); 
    g.addEdge(2, 4, 14); 
    g.addEdge(3, 2, 9 ); 
    g.addEdge(3, 5, 20 ); 
    g.addEdge(4, 3, 7 ); 
    g.addEdge(4, 5, 4); 

    // next exmp 
    /*g.addEdge(0, 1, 3 ); 
    g.addEdge(0, 2, 7 ) ; 
    g.addEdge(1, 3, 9); 
    g.addEdge(1, 4, 9 ); 
    g.addEdge(2, 1, 9 ); 
    g.addEdge(2, 4, 9); 
    g.addEdge(2, 5, 4); 
    g.addEdge(3, 5, 3); 
    g.addEdge(4, 5, 7 ); 
    g.addEdge(0, 4, 10); 

    // next exp 
    g.addEdge(0, 1, 10); 
    g.addEdge(0, 2, 10); 
    g.addEdge(1, 3, 4 ); 
    g.addEdge(1, 4, 8 ); 
    g.addEdge(1, 2, 2 ); 
    g.addEdge(2, 4, 9 ); 
    g.addEdge(3, 5, 10 ); 
    g.addEdge(4, 3, 6 ); 
    g.addEdge(4, 5, 10 ); */
    cout << "Maximum flow " << g.DinicMaxflow(0, 5) << endl ; 
    return 0; 
} 

In this code, I have changed type of flow and C to double. I also changed some parts of the code to adapt it to this new double type. The code works only occusionally! It either outputs Maximum flow 23 which is the right output or throws a Segmentation fault (core dumped) error. I don't really know what is wrong in this code. Any ideas?

rasul
  • 1,009
  • 9
  • 14
  • 1
    `#include` No. Please include the appropriate header files, not this one. Second, you're using `std::vector` in some places, and in other places where you could use it to avoid memory leaks, you don't use it. Like here: `adj = new vector[V];`. This could be `std::vector> adj;` then `adj.resize(V);` or similar. – PaulMcKenzie Oct 29 '18 at 03:13
  • @PaulMcKenzie I have no idea what that library is! I think it is possible to use `#include #include #include #include #include ` instead. – rasul Oct 29 '18 at 03:19
  • 1
    Got a whole lot of uninitialized values here: `int *start = new int[V+1];` that are being passed into `sendFlow` and consumed. Bet you can find out more information about this problem using the [debugging tools](https://en.wikipedia.org/wiki/Debugger) that came with your development environment. Debuggers are the programmer's best friend. – user4581301 Oct 29 '18 at 03:19
  • 1
    [Big explanation of the dangers of and links to explanation of `#include `](https://stackoverflow.com/questions/31816095/why-should-i-not-include-bits-stdc-h) Rule of thumb is anything in `bits` is g++ implementation details that are not intended for public consumption. They are not covered by the C++ Standard and could radically change at a moment's notice. – user4581301 Oct 29 '18 at 03:22
  • Looking at the code on the linked explanation, the code only works by the grace of . The writer's not an idiot, but they are certainly careless. This is the danger of competition code. It only has to work once under controlled circumstances. When they wrote this code, it worked for them. But with a different compiler on different hardware... Sucks to be you. – user4581301 Oct 29 '18 at 03:28
  • @user4581301 When I run debugger (GDB) it says that `Warning: Debuggee TargetArchitecture not detected, assuming x86_64. =cmd-param-changed,param="pagination",value="off" Stopped due to shared library event (no libraries added or removed) Loaded '/lib64/ld-linux-x86-64.so.2'. Symbols loaded. Breakpoint 1, main () at MaxFlow.cpp:181 181 { [Inferior 1 (process 21711) exited normally] The program '/home/opt/Maxflow/MaxFlow' has exited with code 0 (0x00000000).`. – rasul Oct 29 '18 at 03:34
  • @user4581301 Most of this competition code sites start off with a somewhat good explanation of the problem and how to solve it. But the coding to solve the problem is bad, severely lacking, full of memory leaks, many times use non-standard C++, include the "bits.h" stuff, etc. – PaulMcKenzie Oct 29 '18 at 03:34
  • @Opt I was not expecting that, but such is undefined behaviour. Sometimes it even "works". The debug build might be being too friendly and hiding the mistake. Pop a breakpoint on `int *start = new int[V+1];` and see what values you're getting. If you swap the dynamic array for a `std::vector`, you'll get nicely zero-initialized values. I'm not familiar with the algorithm, so that might be the wrong plan, but it's better than jumbled garbage. – user4581301 Oct 29 '18 at 03:49
  • @Opt -- I'll do you a favor. [See this rendition of the code without memory leaks, has the proper headers, and returns the same results](http://coliru.stacked-crooked.com/a/0d424eb97ef58f34). Since you want to use `double`, the simplest thing to me is to make this a template class. If the basics of the algorithm are correct, all you need is to replace `int` with `T` and make a template based on `T`. – PaulMcKenzie Oct 29 '18 at 03:54
  • @PaulMcKenzie I am not quite familiar with C++ so I searched the internet to see how to make templates. I am trying to see if it works. Thanks. – rasul Oct 29 '18 at 04:18
  • 1
    @Opt `sendFlow` should be returning `double`. – PaulMcKenzie Oct 29 '18 at 04:22

1 Answers1

4

I don't know if the algorithm is correct, but assuming it is, the code at the link has several issues.

  1. Usage of the #include<bits/stdc++.h> header.
  2. Memory leaks.

First, usage of bits/stdc++.h should be avoided, and the proper #include files should be used.

Second, the usage of std::vector would take care of the memory leaks. The code uses std::vector in some places, but totally refuses to use it in other places. For example:

int *start = new int[V+1];

should be replaced with:

std::vector<int> start(V+1);

The former was causing a memory leak due to the lack of a delete [] start; in the code. Using std::vector, the memory leak disappears.

Once std::vector is introduced, there is no need for the V member variable in the Graph class that tracks the number of vertices. The reason why is that the vector members are sized to V vertices, and a vector already knows their own size by using the vector::size() member function. So the V member variable is redundant and can be removed.

The last change that can be made is to move struct Edge within the Graph class.


Given all of the changes mentioned, here is a cleaned up version that returns the same result as the original code when run with the test graph set up in the main() function:

#include <vector>
#include <list>
#include <algorithm>
#include <iostream>
#include <climits>

class Graph
{
    struct Edge
    {
        int v; 
        int flow; 
        int C; 
        int rev; 
    };

    std::vector<int> level;
    std::vector<std::vector< Edge >> adj;

public:
    Graph(int V) : level(V), adj(V) {}
    void addEdge(int u, int v, int C)
    {
        adj[u].push_back({ v, 0, C, static_cast<int>(adj[v].size()) });
        adj[v].push_back({ u, 0, 0, static_cast<int>(adj[u].size()) }); 
    }

    bool BFS(int s, int t);
    int sendFlow(int s, int flow, int t, std::vector<int>& ptr);
    int DinicMaxflow(int s, int t);
};

bool Graph::BFS(int s, int t)
{
    std::fill(level.begin(), level.end(), -1);
    level[s] = 0; 
    std::list< int > q;
    q.push_back(s);
    std::vector<Edge>::iterator i;
    while (!q.empty())
    {
        int u = q.front();
        q.pop_front();
        for (i = adj[u].begin(); i != adj[u].end(); i++)
        {
            Edge &e = *i;
            if (level[e.v] < 0 && e.flow < e.C)
            {
                level[e.v] = level[u] + 1;
                q.push_back(e.v);
            }
        }
    }
    return level[t] < 0 ? false : true;
}

int Graph::sendFlow(int u, int flow, int t, std::vector<int>& start)
{
    if (u == t)
        return flow;
    for (; start[u] < static_cast<int>(adj[u].size()); start[u]++)
    {
        // Pick next edge from adjacency list of u 
        Edge &e = adj[u][start[u]];

        if (level[e.v] == level[u] + 1 && e.flow < e.C)
        {
            int curr_flow = std::min(flow, e.C - e.flow);
            int temp_flow = sendFlow(e.v, curr_flow, t, start);

            if (temp_flow > 0)
            {
                e.flow += temp_flow;
                adj[e.v][e.rev].flow -= temp_flow;
                return temp_flow;
            }
        }
    }
    return 0;
}

int Graph::DinicMaxflow(int s, int t)
{
    if (s == t)
        return -1;
    int total = 0; 
    while (BFS(s, t) == true)
    {
        std::vector<int> start(level.size() + 1);
        while (int flow = sendFlow(s, INT_MAX, t, start))
            total += flow;
    }
    return total;
}

The test function is as follows:

int main() 
{ 
    Graph g(6); 
    g.addEdge(0, 1, 16 ); 
    g.addEdge(0, 2, 13 ); 
    g.addEdge(1, 2, 10 ); 
    g.addEdge(1, 3, 12 ); 
    g.addEdge(2, 1, 4 ); 
    g.addEdge(2, 4, 14); 
    g.addEdge(3, 2, 9 ); 
    g.addEdge(3, 5, 20 ); 
    g.addEdge(4, 3, 7 ); 
    g.addEdge(4, 5, 4); 
    std::cout << "Maximum flow " << g.DinicMaxflow(0, 5); 
}

Live Example


Now, if you want to see what happens if you use double instead of int as the flow amount, the easiest thing to do is to create a template class based on the code above. The goal is to take where int is used and instead of replacing int with double, replace int with a template parameter (for example, T). The resulting code would be as follows:

#include <vector>
#include <list>
#include <algorithm>
#include <iostream>
#include <climits>

template <typename T>
class Graph
{
    struct Edge
    {
        int v;
        T flow;
        T C;
        int rev;
    };

    std::vector<int> level;
    std::vector<std::vector<Edge>> adj;

public:
    Graph(int V) : level(V), adj(V) {}
    void addEdge(int u, int v, T C)
    {
        adj[u].push_back({ v, T(), C, static_cast<int>(adj[v].size())});
        adj[v].push_back({ u, T(), T(), static_cast<int>(adj[u].size())}); // reverse edge 
    }
    bool BFS(int s, int t);
    T sendFlow(int s, T flow, int t, std::vector<int>& ptr);
    T DinicMaxflow(int s, int t);
};

template <typename T>
bool Graph<T>::BFS(int s, int t)
{
    std::fill(level.begin(), level.end(), -1);
    level[s] = 0; 
    std::list< int > q;
    q.push_back(s);

    typename std::vector<Edge>::iterator i;
    while (!q.empty())
    {
        int u = q.front();
        q.pop_front();
        for (i = adj[u].begin(); i != adj[u].end(); i++)
        {
            Edge &e = *i;
            if (level[e.v] < 0 && e.flow < e.C)
            {
                level[e.v] = level[u] + 1;
                q.push_back(e.v);
            }
        }
    }
    return level[t] < 0 ? false : true;
}

template <typename T>
T Graph<T>::sendFlow(int u, T flow, int t, std::vector<int>& start)
{
    if (u == t)
        return flow;

    for (; start[u] < static_cast<int>(adj[u].size()); start[u]++)
    {
        Edge &e = adj[u][start[u]];
        if (level[e.v] == level[u] + 1 && e.flow < e.C)
        {
            T curr_flow = std::min(flow, e.C - e.flow);
            T temp_flow = sendFlow(e.v, curr_flow, t, start);
            if (temp_flow > 0)
            {
                e.flow += temp_flow;
                adj[e.v][e.rev].flow -= temp_flow;
                return temp_flow;
            }
        }
    }
    return 0;
}

template <typename T>
T Graph<T>::DinicMaxflow(int s, int t)
{
    if (s == t)
        return -1;
    T total = 0; 

    while (BFS(s, t) == true)
    {
        std::vector<int> start(level.size() + 1);
        while (T flow = sendFlow(s, INT_MAX, t, start))
            total += flow;
    }
    return total;
}

Live Example

The main test example would be to simply use Graph<double> or Graph<int> instead of simply Graph -- everything else in the main function stays the same.

Now that the function is a template, any numerical type that supports the same operations as int or double can be substituted easily by just creating a Graph<numerical_type>.

PaulMcKenzie
  • 34,698
  • 4
  • 24
  • 45
  • I no longer have the `segmentation fault` issue. Thanks to your comments and the answer, I also learned how to use templates. I can see how useful they are. Putting `struct Edge` into the class and using `T()` for initialization was a nice move in applying the template. – rasul Oct 29 '18 at 13:02