Boost::graph Dijkstra 和自定义对象和属性

Boost::graph Dijkstra and custom objects and properties

我想使用 boost 的 dijkstra 算法(因为我在程序的其他部分使用了 boost)。我遇到的问题是将自定义对象(我相信它们被称为 property)添加到 adjacency_list.

本质上我有一个自定义边 class 维护关于边和通过它连接的顶点的各种信息。我想用 adjaceny_list

所需的边缘属性存储我的自定义数据对象

我已经成功实现了 boost provides. I've tried to use custom properties to no avail (boost example, boost properties) 的玩具示例。我可以将我的 VEdge 数据结构封装在结构或其他东西中,我只需要能够检索它。但是我还没弄清楚如何将我的自定义数据结构包含到 boost adjaceny_list 结构中。

在我的例子中,我有以下程序:

Main.cpp:

#include <iostream>
#include <fstream>
#include "dijkstra.h"
#include <vector>

int
main(int, char *[])
{
  // Generate the vector of edges from elsewhere in the program
  std::vector<VEdge*> edges; //someclass.get_edges();

  td* test = new td(edges);
  test->run_d();

  test->print_path();

  return EXIT_SUCCESS;
}

Dijkstra.cpp:

#include <iostream>
#include <fstream>
#include "dijkstra.h"

using namespace boost;

td::td() {
    kNumArcs = sizeof(kEdgeArray) / sizeof(Edge);
    kNumNodes = 5;
}

td::td(std::vector<VEdge*> edges) {
    // add edges to the edge property here
    for(VEdge* e : edges) {
        // for each edge, add to the kEdgeArray variable in some way
        // The boost example forces the input to be an array of edge_property type.  
        // So here is where I will convert my raw VEdge data structure to 
        // the custom edge_property that I am struggling to understand how to create.
    }
    kNumArcs = sizeof(kEdgeArray) / sizeof(Edge);
    kNumNodes = 5;
}

void td::run_d() {
    kGraph = graph_t(kEdgeArray, kEdgeArray + kNumArcs, kWeights, kNumNodes);

    kWeightMap = get(edge_weight, kGraph);
    kP = std::vector<vertex_descriptor >(num_vertices(kGraph));
    kD = std::vector<int>(num_vertices(kGraph));
    kS = vertex(A, kGraph);

    dijkstra_shortest_paths(kGraph, kS,
            predecessor_map(boost::make_iterator_property_map(kP.begin(), get(boost::vertex_index, kGraph))).
                    distance_map(boost::make_iterator_property_map(kD.begin(), get(boost::vertex_index, kGraph))));
}

void td::print_path() {
    std::cout << "distances and parents:" << std::endl;
    graph_traits < graph_t >::vertex_iterator vi, vend;
    for (boost::tie(vi, vend) = vertices(kGraph); vi != vend; ++vi) {
        std::cout << "distance(" << kName[*vi] << ") = " << kD[*vi] << ", ";
        std::cout << "parent(" << kName[*vi] << ") = " << kName[kP[*vi]] << std::
        endl;
    }
}

void td::generate_dot_file() {
    std::cout << std::endl;

    std::ofstream dot_file("figs/dijkstra-eg.dot");

    dot_file << "digraph D {\n"
            << "  rankdir=LR\n"
            << "  size=\"4,3\"\n"
            << "  ratio=\"fill\"\n"
            << "  edge[style=\"bold\"]\n" << "  node[shape=\"circle\"]\n";

    graph_traits < graph_t >::edge_iterator ei, ei_end;
    for (boost::tie(ei, ei_end) = edges(kGraph); ei != ei_end; ++ei) {
        graph_traits < graph_t >::edge_descriptor e = *ei;
        graph_traits < graph_t >::vertex_descriptor
                u = source(e, kGraph), v = target(e, kGraph);
        dot_file << kName[u] << " -> " << kName[v]
                << "[label=\"" << get(kWeightMap, e) << "\"";
        if (kP[v] == u)
            dot_file << ", color=\"black\"";
        else
            dot_file << ", color=\"grey\"";
        dot_file << "]";
    }
    dot_file << "}";
}

