C++ pop()期间的Boost Fibonacci堆访问冲突 上下文
我目前正在实现某种形式的A*算法。我决定使用boost的fibonacci堆作为底层优先级队列 算法运行时正在生成我的图形。作为顶点对象,我正在使用:C++ pop()期间的Boost Fibonacci堆访问冲突 上下文,c++,boost,C++,Boost,我目前正在实现某种形式的A*算法。我决定使用boost的fibonacci堆作为底层优先级队列 算法运行时正在生成我的图形。作为顶点对象,我正在使用: 类顶点{ 公众: 顶点(双,双); 双距离=标准::数值限制::最大值(); 双启发式=0; HeapData*fib; 顶点*前置=空ptr; std::向量adj; 双欧几里德常数(顶点*v); } 我的边缘看起来像: 类边缘{ 公众: 边(顶点*,双); 顶点*顶点=nullptr; 双倍重量=1; } 为了使用boosts fibon
类顶点{
公众:
顶点(双,双);
双距离=标准::数值限制::最大值();
双启发式=0;
HeapData*fib;
顶点*前置=空ptr;
std::向量adj;
双欧几里德常数(顶点*v);
}
我的边缘看起来像:
类边缘{
公众:
边(顶点*,双);
顶点*顶点=nullptr;
双倍重量=1;
}
为了使用boosts fibonacci heap,我读到应该创建一个heap数据对象,我这样做:
struct HeapData{
顶点*v;
boost::heap::fibonacci\u heap::handle\u类型句柄;
HeapData(顶点*u){
v=u;
}
布尔操作站+rhs.v->启发式距离+v->启发式;
}
};
注意,我在比较器中包含了启发式和实际距离,以获得我想要的A*行为
我的实际A*实现如下所示:
boost::heap::fibonacci\u heap;
HeapData fibs(起始点);
起点->距离=0;
起始点->启发式=获取启发式(起始点);
自动句柄=heap.push(fibs);
(*手柄)。手柄=手柄;
而(!heap.empty()){
HeapData u=heap.top();
heap.pop();
如果(u.v->等于(端点)){
返回;
}
doSomeGraphCreationStuff(u.v);//这仅创建顶点和边
用于(边缘*e:u.v->adj){
双新距离=e->重量+u.v->距离;
如果(e->顶点->距离>新距离){
e->顶点->距离=新距离;
e->顶点->前置=u.v;
如果(!e->顶点->纤维){
如果(!u.v->等于(端点)){
e->顶点->启发式=获取启发式(e->顶点);
}
e->vertex->fib=新HeapData(e->vertex);
e->vertex->fib->handle=heap.push(*(e->vertex->fib));
}
否则{
堆。增加(e->顶点->纤维->手柄);
}
}
}
}
问题
如果我使用一个非常小的启发式算法(将a*退化为Dijkstra),算法运行得很好。但是,如果我引入一些更强的启发式,程序会抛出一个exepction,说明:
0xc000005:访问冲突写入位置0x0000000000000000。
在取消链接的方法中,提升循环_list_algorithm.hpp。出于某种原因,next
和prev
为空。这是调用heap.pop()
的直接结果。
请注意,heap.pop()
可以正常工作好几次,并且不会立即崩溃
问题:
是什么原因导致此问题?如何解决此问题?
我试过的
- 我的第一个想法是,我意外地调用了increase(),即使距离+启发式变得越来越大而不是越来越小(根据文档,这可能会破坏东西)。但是,在我的实现中,这是不可能的,因为只有当距离变小时,我才能更改节点。试探法保持不变。我尝试使用update()而不是increase(),但没有成功
- 我试图设置几个断点以获得更详细的视图,但我的数据集很大,无法用较小的数据集重现
- Boost版本:1.76.0
- C++14
- 增加函数确实是正确的(而不是减少函数),因为所有的提升堆都实现为最大堆。我们通过反转比较器并使用递增/递减反转得到一个最小堆
e->vertex->fib = new HeapData(e->vertex);
e->vertex->fib->handle = heap.push(*(e->vertex->fib));
- 在第一行中创建一个HeapData对象。您使
成员指向该对象fib
- 第二行插入该对象的副本(意思是,它是一个新对象,具有不同的对象标识,或者实际上是一个不同的地址)
指向队列中不存在的(泄漏的)HeapData对象,然后e->vertex->fib
- 实际排队的
副本有一个默认构造的HeapData
成员,这意味着该句柄包装了一个空指针。(检查handle
中的detail/stable_heap.hpp
,以验证这一点)boost::heap::detail::node_handle
和HeapData
应该合并:HeapData仅用于将句柄链接到顶点,但您已经可以使顶点直接包含句柄 由于这次合并Vertex
- 顶点队列现在实际上包含顶点,表示代码的意图
- 将所有顶点访问减少一个间接级别(减少冲突)
- 您可以将推送操作写在一行中,从而消除bug出现的空间。之前:
之后:target->fib = new HeapData(target); target->fib->handle = heap.push(*(target->fib));
target->fibhandle = heap.push(target);
- 您的
类实际上并没有对边进行建模,而是对目标进行“邻接” 边缘的一部分,带有Edge
using Node = Vertex*; struct PrioCompare { bool operator()(Node a, Node b) const; };
namespace bh = boost::heap; using Heap = bh::fibonacci_heap<Node, bh::compare<PrioCompare>>; using Handle = Heap::handle_type;
Cost cost() const { return distance + heuristic; }
static constexpr auto INF = std::numeric_limits<Cost>::infinity(); Cost distance = INF;
assert(target->cost() < previous_cost); heap.increase(target->fibhandle);
Cost AStarSearch(Node start, Node destination) { Heap heap; start->distance = 0; start->fibhandle = heap.push(start); while (!heap.empty()) { Node u = heap.top(); heap.pop(); if (u->equals(destination)) { return u->cost(); } u->heuristic = getHeuristic(start); doSomeGraphCreationStuff(u); for (auto& [target, weight] : u->adj) { auto curDistance = weight + u->distance; // if cheaper route, queue or update queued if (curDistance < target->distance) { auto cost_prior = target->cost(); target->distance = curDistance; target->predecessor = u; if (target->fibhandle == NOHANDLE) { target->fibhandle = heap.push(target); } else { assert(target->cost() < cost_prior); heap.update(target->fibhandle); } } } } return INF; }
#include <boost/heap/fibonacci_heap.hpp> #include <iostream> using Cost = double; struct Vertex; Cost getHeuristic(Vertex const*) { return 0; } void doSomeGraphCreationStuff(Vertex const*) { // this only creates vertices and edges } struct OutEdge { // adjacency from implied source vertex Vertex* target = nullptr; Cost weight = 1; }; namespace bh = boost::heap; using Node = Vertex*; struct PrioCompare { bool operator()(Node a, Node b) const; }; using Heap = bh::fibonacci_heap<Node, bh::compare<PrioCompare>>; using Handle = Heap::handle_type; static const Handle NOHANDLE{}; // for expressive comparisons static constexpr auto INF = std::numeric_limits<Cost>::infinity(); struct Vertex { Vertex(Cost d = INF, Cost h = 0) : distance(d), heuristic(h) {} Cost distance = INF; Cost heuristic = 0; Handle fibhandle{}; Vertex* predecessor = nullptr; std::vector<OutEdge> adj; Cost cost() const { return distance + heuristic; } Cost euclideanDistanceTo(Vertex* v); bool equals(Vertex const* u) const { return this == u; } }; // Now Vertex is a complete type, implement comparison bool PrioCompare::operator()(Node a, Node b) const { return a->cost() > b->cost(); } Cost AStarSearch(Node start, Node destination) { Heap heap; start->distance = 0; start->fibhandle = heap.push(start); while (!heap.empty()) { Node u = heap.top(); heap.pop(); if (u->equals(destination)) { return u->cost(); } u->heuristic = getHeuristic(start); doSomeGraphCreationStuff(u); for (auto& [target, weight] : u->adj) { auto curDistance = weight + u->distance; // if cheaper route, queue or update queued if (curDistance < target->distance) { auto cost_prior = target->cost(); target->distance = curDistance; target->predecessor = u; if (target->fibhandle == NOHANDLE) { target->fibhandle = heap.push(target); } else { assert(target->cost() < cost_prior); heap.update(target->fibhandle); } } } } return INF; } int main() { // a very very simple graph data structure with minimal helpers: std::vector<Vertex> graph(10); auto node = [&graph](int id) { return &graph.at(id); }; auto id = [&graph](Vertex const* node) { return node - graph.data(); }; // defining 6 edges graph[0].adj = {{node(2), 1.5}, {node(3), 15}}; graph[2].adj = {{node(4), 2.5}, {node(1), 5}}; graph[1].adj = {{node(7), 0.5}}; graph[7].adj = {{node(3), 0.5}}; // do a search Node startPoint = node(0); Node endPoint = node(7); Cost cost = AStarSearch(startPoint, endPoint); std::cout << "Overall cost: " << cost << ", reverse path: \n"; for (Node node = endPoint; node != nullptr; node = node->predecessor) { std::cout << " - " << id(node) << " distance " << node->distance << "\n"; } }
Overall cost: 7, reverse path: - 7 distance 7 - 1 distance 6.5 - 2 distance 1.5 - 0 distance 0
0 2 1.5 0 3 15 2 4 2.5 2 1 5 1 7 0.5 7 3 0.5
#include <boost/heap/fibonacci_heap.hpp> #include <iostream> #include <deque> #include <fstream> using Cost = double; struct Vertex; struct OutEdge { // adjacency from implied source vertex Vertex* target = nullptr; Cost weight = 1; }; namespace bh = boost::heap; using Node = Vertex*; struct PrioCompare { bool operator()(Node a, Node b) const; }; using MutableQueue = bh::fibonacci_heap<Node, bh::compare<PrioCompare>>; using Handle = MutableQueue::handle_type; static const Handle NOHANDLE{}; // for expressive comparisons static constexpr auto INF = std::numeric_limits<Cost>::infinity(); struct Vertex { Vertex(Cost d = INF, Cost h = 0) : distance(d), cachedHeuristic(h) {} Cost distance = INF; Cost cachedHeuristic = 0; Handle handle{}; Vertex* predecessor = nullptr; std::vector<OutEdge> adj; Cost cost() const { return distance + cachedHeuristic; } Cost euclideanDistanceTo(Vertex* v); }; // Now Vertex is a complete type, implement comparison bool PrioCompare::operator()(Node a, Node b) const { return a->cost() > b->cost(); } class Graph { std::vector<Cost> _heuristics; Cost getHeuristic(Vertex* v) { size_t n = id(v); return n < _heuristics.size() ? _heuristics[n] : 0; } void doSomeGraphCreationStuff(Vertex const*) { // this only creates vertices and edges } public: Graph(std::string edgeFile, std::string heurFile) { { std::ifstream stream(heurFile); _heuristics.assign(std::istream_iterator<Cost>(stream), {}); if (!stream.eof()) throw std::runtime_error("Unexpected heuristics"); } std::ifstream stream(edgeFile); size_t src, tgt; double weight; while (stream >> src >> tgt >> weight) { _nodes.resize(std::max({_nodes.size(), src + 1, tgt + 1})); _nodes[src].adj.push_back({node(tgt), weight}); } if (!stream.eof()) throw std::runtime_error("Unexpected input"); } Node search(size_t from, size_t to) { assert(from < _nodes.size()); assert(to < _nodes.size()); return AStar(node(from), node(to)); } size_t id(Node node) const { // ugh, this is just for "pretty output"... for (size_t i = 0; i < _nodes.size(); ++i) { if (node == &_nodes[i]) return i; } throw std::out_of_range("id"); }; Node node(int id) { return &_nodes.at(id); }; private: // simple graph data structure with minimal helpers: std::deque<Vertex> _nodes; // reference stable when growing at the back // search state MutableQueue _queue; void enqueue(Node n) { assert(n && (n->handle == NOHANDLE)); // get heuristic before insertion! n->cachedHeuristic = getHeuristic(n); n->handle = _queue.push(n); } Node dequeue() { Node node = _queue.top(); node->handle = NOHANDLE; _queue.pop(); return node; } Node AStar(Node start, Node destination) { _queue.clear(); start->distance = 0; enqueue(start); while (!_queue.empty()) { Node u = dequeue(); if (u == destination) { return u; } doSomeGraphCreationStuff(u); for (auto& [target, weight] : u->adj) { auto curDistance = u->distance + weight; // if cheaper route, queue or update queued if (curDistance < target->distance) { auto cost_prior = target->cost(); target->distance = curDistance; target->predecessor = u; if (target->handle == NOHANDLE) { // also caches heuristic enqueue(target); } else { // NOTE: avoid updating heuristic here, because it // breaks the queue invariant if heuristic increased // more than decrease in distance assert(target->cost() < cost_prior); _queue.increase(target->handle); } } } } return nullptr; } }; int main() { Graph graph("input.txt", "heur.txt"); Node arrival = graph.search(0, 7); std::cout << "reverse path: \n"; for (Node n = arrival; n != nullptr; n = n->predecessor) { std::cout << " - " << graph.id(n) << " cost " << n->cost() << "\n"; } }
reverse path: - 7 cost 7 - 1 cost 17.5 - 2 cost 100.5 - 0 cost 7