【问题标题】:Unable to call boost::clear_vertex while using listS for the vertex and edge lists对顶点和边列表使用 listS 时无法调用 boost::clear_vertex
【发布时间】:2018-12-13 21:38:02
【问题描述】:

我正在编写一个程序,该程序使用 boost 图形库来解决旅行商问题,该问题使用 A* 搜索和最小生成树启发式算法。我对 boost::graph 很陌生 在我的启发式课程中,我计算了所有尚未访问的顶点的最小生成树。我通过维护一个来跟踪访问了哪些顶点 原始图的副本,我在每次调用启发式时从中删除当前顶点及其所有边。但是,当我调用 boost::clear_vertex(u, subGraph) 时,uvertex_descriptorsubGraph 是我从中减去顶点的原始图的副本,我得到一个调试断言失败说明:

列出擦除迭代器超出范围。

经过一些调试,我发现最终在 STL <list> 的第 1383 行产生了错误,由于某种原因,以下条件为 false:

_Where._Getcont() != _STD addressof(this->_Get_data()).

这是我的启发式课程:

class MST_Heuristic : public astar_heuristic<MyGraphType, double>
{
public:
    MST_Heuristic(vertex_descriptor goal, MyGraphType g)
        : m_goal(goal), subGraph(g), firstRun(true) {}
    double operator () (vertex_descriptor u)
    {
        double MSTDist = 0.0;
        double startDist = numeric_limits<double>::infinity();
        int minEdgeWeight = subGraph[*out_edges(u, subGraph).first].weight;         // initialize minEdgeWeight to weight of first out edge

        if (firstRun)
        {
            IndexMap mapIndex;
            associative_property_map<IndexMap> vertexIndices(mapIndex);
            int j = 0;
            for (auto v = vertices(subGraph).first; v != vertices(subGraph).second; v++)
            {
                put(vertexIndices, *v, j++);
            }

            dijkstra_shortest_paths(subGraph, u, get(&VertexData::pred, subGraph),  // calculate the shortest path from the start for each vertex
                get(&VertexData::dist2, subGraph), get(&EdgeData::weight, subGraph),
                vertexIndices, less<double>(), plus<double>(),
                numeric_limits<double>::infinity(), 0, do_nothing_dijkstra_visitor(),
                get(&VertexData::color, subGraph));
        }
        for (auto ed : make_iterator_range(out_edges(u, subGraph)))
        {
            minEdgeWeight = min(subGraph[ed].weight, minEdgeWeight);                // find distance from nearest unvisited vertex to the current vertex
        }
        clear_vertex(u, subGraph);
        remove_vertex(u, subGraph);
        // Problem here; The problem has to do with removing vertices/edges and destabilizing the graph, thereby making it impossible to iterate through the graph

        IndexMap mapIndex;
        associative_property_map<IndexMap> vertexIndices(mapIndex);
        int j = 0;
        for (auto v = vertices(subGraph).first; v != vertices(subGraph).second; v++)
        {
            put(vertexIndices, *v, j++);
        }

        prim_minimum_spanning_tree(subGraph, *vertices(subGraph).first,             // calculate the minimum spanning tree
            get(&VertexData::pred, subGraph), get(&VertexData::dist, subGraph),
            get(&EdgeData::weight, subGraph), vertexIndices,
            do_nothing_dijkstra_visitor());

        for (auto vd : make_iterator_range(vertices(subGraph)))                     // estimate distance to travel all the unvisited vertices
        {
            MSTDist += subGraph[vd].dist;
            startDist = min(startDist, subGraph[vd].dist2);
        }

        firstRun = false;
        return static_cast<double>(minEdgeWeight) + MSTDist + startDist;            // return the result of the heuristic function
    }
private:
    vertex_descriptor m_goal;
    MyGraphType subGraph;
    bool firstRun;
};

以下是一些相关的 typedef:

typedef adjacency_list_traits<listS, listS, undirectedS> GraphTraits;               // to simplify the next definition

typedef GraphTraits::vertex_descriptor vertex_descriptor;                           // vertex descriptor for the graph

typedef GraphTraits::edge_descriptor edge_descriptor;                               // edge descriptor for the graph

typedef std::map<vertex_descriptor, size_t>IndexMap;                                // type used for the vertex index property map

typedef adjacency_list<listS, listS, undirectedS,VertexData, EdgeData> MyGraphType; // graph type

我非常感谢有人为我澄清为什么会发生这种情况。此外,我对启发式类的想法可能是完全愚蠢的,所以如果你认为我应该尝试一些其他方法来实现最小生成树启发式而不是继续搞乱这个,我当然愿意接受这个前景。如果我的启发式是愚蠢的,我真的很感激一些关于还能做什么的建议。我的 boost 版本是 boost_1_67_0,我使用的是 MS Visual Studio 2017。