Dijkstra.h:

#ifndef _TEMPD_H_
#define _TEMPD_H_

#pragma once

#include <boost/config.hpp>
#include <boost/graph/graph_traits.hpp>
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/dijkstra_shortest_paths.hpp>
#include <boost/property_map/property_map.hpp>

using namespace boost;

typedef adjacency_list < listS, vecS, directedS,
        no_property, property < edge_weight_t, int > > graph_t;
typedef graph_traits < graph_t >::vertex_descriptor vertex_descriptor;
typedef std::pair<int, int> Edge;

struct VEdge{
    // custom variables here
    VNode start;
    VNode end;
    int weight;
    int id;
    // other irrelevant data pertinent to my program that must be preserved
};

struct VNode {
    // custom variables here
    int x;
    int y;
    int id;
    // other irrelevant data pertinent to my program that must be preserved
}

enum nodes { A, B, C, D, E };

class td {
public:
    td();
    td(std::vector<VEdge*>);
    ~td();

    void run_d();

    void print_path();
    void generate_dot_file();
private:
    Edge kEdgeArray[9] = { Edge(A, C), Edge(B, B), Edge(B, D), Edge(B, E),
            Edge(C, B), Edge(C, D), Edge(D, E), Edge(E, A), Edge(E, B)
    };
    char kName[5] = {'A','B','C','D','E'};
    int kWeights[9] = { 1, 2, 1, 2, 7, 3, 1, 1, 1 };
    int kNumArcs;
    int kNumNodes;
    vertex_descriptor kS;
    graph_t kGraph;
    std::vector<int> kD;
    std::vector<vertex_descriptor> kP;
    property_map<graph_t, edge_weight_t>::type kWeightMap;
};
#endif

我知道我的例子有点做作,但它传达了我想要完成的事情。我知道我的 edge_descriptor 需要一个自定义数据结构,它被发送到 graph_t typedef.

所以我想改变我的 Dijkstra.h 文件,使其看起来像这样:

struct vertex_label_t {vertex_property_tag kind;};
struct edge_label_t {edge_property_tag kind;};

typedef property <vertex_custom_t, VNode*>,
    property <vertex_label_t, string>,
        property <vertex_root_t, ing> > > vertex_p;

typedef property <edge_custom_t, VEdge*>,
    property <edge_label_t, string > > edge_p;

typedef adjacency_list < listS, vecS, directedS,
        vertex_p, edge_p > graph_t;
typedef graph_traits < graph_t >::vertex_descriptor vertex_descriptor;

好的。自 以来,您已经取得了长足的进步;该示例是独立的,可以编译¹

我想我可以把一些点联系起来,希望这会有所帮助。

1。使用 VEdge

对于最简单的选项,我将使用 Bundled Properties,并按如下方式定义 VEdge

struct VEdge {
    int id;
    int source, target;
    double weight;
    // custom variables here
};

现在,我们将图定义为

using graph_t = boost::adjacency_list<boost::listS, boost::vecS, 
                    boost::directedS, boost::no_property, VEdge>;
using weight_map_t = boost::property_map<graph_t, double VEdge::*>::type;

如您所见,权重图的类型稍微复杂一些,如 Properties maps from bundled properties 中所述。您可以获得实际地图:

weight_map_t kWeightMap = boost::get(&VEdge::weight, kGraph);

现在,让我们在 VEdge (A=0...E=4):

的向量中重新创建问题中的测试数据
std::vector<VEdge> edges {
    { 2100, 0, 2, 1 },
    { 2101, 1, 1, 2 },
    { 2102, 1, 3, 1 },
    { 2103, 1, 4, 2 },
    { 2104, 2, 1, 7 },
    { 2105, 2, 3, 3 },
    { 2106, 3, 4, 1 },
    { 2107, 4, 0, 1 },
    { 2108, 4, 1, 1 },
};

