Search code examples
c++graphboost

Boost graph non contiguous vertex indices


#include <boost/graph/adjacency_list.hpp>
typedef boost::adjacency_list<boost::vecS, boost::vecS, boost::directedS,
                                        boost::no_property,
                                        boost::property<boost::edge_weight_t, double>>
              DiGraph;

typedef boost::graph_traits<DiGraph>::vertex_descriptor vertex_descriptor;

int main () {
std::vector<std::size_t> vertices = { 1, 5, 10};
std::vector<std::pair<std::size_t, std::size_t>> edges = {std::make_pair(1, 5),
                                                                   std::make_pair(5, 10)};
std::vector<double> weights = {2., 2.};

DiGraph di_graph (edges.begin(), edges.end(), weights.begin(), vertices.size());


DiGraph::vertex_descriptor v_start = boost::vertex(1, di_graph);


std::vector<vertex_descriptor> parents(boost::num_vertices(di_graph));

boost::dijkstra_shortest_paths(di_graph, v_start,
      boost::predecessor_map(boost::make_iterator_property_map(parents.begin(), boost::get(boost::vertex_index, di_graph))));

}

This allocates a vector parents of size 11, since boost uses contiguous vertex indices. I want the non-contiguous vertices (1, 5, 10..) but don't want the unnecessary memory space for the vector parents. How can I make a mapping from my vertex indices to the vertex indices 1, 2, 3 and pass it to boost::dijkstra_shortest_paths?

On top of that it would be even more convenient to receive the result of dijkstra in a struct parents and access the predecessor of an element with my index, e.g.

parents[10]

but without a vector of length 11 or just have an easy conversion function f I could use

parents[f(10)]

I did take a look at the documentation of boost graph and thought the IndexMap could make this possible, but I don't understand the concept and can't make it work.


Solution

  • With the boost::vecS vertex container selection, the vertex index is implicit, and the call

    DiGraph di_graph(
        edges.begin(), edges.end(), weights.begin(), vertices.size());
    

    is a lie: you tell it that there are 3 vertices, but then you index out of bounds (5, 10 are outside [0,1,2]).

    Note also that

    V v_start = boost::vertex(1, di_graph);
    

    selects the second vertex, not vertex 1.

    Internal Names

    I'd probably suggest a more recent addition: internal vertex names. If we add a vertex property bundle, like simply int:

    using DiGraph = boost::adjacency_list<
        boost::vecS,
        boost::vecS,
        boost::directedS,
        int,
        boost::property<boost::edge_weight_t, double>>;
    

    And then also teach BGL that we can use it as the vertex internal name:

    template<> struct boost::graph::internal_vertex_name<int> {
        struct type : std::identity {
            using result_type = int;
        };
    };
    

    Now creating the equivalent graph is simply:

    DiGraph g;
    add_edge(1, 5, 2., g);
    add_edge(5, 10, 2., g);
    

    That's all. You can see that it created vertices with implicit indices as the descriptors:

    for (auto e : make_iterator_range(edges(g))) {
        std::cout << "edge: " << e << "\n";
    }
    

    Prints:

    edge: (0,2)
    edge: (1,0)
    

    To get the names, use property maps like so:

    for (auto v : make_iterator_range(vertices(g))) {
        std::cout << "vertex at index " << v << " named " << g[v] << "\n";
    }
    

    Printing

    vertex at index 0 named 5
    vertex at index 1 named 1
    vertex at index 2 named 10
    

    Using internal vertex names, you can query vertices by property bundles:

    boost::optional<V> v_start = g.vertex_by_property(1);
    

    Now, all I can suggest is using safe iterator maps:

    dijkstra_shortest_paths(
        g,
        v_start.value(),
        boost::predecessor_map(boost::make_safe_iterator_property_map(
            parents.begin(), parents.size(), get(boost::vertex_index, g))));
    
    for (size_t i = 0; i < parents.size(); ++i) {
        std::cout << "Parent for '" << g[i] << "' is '" << g[parents[i]] << "'\n";
    }
    

    Live Demo

    Live On Coliru

    #include <boost/graph/adjacency_list.hpp>
    #include <boost/graph/dijkstra_shortest_paths.hpp>
    #include <iostream>
    
    template<> struct boost::graph::internal_vertex_name<int> {
        struct type : std::identity {
            using result_type = int;
        };
    };
    
    using DiGraph = boost::adjacency_list<
        boost::vecS,
        boost::vecS,
        boost::directedS,
        int,
        boost::property<boost::edge_weight_t, double>>;
    
    using V = DiGraph::vertex_descriptor;
    using boost::make_iterator_range;
    
    int main()
    {
        DiGraph g;
        add_edge(1, 5, 2., g);
        add_edge(5, 10, 2., g);
    
        for(auto e : make_iterator_range(edges(g)))
            std::cout << "edge: " << e << "\n";
    
        for(auto v : make_iterator_range(vertices(g)))
            std::cout << "vertex at index " << v << " named " << g[v] << "\n";
    
        boost::optional<V> v_start = g.vertex_by_property(1);
    
        std::vector<V> parents(num_vertices(g));
    
        dijkstra_shortest_paths(
            g,
            v_start.value(),
            boost::predecessor_map(boost::make_safe_iterator_property_map(
                parents.begin(), parents.size(), get(boost::vertex_index, g))));
    
        for (size_t i = 0; i < parents.size(); ++i) {
            std::cout << "Parent for '" << g[i] << "' is '" << g[parents[i]] << "'\n";
        }
    }
    

    Prints

    edge: (0,2)
    edge: (1,0)
    vertex at index 0 named 5
    vertex at index 1 named 1
    vertex at index 2 named 10
    Parent for '5' is '1'
    Parent for '1' is '1'
    Parent for '10' is '5'