5

I'm having problems in filtering the subgraphs with the same component in the original graph. I want to output them in a vector of subgraphs. Following the example in `connected_components I've tried to adapt it to me needs:

// Create a typedef for the Graph type
typedef adjacency_list<
vecS,
vecS,
undirectedS,
property<vertex_index_t,int >,
property<edge_index_t,int> > Graph;

//typedef subgraph < Graph > SubGraph;
typedef typename graph_traits<Graph>::vertex_descriptor Vertex;
typedef typename graph_traits<Graph>::edge_descriptor Edge;
typedef graph_traits<Graph> GraphTraits;

// Iterators
typedef graph_traits<Graph>::vertex_iterator vertex_iter;
typedef graph_traits<Graph>::edge_iterator edge_iter;
typedef property_map<Graph, vertex_index_t>::type VertexIndexMap;
typedef property_map<Graph, edge_index_t>::type EdgeIndexMap;

std::vector<Graph> connected_components_subgraphs(const Graph &g)
{
    std::vector<int> component(num_vertices(g));
    int num = boost::connected_components(g, &component[0]);
    for (int i=0; i<component.size(); i++)
        cout << component[i] << endl;
    cout << "NUM=" << num << endl;

    // Something to output the induced subgraphs where every subgraph is in the same component
}

I'm totally stuck in the filtering of the graph, because I don't understand how an external property that is stored for the vertices in the vector component, can be exploited or passed to some functor needed to the filter the graph.

In particular, it seems that this question is very similar to my needs, but with no pieces of code I find it very difficult to figure out the problem.

splitting a boost graph into connected components

How can I output the induced subgraphs from the nodes in the same connected component?

Community
  • 1
  • 1
linello
  • 8,451
  • 18
  • 63
  • 109

2 Answers2

7

You could use filtered_graph views of the main graph:

typedef filtered_graph<Graph, EdgeInComponent, VertexInComponent> ComponentGraph;

std::vector<ComponentGraph> connected_components_subgraphs(Graph const&g)
{
    vertex_component_map mapping = boost::make_shared<std::vector<unsigned long>>(num_vertices(g));
    size_t num = boost::connected_components(g, mapping->data());

    std::vector<ComponentGraph> component_graphs;

    for (size_t i = 0; i < num; i++)
        component_graphs.push_back(ComponentGraph(g, EdgeInComponent(mapping, i, g), VertexInComponent(mapping, i)));

    return component_graphs;
}

Of course, this just begs the question how to implement the filter predicates. I opted to share the mapping vector:

typedef boost::shared_ptr<std::vector<unsigned long>> vertex_component_map;

I didn't want to assume that you could share a global or just copy it around. For example, the VertexInComponent predicate looks like:

struct VertexInComponent
{ 
    vertex_component_map mapping_;
    unsigned long which_;

    VertexInComponent(vertex_component_map m, unsigned long which)
        : mapping_(m), which_(which) {}

    template <typename Vertex> bool operator()(Vertex const&v) const {
        return mapping_->at(v)==which_;
    } 
};

Similarly, the EdgeInComponent can be implemented. In fact, you can probably shortcut it and use something like

struct AnyElement { 
    template <typename EdgeOrVertex> bool operator()(EdgeOrVertex const&) const { return true; }
};

for one of the two. Here's a sample main:

Graph g;

add_edge(0, 1, g);
add_edge(1, 4, g);
add_edge(4, 0, g);
add_edge(2, 5, g);

for (auto const& component : connected_components_subgraphs(g))
{
    std::cout << "component [ ";
    for (auto e :  make_iterator_range(edges(component)))
        std::cout << source(e, component) << " -> " << target(e, component) << "; ";
    std::cout << "]\n";
}

And it prints:

component [ 0 -> 1; 1 -> 4; 4 -> 0; ]
component [ 2 -> 5; ]
component [ ]

Full Code

See it Live On Coliru

#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/connected_components.hpp>
#include <boost/graph/filtered_graph.hpp>
#include <boost/make_shared.hpp>
#include <boost/range/iterator_range.hpp>
#include <iostream>

using namespace boost;

// Create a typedef for the Graph type
typedef adjacency_list<vecS, vecS, undirectedS, property<vertex_index_t, int>, property<edge_index_t, int>> Graph;

// typedef subgraph < Graph > SubGraph;
typedef typename graph_traits<Graph>::vertex_descriptor Vertex;
typedef typename graph_traits<Graph>::edge_descriptor Edge;
typedef graph_traits<Graph> GraphTraits;

// Iterators
typedef graph_traits<Graph>::vertex_iterator vertex_iter;
typedef graph_traits<Graph>::edge_iterator edge_iter;
typedef property_map<Graph, vertex_index_t>::type VertexIndexMap;
typedef property_map<Graph, edge_index_t>::type EdgeIndexMap;

typedef boost::shared_ptr<std::vector<unsigned long>> vertex_component_map;

struct EdgeInComponent
{ 
    vertex_component_map mapping_;
    unsigned long which_;
    Graph const& master_;

    EdgeInComponent(vertex_component_map m, unsigned long which, Graph const& master) 
        : mapping_(m), which_(which), master_(master) {}

    template <typename Edge> bool operator()(Edge const&e) const {
        return mapping_->at(source(e,master_))==which_
            || mapping_->at(target(e,master_))==which_;
    } 
};

struct VertexInComponent
{ 
    vertex_component_map mapping_;
    unsigned long which_;