test_dijkstra test(edges);

构造函数要从边中查找顶点的数量有点复杂。我使用 Boost Range 算法找到最大顶点节点 ID 并传递它:

test_dijkstra::test_dijkstra(std::vector<VEdge> edges) {
    using namespace boost::adaptors;

    size_t max_node;

    boost::partial_sort_copy(
            edges | transformed([](VEdge const &e) -> size_t { return std::max(e.source, e.target); }),
            boost::make_iterator_range(&max_node, &max_node + 1),
            std::greater<size_t>());

    auto e = edges | transformed([](VEdge const &ve) { return std::make_pair(ve.source, ve.target); });
    kGraph = graph_t(e.begin(), e.end(), edges.begin(), max_node + 1);
}

请注意如何传递 edges.begin():它不是 "forced to be a an array of edge_property type"。一个迭代器就可以了。

现在 dijkstra 需要获取 weight_map 参数,因为它不再是默认的内部 属性:

void test_dijkstra::run_dijkstra() {

    weight_map_t kWeightMap = boost::get(&VEdge::weight, kGraph);

    vertex_descriptor kS    = vertex(0, kGraph);
    kP                      = std::vector<vertex_descriptor>(num_vertices(kGraph));
    kD                      = std::vector<int>(num_vertices(kGraph));

    dijkstra_shortest_paths(
        kGraph, kS,
            predecessor_map(boost::make_iterator_property_map(kP.begin(), get(boost::vertex_index, kGraph)))
        .distance_map(boost::make_iterator_property_map(kD.begin(), get(boost::vertex_index, kGraph)))
        .weight_map(kWeightMap));
}

对于这个示例,我将 A 转换为 0 作为起始顶点。结果路径与原始路径完全相同²

完整节目

Live On Coliru

#include <boost/config.hpp>
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/dijkstra_shortest_paths.hpp>
#include <boost/graph/graph_traits.hpp>
#include <boost/property_map/property_map.hpp>
#include <boost/range/algorithm.hpp>
#include <boost/range/adaptors.hpp>
#include <fstream>
#include <iostream>

struct VEdge {
    int id;
    int source, target;
    double weight;
    // custom variables here
};

class test_dijkstra {
    using graph_t           = boost::adjacency_list<boost::listS, boost::vecS, boost::directedS, boost::no_property, VEdge>;
    using vertex_descriptor = boost::graph_traits<graph_t>::vertex_descriptor;
    using edge_descriptor   = boost::graph_traits<graph_t>::edge_descriptor;
    using weight_map_t      = boost::property_map<graph_t, double VEdge::*>::type;

  public:
    test_dijkstra(std::vector<VEdge>);
    ~test_dijkstra() {}

    void run_dijkstra();

    void print_path();
    void generate_dot_file();

  private:
    graph_t kGraph;

    std::vector<int> kD;
    std::vector<vertex_descriptor> kP;
};

test_dijkstra::test_dijkstra(std::vector<VEdge> edges) {
    using namespace boost::adaptors;

    size_t max_node;

    boost::partial_sort_copy(
            edges | transformed([](VEdge const &e) -> size_t { return std::max(e.source, e.target); }),
            boost::make_iterator_range(&max_node, &max_node + 1),
            std::greater<size_t>());

    auto e = edges | transformed([](VEdge const &ve) { return std::make_pair(ve.source, ve.target); });
    kGraph = graph_t(e.begin(), e.end(), edges.begin(), max_node + 1);
}

void test_dijkstra::run_dijkstra() {

    weight_map_t kWeightMap = boost::get(&VEdge::weight, kGraph);

    vertex_descriptor kS    = vertex(0, kGraph);
    kP                      = std::vector<vertex_descriptor>(num_vertices(kGraph));
    kD                      = std::vector<int>(num_vertices(kGraph));

    dijkstra_shortest_paths(
        kGraph, kS,
            predecessor_map(boost::make_iterator_property_map(kP.begin(), get(boost::vertex_index, kGraph)))
           .distance_map(boost::make_iterator_property_map(kD.begin(), get(boost::vertex_index, kGraph)))
           .weight_map(kWeightMap));
}

