How to mark the back-edge for cycle detection in Directed Graph using Boost Graph Library

Viewed 46

I can write my own DFS but the codebase I use has been using Boost Graph Library and so it will be neat if I can make a minor modification to it.

Here is the code snippet used from https://www.boost.org/doc/libs/1_56_0/libs/graph/doc/file_dependency_example.html#sec:cycles for cycle detection

 struct cycle_detector : public dfs_visitor<>
  {
    cycle_detector( bool& has_cycle) 
      : _has_cycle(has_cycle) { }

    template <class Edge, class Graph>
    void back_edge(Edge, Graph&) {
      _has_cycle = true;
    }
  protected:
    bool& _has_cycle;
  };

We can now invoke the BGL depth_first_search() algorithm and pass in the cycle detector visitor.


  bool has_cycle = false;
  cycle_detector vis(has_cycle);
  boost::depth_first_search(g, visitor(vis));
  std::cout << "The graph has a cycle? " << has_cycle << std::endl;

Here is my modification to mark the nodes for the back_edge:

struct cycle_detector : public dfs_visitor<>
  {
    cycle_detector( bool& has_cycle) 
      : _has_cycle(has_cycle) { }

    template <class Edge, class Graph>
    void back_edge(Edge e, Graph& g) {
       cycleFrom = index[source(e,g)];
       cycleTo = index[target(e,g)];
      _has_cycle = true;
    }
    int& cycleFrom, cycleTo
  protected:
    bool& _has_cycle;
  };

We can now invoke the BGL depth_first_search() algorithm and pass in the cycle detector visitor.



  bool has_cycle = false;
  cycle_detector vis(has_cycle);
  boost::depth_first_search(g, visitor(vis));

  if(has_cycle)  std::cout << "The graph has a cycle from " << vis.cycleFrom << " to " << vis.cycleTo << std::endl;

But it marks this line (and the one with target): cycleFrom = index[source(e,g)], highlights index and says error: overloaded function with no contextual information. I have went through the documentation and various code snippets and couldn't figure out what I should modify and how I should access the index of the source and target vertices of this back_edge where the cycle is detected

1 Answers

You have a few syntactic errors, e.g. missing ; after cycleTo:

  int& cycleFrom, cycleTo
protected:

Now, note that int& a, b; does not declare two references. It declares int &a; int b;. That's not what you want.

Next up, cycleFrom and cycleTo weren't declared. Did you mean cyclefrom and cycleto?

Next up, index is not declared.

Finally, reference members need to be initialized in the constructor.

Those are the syntactic problems. Even if you address them, there's the problem that you didn't anticipate when there is more than one back-edge. You will just repeatedly overwrite the result variables. That's not a big issue with the bool _has_cycle (after all, it would only get truer...). But with the from/to information you stand to lose information, or get unexpected answers.

Simplist Fix

Just removing the listed errors: Live On Compiler Explorer

#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/depth_first_search.hpp>
#include <iostream>

using G = boost::adjacency_list<>;
using V = G::vertex_descriptor;
using E = G::edge_descriptor;

struct cycle_detector : boost::dfs_visitor<> {
    cycle_detector(bool& has_cycle, V& cyclefrom, V& cycleto)
        : _has_cycle(has_cycle)
        , cyclefrom(cyclefrom)
        , cycleto(cycleto) {}

    void back_edge(E e, G const& g) {
        cyclefrom  = source(e, g);
        cycleto    = target(e, g);
        _has_cycle = true;
    }

    bool& _has_cycle;
    V&    cyclefrom;
    V&    cycleto;
};

int main() {
    boost::adjacency_list<> g;
    add_edge(1, 2, g);
    add_edge(2, 3, g);
    add_edge(4, 5, g);
    add_edge(5, 2, g);

    bool has_cycle = false;
    V    from, to;

    cycle_detector vis(has_cycle, from, to);

    depth_first_search(g, visitor(vis));

    std::cout << "graph has cycle? " << std::boolalpha << has_cycle << "\n";

    // make cycle
    add_edge(3, 4, g);
    depth_first_search(g, visitor(vis));

    std::cout << "graph has cycle? " << std::boolalpha << has_cycle << "\n";
    std::cout << "cycle from: " << from << " to " << to << "\n";
}