    VertexInComponent(vertex_component_map m, unsigned long which)
        : mapping_(m), which_(which) {}

    template <typename Vertex> bool operator()(Vertex const&v) const {
        return mapping_->at(v)==which_;
    } 
};

struct AnyVertex { 
    template <typename Vertex> bool operator()(Vertex const&) const { return true; }
};

typedef filtered_graph<Graph, EdgeInComponent, VertexInComponent> ComponentGraph;

std::vector<ComponentGraph> connected_components_subgraphs(Graph const&g)
{
    vertex_component_map mapping = boost::make_shared<std::vector<unsigned long>>(num_vertices(g));
    size_t num = boost::connected_components(g, mapping->data());

    std::vector<ComponentGraph> component_graphs;

    for (size_t i = 0; i < num; i++)
        component_graphs.push_back(ComponentGraph(g, EdgeInComponent(mapping, i, g), VertexInComponent(mapping, i)));

    return component_graphs;
}

int main()
{
    Graph g;

    add_edge(0, 1, g);
    add_edge(1, 4, g);
    add_edge(4, 0, g);
    add_edge(2, 5, g);

    for (auto const& component : connected_components_subgraphs(g))
    {
        std::cout << "component [ ";
        for (auto e :  make_iterator_range(edges(component)))
            std::cout << source(e, component) << " -> " << target(e, component) << "; ";
        std::cout << "]\n";
    }
}

Bonus: c++11

If you can use C++11 lambdas can make the code considerably shorter because you can define the filter predicates in-place:

See it Live On Coliru

typedef filtered_graph<Graph, function<bool(Graph::edge_descriptor)>, function<bool(Graph::vertex_descriptor)> > ComponentGraph;

std::vector<ComponentGraph> connected_components_subgraphs(Graph const&g)
{
    vertex_component_map mapping = boost::make_shared<std::vector<unsigned long>>(num_vertices(g));
    size_t num = boost::connected_components(g, mapping->data());

    std::vector<ComponentGraph> component_graphs;

    for (size_t i = 0; i < num; i++)
        component_graphs.emplace_back(g,
            [mapping,i,&g](Graph::edge_descriptor e) {
                return mapping->at(source(e,g))==i
                    || mapping->at(target(e,g))==i;
            }, 
            [mapping,i](Graph::vertex_descriptor v) {
                return mapping->at(v)==i;
            });

    return component_graphs;
}
sehe
  • 374,641
  • 47
  • 450
  • 633
  • Wonderful! In particular I had problems in understanding how to pass the components vector to the filter. In this way you explained me this concept very clearly. I'm also quite a newbie in Boost Graph and fully grasping it is pretty difficult. Thanks! – linello Nov 06 '14 at 12:15
  • Another comment I have, is it possible to count the number of vertices in each of those `filtered_graph` in output? Because `num_vertices` returns the number of vertices of the original unfiltered graph. Should I use a boost::subgraph on the nodes of every filtered subgraph? – linello Nov 06 '14 at 12:19
  • @linello If you use `vecS` with `adjacency_list` then by definition all the indices exist "virtually". You should probably choose a different container policy for the `VertexList` template argument. – sehe Nov 06 '14 at 12:25
  • As a bonus I added a [c++11 version](http://coliru.stacked-crooked.com/a/9a3aad2170e37c97) to the sample code – sehe Nov 06 '14 at 12:27
  • 1
    @linello Though this should probably have been a separate question, here's a demo that uses `setS` for the `VertexList` and shows how `distance(vertices(component))` returns the number of vertices you desire: **[Live On Coliru](http://coliru.stacked-crooked.com/a/9fddc5d3072882ac)** – sehe Nov 06 '14 at 12:43
  • Thanks your code is excellent. About the num_vertices call on the filtered_graph, is very strange that is impossible to get the exact number of vertices of the filtered graph but only the num_vertices of the underlying graph. Is there a solution or a workaround to this? In particular it would be nice rather than outputting filtered_graphs, to output regular graphs. I think that in someway we have to typedef subgraph > Graph as the new class for the graph. Am I right? – linello Nov 06 '14 at 14:03
  • Disregard my previous comment. Reading the fine print for `connected_components` I find that I /needed/ a `color_map` when using `setS` for `VertexList` (I am surprised the program compiled). Here's a **[rough version that returns `vector` by using `copy_graph`](http://coliru.stacked-crooked.com/a/6a5a11035415d388)**. You will probably want to do something more sensible with the `vertex_index_map`, but I'll leave that to you. – sehe Nov 07 '14 at 10:04
  • @sehe why not use `subgraph`? I've added another answer. – Taylor May 12 '16 at 19:29
4

Instead of filtered_graph, you can use subgraph as follows:

vector<int> comp(num_vertices(g));
size_t num = boost::connected_components(g, comp.data());

vector<Graph*> comps(num);
for(size_t i=0;i<num;++i) {
    comps[i] = & g.create_subgraph();
}

for(size_t i=0;i<num_vertices(g);++i) {
    add_vertex(i, *comps[comp[i]]);
}

where Graph is defined as:

using Graph = subgraph< adjacency_list<vecS, vecS, undirectedS, property<vertex_index_t, int>, property<edge_index_t, int>> >;

Note that you'll need to use local_to_global to map vertex descriptors from the subgraph to the root graph.

Running example: http://coliru.stacked-crooked.com/a/cebece41c0daed87

It would be interesting to know the merits of filtered_graph vs subgraph in this case.

Taylor
  • 5,871
  • 2
  • 30
  • 64