void test_dijkstra::print_path() {
    std::cout << "distances and parents:" << std::endl;
    boost::graph_traits<graph_t>::vertex_iterator vi, vend;

    for (boost::tie(vi, vend) = vertices(kGraph); vi != vend; ++vi) {
        std::cout << "distance(" << *vi << ") = " << kD[*vi] << ", ";
        std::cout << "parent(" << *vi << ") = " << kP[*vi] << "\n";
    }
}

void test_dijkstra::generate_dot_file() {
    weight_map_t kWeightMap = boost::get(&VEdge::weight, kGraph);

    std::ofstream dot_file("figs/dijkstra-eg.dot");

    dot_file << "digraph D {\n"
             << "  rankdir=LR\n"
             << "  size=\"4,3\"\n"
             << "  ratio=\"fill\"\n"
             << "  edge[style=\"bold\"]\n"
             << "  node[shape=\"circle\"]\n";

    boost::graph_traits<graph_t>::edge_iterator ei, ei_end;
    for (boost::tie(ei, ei_end) = edges(kGraph); ei != ei_end; ++ei) {
        boost::graph_traits<graph_t>::edge_descriptor e = *ei;
        boost::graph_traits<graph_t>::vertex_descriptor u = source(e, kGraph), v = target(e, kGraph);
        dot_file << u << " -> " << v << "[label=\"" << get(kWeightMap, e) << "\"";

        if (kP[v] == u)
            dot_file << ", color=\"black\"";
        else
            dot_file << ", color=\"grey\"";
        dot_file << "]";
    }
    dot_file << "}";
}

int main() {
    std::vector<VEdge> edges {
        { 2100, 0, 2, 1 },
        { 2101, 1, 1, 2 },
        { 2102, 1, 3, 1 },
        { 2103, 1, 4, 2 },
        { 2104, 2, 1, 7 },
        { 2105, 2, 3, 3 },
        { 2106, 3, 4, 1 },
        { 2107, 4, 0, 1 },
        { 2108, 4, 1, 1 },
    };

    test_dijkstra test(edges);
    test.run_dijkstra();

    test.print_path();
    test.generate_dot_file();
}

2。使用 VEdge*

如果您坚持在属性中使用指针,一些事情会变得更加复杂:

  • 您需要管理元素的生命周期
  • 您不能使用 double VEdge::* weight_map_t。相反,您需要为此调整自定义 属性 地图:

    auto kWeightMap = boost::make_transform_value_property_map(
                [](VEdge* ve) { return ve->weight; },
                boost::get(boost::edge_bundle, kGraph)
            );
    
  • 从好的方面来说,您可以使用简写 索引符号 edge_descriptor 计算边缘属性,如 generate_dot_file():

    dot_file << u << " -> " << v << "[label=\"" << kGraph[e]->weight << "\"";
    
  • 当然,这种方法避免了将 VEdge 个对象复制到包中,因此效率更高

事不宜迟(也不用担心内存泄漏):

Live On Coliru

#include <boost/config.hpp>
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/dijkstra_shortest_paths.hpp>
#include <boost/graph/graph_traits.hpp>
#include <boost/property_map/property_map.hpp>
#include <boost/range/algorithm.hpp>
#include <boost/range/adaptors.hpp>

#include <boost/property_map/transform_value_property_map.hpp>

#include <fstream>
#include <iostream>

struct VEdge {
    int id;
    int source, target;
    double weight;
    // custom variables here
};

class test_dijkstra {
    using graph_t           = boost::adjacency_list<boost::listS, boost::vecS, boost::directedS, boost::no_property, VEdge*>;
    using vertex_descriptor = boost::graph_traits<graph_t>::vertex_descriptor;
    using edge_descriptor   = boost::graph_traits<graph_t>::edge_descriptor;