Prints

graph has cycle? false
graph has cycle? true
cycle from: 5 to 2

Vertex Indices Instead

If you really wanted vertex indices instead of descriptors: Live

#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/depth_first_search.hpp>
#include <iostream>

using G = boost::adjacency_list<>;
using V = G::vertex_descriptor;
using E = G::edge_descriptor;

struct cycle_detector : boost::dfs_visitor<> {
    cycle_detector(bool& has_cycle, size_t& cyclefrom, size_t& cycleto)
        : _has_cycle(has_cycle)
        , _cyclefrom_idx(cyclefrom)
        , _cycleto_idx(cycleto) {}

    void back_edge(E e, G const& g) {
        auto index = get(boost::vertex_index, g);
        _cyclefrom_idx  = index[source(e, g)];
        _cycleto_idx    = index[target(e, g)];
        _has_cycle = true;
    }

    bool&   _has_cycle;
    size_t& _cyclefrom_idx;
    size_t& _cycleto_idx;
};

int main() {
    boost::adjacency_list<> g;
    add_edge(1, 2, g);
    add_edge(2, 3, g);
    add_edge(4, 5, g);
    add_edge(5, 2, g);

    bool   has_cycle = false;
    size_t from_idx, to_idx;

    cycle_detector vis(has_cycle, from_idx, to_idx);

    depth_first_search(g, visitor(vis));

    std::cout << "graph has cycle? " << std::boolalpha << has_cycle << "\n";

    // make cycle
    add_edge(3, 4, g);
    depth_first_search(g, visitor(vis));

    std::cout << "graph has cycle? " << std::boolalpha << has_cycle << "\n";
    std::cout << "cycle from indices: " << from_idx << " to " << to_idx << "\n";
}

Of course this makes more sense with a different graph model: https://godbolt.org/z/Ma9nf964v

Making It (More) Useful

What if, instead, you wanted to get the information in a more useful way? Let's make the visitor simpler and more generic:

template <typename Callback>
struct back_edge_collector : boost::dfs_visitor<> {
    Callback _cb;
    back_edge_collector(Callback cb) : _cb(std::move(cb)) {}
    void back_edge(E e, G const&) const { _cb(e); }
};

Now you can use it directly: https://godbolt.org/z/acsM69vc3

template <typename Callback>
struct back_edge_collector : boost::dfs_visitor<> {
    Callback _cb;
    back_edge_collector(Callback cb) : _cb(std::move(cb)) {}
    void back_edge(E e, G const&) const { _cb(e); }
};

int main() {
    boost::adjacency_list<> g;
    add_edge(1, 2, g);
    add_edge(2, 3, g);
    add_edge(4, 5, g);
    add_edge(5, 2, g);

    bool has_cycle = false;

    back_edge_collector vis([&](E e) {
        has_cycle = true;
        std::cout << "Back-edge: " << e << "\n";
    });

    depth_first_search(g, visitor(vis));

    std::cout << "graph has cycle? " << std::boolalpha << has_cycle << "\n";

    // make cycle
    add_edge(3, 4, g);
    depth_first_search(g, visitor(vis));

    std::cout << "graph has cycle? " << std::boolalpha << has_cycle << "\n";
}

Prints

graph has cycle? false
Back-edge: (5,2)
graph has cycle? true

And since it uses inversion of control it doesn't change when you want to do something different: https://godbolt.org/z/3j7n8xz1j

std::set<E> back_edges;
depth_first_search(g, visitor(back_edge_collector([&back_edges](E e) {
                       back_edges.insert(e);
                   })));

std::cout << "graph has " << back_edges.size() << " unique back_edges\n";

Output:

graph has 1 unique back_edges
Related