【问题讨论】:

  • 好的。所以,我从 Sehe 的答案中实现了建议/修订,对于任何未来的读者来说,答案都有效,但这对 A* 来说不是一个好的启发式方法,因为在每个顶点都调用了一次启发式函数之后,子图已完全销毁,无法再用于算法。
  • 也许您可以通过引用获取“子图” - 假设修改正在搜索的图是合法的
  • @sehe 感谢您的建议。我决定改为尝试使子图成为一个指针,并根据原始图中顶点的颜色在子图中的哪些顶点和边为每个启发式调用动态创建子图。这样就可以了,所以我不必从子图中删除东西,所以希望这会更好。

标签: c++ boost a-star boost-graph


【解决方案1】:

您遇到了来自 MSVC 的迭代器调试检查。这很好,因为否则您可能不知道它,您的程序会(静默?)Undefined Behaviour

现在让我看看代码。

这看起来很可疑:

double minEdgeWeight =
    subGraph[*out_edges(u, subGraph).first].weight; // initialize minEdgeWeight to weight of first out edge

这带有一个隐含的假设,即u 至少有一个出边。这可能是真的,但你真的应该检查一下。

进一步inspection with UbSan:

/home/sehe/custom/boost_1_67_0/boost/graph/breadth_first_search.hpp:82:30: runtime error: load of value 3200171710, which is not a valid value for type 'boost::default_color_type'
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior /home/sehe/custom/boost_1_67_0/boost/graph/breadth_first_search.hpp:82:30 in 
/home/sehe/custom/boost_1_67_0/boost/graph/breadth_first_search.hpp:83:13: runtime error: load of value 3200171710, which is not a valid value for type 'ColorValue' (aka 'boost::default_color_type')
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior /home/sehe/custom/boost_1_67_0/boost/graph/breadth_first_search.hpp:83:13 in 
/home/sehe/custom/boost_1_67_0/boost/graph/breadth_first_search.hpp:87:15: runtime error: load of value 3200171710, which is not a valid value for type 'ColorValue' (aka 'boost::default_color_type')
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior /home/sehe/custom/boost_1_67_0/boost/graph/breadth_first_search.hpp:87:15 in 
sotest: /home/sehe/custom/boost_1_67_0/boost/graph/two_bit_color_map.hpp:86: void boost::put(const two_bit_color_map<IndexMap> &, typename property_traits<IndexMap>::key_type, boost::two_bit_color_type) [IndexMap = boost::associative_property_map<std::map<void *, unsigned long, std::less<void *>, std::allocator<std::pair<void *const, unsigned long> > > >]: Assertion `(std::size_t)i < pm.n' failed.

也许初始化那个颜色图是个好主意。我不知道这是否适用于您的代码,因为您没有包含相关代码 (again)。

所以我改变了:

struct VertexData {
    vertex_descriptor pred;
    double dist = 0, dist2 = 0;
    boost::default_color_type color = {};
};

不,仍然是同样的错误。现在通读代码。

... 20 分钟后。啊哈。您正在将图表复制到subGraph。但是,您还传递了一个参数u。这怎么可能是正确的?顶点u 很可能来自subGraph。这可能是另一个错误来源。

让我们也解决这个问题:

msth(msth.vertex(2));

使用新成员访问器:

vertex_descriptor vertex(std::size_t n) const {
    return boost::vertex(n, subGraph);
}

收到您的评论

    // Problem here; The problem has to do with removing vertices/edges and destabilizing the graph, thereby making
    // it impossible to iterate through the graph

很明显,您在图形外部有一个顶点u。与“不稳定”无关(这不是它的工作原理。迭代器有时会失效,但不会因此而变得不稳定:如果不小心,您可能会调用未定义的行为)。

至少,当通过有效的u 时,UbSan 和 ASan 不会在这里抱怨,这是一个好兆头。很可能您的编译器的调试迭代器也不会抱怨。

现在,请注意:

  • listS 确实 使 remove 上的任何其他迭代器无效(这也在 Iterator invalidation rules 中)。显然,只有那一个被移除了。

  • m_goal 遇到与u 相同的问题:它几乎不可能来自正确的图表,因为您要复制整个图表

  • 尽管remove 仅使特定的顶点描述符无效,但您似乎正试图在 A* 搜索的回调中执行此操作。这很可能会破坏该算法假定的不变量(我没有检查文档,但你应该检查!再次,这是因为你没有显示 A* 相关代码)。

  • 对于Weight 是否为double,您的代码似乎仍然存在问题。 (为什么是static_cast?)


最终结果

这就是我最后得到的,包括各种清理。

Live On Coliru

#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/astar_search.hpp>
#include <boost/graph/visitors.hpp>
#include <boost/graph/dijkstra_shortest_paths.hpp>
#include <boost/graph/prim_minimum_spanning_tree.hpp>
#include <boost/graph/graph_utility.hpp>
#include <iomanip>
#include <numeric>

typedef boost::adjacency_list_traits<boost::listS, boost::listS, boost::undirectedS>
    GraphTraits;                                          // to simplify the next definition
typedef GraphTraits::vertex_descriptor vertex_descriptor; // vertex descriptor for the graph
typedef GraphTraits::edge_descriptor edge_descriptor;     // edge descriptor for the graph
typedef double Weight;

struct VertexData {
    std::string name;
    VertexData(std::string name = "") : name(std::move(name)) {}
    //
    vertex_descriptor pred {};
    Weight dist = 0, dist2 = 0;
    boost::default_color_type color = {};

    friend std::ostream& operator<<(std::ostream &os, VertexData const &vd) {
        return os << "{name:" << std::quoted(vd.name) << "}";
    }
};

struct EdgeData {
    Weight weight = 1;
};

typedef boost::adjacency_list<boost::listS, boost::listS, boost::undirectedS, VertexData, EdgeData>
    MyGraphType; // graph type

class MST_Heuristic : public boost::astar_heuristic<MyGraphType, Weight> {
    struct do_nothing_dijkstra_visitor : boost::default_dijkstra_visitor {};

    auto make_index() const {
        std::map<vertex_descriptor, size_t> m;
        size_t n=0;
        for (auto vd : boost::make_iterator_range(vertices(subGraph)))
            m[vd] = n++;
        return m;
    }
  public:
    MST_Heuristic(MyGraphType g) : subGraph(g), firstRun(true) {}

    Weight operator()(vertex_descriptor u) {

        if (firstRun) {
            auto idx = make_index();
            dijkstra_shortest_paths(
                subGraph, u,
                get(&VertexData::pred, subGraph), // calculate the shortest path from the start for each vertex
                get(&VertexData::dist2, subGraph),
                get(&EdgeData::weight, subGraph),
                boost::make_assoc_property_map(idx), std::less<Weight>(),
                std::plus<Weight>(), std::numeric_limits<Weight>::infinity(), 0, do_nothing_dijkstra_visitor(),
                get(&VertexData::color, subGraph));
        }

        Weight minEdgeWeight = std::numeric_limits<Weight>::max(); // initialize minEdgeWeight to weight of first out edge
        for (auto ed : make_iterator_range(out_edges(u, subGraph))) {
            minEdgeWeight = std::min(subGraph[ed].weight, minEdgeWeight); // find distance from nearest unvisited vertex to the current vertex
        }

        clear_vertex(u, subGraph);
        remove_vertex(u, subGraph);

        {
            auto idx = make_index();
            prim_minimum_spanning_tree(subGraph, vertex(0), // calculate the minimum spanning tree
                                       get(&VertexData::pred, subGraph), get(&VertexData::dist, subGraph),
                                       get(&EdgeData::weight, subGraph), boost::make_assoc_property_map(idx),
                                       do_nothing_dijkstra_visitor());
        }

        //// combine
        Weight MSTDist = 0.0;
        Weight startDist = std::numeric_limits<Weight>::infinity();

        for (auto vd : boost::make_iterator_range(vertices(subGraph))) // estimate distance to travel all the unvisited vertices
        {
            MSTDist += subGraph[vd].dist;
            startDist = std::min(startDist, subGraph[vd].dist2);
        }

        firstRun = false;
        return minEdgeWeight + MSTDist + startDist; // return the result of the heuristic function
    }

    vertex_descriptor vertex(std::size_t n) const {
        return boost::vertex(n, subGraph);
    }

  private:

    MyGraphType subGraph;
    bool firstRun;
};

int main() {
    MyGraphType g;

    auto v1 = add_vertex({"one"}, g);
    auto v2 = add_vertex({"two"}, g);
    auto v3 = add_vertex({"three"}, g);
    auto v4 = add_vertex({"four"}, g);
    auto v5 = add_vertex({"five"}, g);

    add_edge(v1, v2, g);
    add_edge(v2, v3, g);
    add_edge(v3, v4, g);
    add_edge(v4, v5, g);

    print_graph(g, get(&VertexData::name, g));

    MST_Heuristic msth(g);
    msth(msth.vertex(2));
}

打印

one <--> two 
two <--> one three 
three <--> two four 
four <--> three five 
five <--> four 

【讨论】:

  • 非常感谢您的帮助;我完全应该看到u 不是来自subGraph。并感谢您指出/修复的其他问题。在几个问题上,我从你那里学到的东西比从教授那里学到的要多得多。顺便说一句,静态转换为 int 的原因是因为我的教授说重量应该作为 int 输出。
  • @Pumpkin2288 你为什么不使用upvoting/accepting 系统?现在,看起来你只取不还。如果 Sehe 的回答解决了您的问题,请接受它。 感谢词很好,但接受的答案会通知其他用户问题已解决。开始使用它。即使答案不能完全解决您的问题,您也可以投票赞成这样的答案。这个答案充满了宝贵的意见,我相信它付出了很多努力。
  • 感谢@rafix07 - 另请参阅 OP:meta.stackexchange.com/questions/5234/…
  • @rafix07 哦,对不起,我是新手,现在还不知道系统是如何工作的。感谢那里的解释。并感谢该链接sehe
  • 对于我应该在一个问题中发布多少代码,是否有任何一般准则?我的印象是我应该尽量不要在我的问题中只转储一个大约 300 行的整个程序,但我一直没有发布足够的内容来明确问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-05-02
  • 1970-01-01
  • 2018-10-22
  • 2015-10-27
  • 2014-09-21
  • 2011-08-12
相关资源
最近更新 更多