  public:
    test_dijkstra(std::vector<VEdge*>);
    ~test_dijkstra() {}

    void run_dijkstra();

    void print_path();
    void generate_dot_file();

  private:
    graph_t kGraph;

    std::vector<int> kD;
    std::vector<vertex_descriptor> kP;
};

test_dijkstra::test_dijkstra(std::vector<VEdge*> edges) {
    using namespace boost::adaptors;

    size_t max_node;

    boost::partial_sort_copy(
            edges | transformed([](VEdge const* e) -> size_t { return std::max(e->source, e->target); }),
            boost::make_iterator_range(&max_node, &max_node + 1),
            std::greater<size_t>());

    auto e = edges | transformed([](VEdge const *ve) { return std::make_pair(ve->source, ve->target); });
    kGraph = graph_t(e.begin(), e.end(), edges.begin(), max_node + 1);
}

void test_dijkstra::run_dijkstra() {

    auto kWeightMap = boost::make_transform_value_property_map(
                [](VEdge* ve) { return ve->weight; },
                boost::get(boost::edge_bundle, kGraph)
            );

    vertex_descriptor kS    = vertex(0, kGraph);
    kP                      = std::vector<vertex_descriptor>(num_vertices(kGraph));
    kD                      = std::vector<int>(num_vertices(kGraph));

    dijkstra_shortest_paths(
        kGraph, kS,
            predecessor_map(boost::make_iterator_property_map(kP.begin(), get(boost::vertex_index, kGraph)))
           .distance_map(boost::make_iterator_property_map(kD.begin(), get(boost::vertex_index, kGraph)))
           .weight_map(kWeightMap));
}

void test_dijkstra::print_path() {
    std::cout << "distances and parents:" << std::endl;
    boost::graph_traits<graph_t>::vertex_iterator vi, vend;

    for (boost::tie(vi, vend) = vertices(kGraph); vi != vend; ++vi) {
        std::cout << "distance(" << *vi << ") = " << kD[*vi] << ", ";
        std::cout << "parent(" << *vi << ") = " << kP[*vi] << "\n";
    }
}

void test_dijkstra::generate_dot_file() {

    std::ofstream dot_file("figs/dijkstra-eg.dot");

    dot_file << "digraph D {\n"
             << "  rankdir=LR\n"
             << "  size=\"4,3\"\n"
             << "  ratio=\"fill\"\n"
             << "  edge[style=\"bold\"]\n"
             << "  node[shape=\"circle\"]\n";

    boost::graph_traits<graph_t>::edge_iterator ei, ei_end;
    for (boost::tie(ei, ei_end) = edges(kGraph); ei != ei_end; ++ei) {
        boost::graph_traits<graph_t>::edge_descriptor e = *ei;
        boost::graph_traits<graph_t>::vertex_descriptor u = source(e, kGraph), v = target(e, kGraph);
        dot_file << u << " -> " << v << "[label=\"" << kGraph[e]->weight << "\"";

        if (kP[v] == u)
            dot_file << ", color=\"black\"";
        else
            dot_file << ", color=\"grey\"";
        dot_file << "]";
    }
    dot_file << "}";
}

int main() {
    std::vector<VEdge*> edges {
        new VEdge { 2100, 0, 2, 1 },
        new VEdge { 2101, 1, 1, 2 },
        new VEdge { 2102, 1, 3, 1 },
        new VEdge { 2103, 1, 4, 2 },
        new VEdge { 2104, 2, 1, 7 },
        new VEdge { 2105, 2, 3, 3 },
        new VEdge { 2106, 3, 4, 1 },
        new VEdge { 2107, 4, 0, 1 },
        new VEdge { 2108, 4, 1, 1 },
    };

    test_dijkstra test(edges);
    test.run_dijkstra();

    test.print_path();
    test.generate_dot_file();
}

¹ 拍打后 silly typos

²自成一体Live On Coliru