mirror of
https://github.com/ZmnSCPxj/clboss.git
synced 2026-08-13 12:33:20 +02:00
Graph/Dijkstra.hpp: Shortest-path-tree algorithm.
This commit is contained in:
parent
71b03f42fb
commit
bb23e2780f
4 changed files with 385 additions and 0 deletions
244
Graph/Dijkstra.hpp
Normal file
244
Graph/Dijkstra.hpp
Normal file
|
|
@ -0,0 +1,244 @@
|
|||
#ifndef GRAPH_DIJKSTRA_HPP
|
||||
#define GRAPH_DIJKSTRA_HPP
|
||||
|
||||
#include"Graph/TreeNode.hpp"
|
||||
#include"Util/make_unique.hpp"
|
||||
#include<algorithm>
|
||||
#include<functional>
|
||||
#include<map>
|
||||
#include<memory>
|
||||
#include<queue>
|
||||
#include<set>
|
||||
|
||||
namespace Graph {
|
||||
|
||||
/** class Graph::Dijkstra<Node, Cost>
|
||||
*
|
||||
* @brief an implementation of the Dijkstra
|
||||
* shortest-path algorithm.
|
||||
*
|
||||
* @desc this implementation uses a data
|
||||
* interface, i.e. it is mostly passive and
|
||||
* will just wait for data to be fed into it
|
||||
* by the client.
|
||||
*
|
||||
* This allows clients with non-standard
|
||||
* execution policies and incremental map
|
||||
* loading to use this class.
|
||||
*/
|
||||
template< typename Node
|
||||
, typename Cost = double
|
||||
, typename CmpN = std::less<Node>
|
||||
, typename CmpC = std::less<Cost>
|
||||
, typename AddC = std::plus<Cost>
|
||||
>
|
||||
class Dijkstra {
|
||||
public:
|
||||
typedef Graph::TreeNode<std::pair<Node const*, Cost>> TreeNode;
|
||||
typedef
|
||||
std::map< Node
|
||||
, std::unique_ptr<TreeNode>
|
||||
, CmpN
|
||||
> Result;
|
||||
|
||||
Dijkstra() =delete;
|
||||
Dijkstra(Dijkstra&&) =default;
|
||||
Dijkstra( Dijkstra const& o
|
||||
) : cmp_c(o.cmp_c)
|
||||
, add_c(o.add_c)
|
||||
, q(o.q)
|
||||
, treenodes()
|
||||
, closed(o.closed)
|
||||
{
|
||||
/* treenodes have to be copied. */
|
||||
copy_treenodes(o.treenodes);
|
||||
}
|
||||
|
||||
explicit
|
||||
Dijkstra( Node root
|
||||
, Cost root_cost = 0
|
||||
, CmpN cmp_n_ = CmpN()
|
||||
, CmpC cmp_c_ = CmpC()
|
||||
, AddC add_c_ = AddC()
|
||||
) : cmp_c(cmp_c_)
|
||||
, add_c(std::move(add_c_))
|
||||
, q(WrappedCmpC(cmp_c_))
|
||||
, treenodes(cmp_n_)
|
||||
, closed()
|
||||
{
|
||||
initialize(std::move(root), std::move(root_cost));
|
||||
}
|
||||
|
||||
/** Graph::Dijkstra<Node, Cost>::current
|
||||
*
|
||||
* @brief return the current node being considered
|
||||
* by the algorithm.
|
||||
*
|
||||
* @desc the client should provide the neighbors
|
||||
* of the current node via the `neighbor` member
|
||||
* function.
|
||||
*
|
||||
* This function can return `nullptr`, meaning the
|
||||
* run has ended and there are no more nodes
|
||||
* reachable from the root node.
|
||||
*
|
||||
* If you are using this class as a pathfinding
|
||||
* algorithm (as opposed to a shortest-path-tree
|
||||
* algorithm) you can stop when the goal node (or
|
||||
* one of the goal nodes) is returned by this
|
||||
* function.
|
||||
*/
|
||||
Node const* current() const {
|
||||
if (q.empty())
|
||||
return nullptr;
|
||||
return q.top().first->data.first;
|
||||
}
|
||||
/** Graph::Dijsktra<Node, Cost>::neighbor
|
||||
*
|
||||
* @brief inform the algorithm that the `current()`
|
||||
* node has a directly-reachable neighbor, that has
|
||||
* a cost `c` to go to.
|
||||
*
|
||||
* @desc do not call this if `current()` returns
|
||||
* `nullptr`.
|
||||
* Calling this will not change what `current()`
|
||||
* returns.
|
||||
*/
|
||||
void neighbor(Node n, Cost c) {
|
||||
/* for each neighbor v of u: */ /* v = n, u = current() */
|
||||
auto& u = *q.top().first;
|
||||
auto& u_cost = u.data.second;
|
||||
auto alt = add_c(u_cost, c);
|
||||
|
||||
auto it = treenodes.find(n);
|
||||
if (it == treenodes.end()) {
|
||||
/* No TreeNode yet, initialize it to alt. */
|
||||
auto& v = create_treenode(n);
|
||||
v.data.second = alt;
|
||||
reparent(v, u);
|
||||
q.push(std::make_pair(&v, alt));
|
||||
} else {
|
||||
auto& v = *it->second;
|
||||
if (cmp_c(alt, v.data.second)) {
|
||||
v.data.second = alt;
|
||||
reparent(v, u);
|
||||
q.push(std::make_pair(&v, alt));
|
||||
}
|
||||
}
|
||||
}
|
||||
/** Graph::Dijkstra<Node, Cost>::end_neighbors
|
||||
*
|
||||
* @brief inform the algorithm that the `current()`
|
||||
* node has no more neighbors.
|
||||
*
|
||||
* @desc do not call this if `current()` returns
|
||||
* `nullptr`.
|
||||
* After this call, `current()` will change.
|
||||
*/
|
||||
void end_neighbors() {
|
||||
auto u = q.top().first;
|
||||
closed.insert(u);
|
||||
q.pop();
|
||||
/* Filter out those already in the closed set. */
|
||||
while ( !q.empty()
|
||||
&& closed.find(q.top().first) != closed.end()
|
||||
)
|
||||
q.pop();
|
||||
}
|
||||
|
||||
/** Graph::Dijkstra<Node::Cost>::finalize
|
||||
*
|
||||
* @brief destruct this algorithm and extract its
|
||||
* result.
|
||||
*
|
||||
* @desc after calling this function, the object is
|
||||
* now in an unusable state.
|
||||
*/
|
||||
Result
|
||||
finalize()&& {
|
||||
return std::move(treenodes);
|
||||
}
|
||||
|
||||
private:
|
||||
class WrappedCmpC {
|
||||
private:
|
||||
CmpC cmp_c;
|
||||
public:
|
||||
explicit
|
||||
WrappedCmpC(CmpC cmp_c_) : cmp_c(cmp_c_) { }
|
||||
bool operator()( std::pair<TreeNode*, Cost> const& a
|
||||
, std::pair<TreeNode*, Cost> const& b
|
||||
) const {
|
||||
/* We want the priority queue to return
|
||||
* the *lowest* cost, but std::priority_queue
|
||||
* is designed to return the *highest* score
|
||||
* when given std::less.
|
||||
* So flip the args here.
|
||||
*/
|
||||
return cmp_c(b.second, a.second);
|
||||
}
|
||||
};
|
||||
|
||||
CmpC cmp_c;
|
||||
AddC add_c;
|
||||
std::priority_queue< std::pair<TreeNode*, Cost>
|
||||
, std::vector<std::pair<TreeNode*, Cost>>
|
||||
, WrappedCmpC
|
||||
> q;
|
||||
Result treenodes;
|
||||
std::set<TreeNode*> closed;
|
||||
|
||||
void initialize(Node root, Cost root_cost) {
|
||||
auto& tn = get_treenode(root);
|
||||
tn.data.second = root_cost;
|
||||
q.push(std::make_pair(&tn, root_cost));
|
||||
}
|
||||
TreeNode& get_treenode(Node n) {
|
||||
auto it = treenodes.find(n);
|
||||
if (it == treenodes.end())
|
||||
return create_treenode(std::move(n));
|
||||
return *it->second;
|
||||
}
|
||||
TreeNode& create_treenode(Node n) {
|
||||
auto tn = Util::make_unique<TreeNode>();
|
||||
auto itb = treenodes.insert(std::make_pair(
|
||||
std::move(n), std::move(tn)
|
||||
));
|
||||
auto it = itb.first;
|
||||
it->second->data.first = &it->first;
|
||||
return *it->second;
|
||||
}
|
||||
void reparent(TreeNode& node, TreeNode& parent) {
|
||||
if (node.parent) {
|
||||
/* Remove it from the existing parent children. */
|
||||
auto& curparent = const_cast<TreeNode&>(*node.parent);
|
||||
auto& children = curparent.children;
|
||||
children.erase( std::remove( children.begin()
|
||||
, children.end()
|
||||
, &node
|
||||
)
|
||||
, children.end()
|
||||
);
|
||||
}
|
||||
node.parent = &parent;
|
||||
parent.children.push_back(&node);
|
||||
}
|
||||
|
||||
void copy_treenodes(Result const& src) {
|
||||
for (auto const& e : src) {
|
||||
auto const& n = e.first;
|
||||
auto const& tn = *e.second;
|
||||
auto& my_tn = get_treenode(n);
|
||||
if (tn.parent) {
|
||||
auto const& parent_n = *tn.parent->data.first;
|
||||
auto& my_parent_tn = get_treenode(parent_n);
|
||||
reparent(my_tn, my_parent_tn);
|
||||
}
|
||||
my_tn.data.second = tn.data.second;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* !defined(GRAPH_DIJKSTRA_HPP) */
|
||||
22
Graph/TreeNode.hpp
Normal file
22
Graph/TreeNode.hpp
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
#ifndef GRAPH_TREENODE_HPP
|
||||
#define GRAPH_TREENODE_HPP
|
||||
|
||||
#include<vector>
|
||||
|
||||
namespace Graph {
|
||||
|
||||
/** struct Graph::TreeNode<a>
|
||||
*
|
||||
* @brief generic n-ary tree node, with
|
||||
* associated data on the nodes.
|
||||
*/
|
||||
template<typename a>
|
||||
struct TreeNode {
|
||||
a data;
|
||||
TreeNode const* parent;
|
||||
std::vector<TreeNode const*> children;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* !defined(GRAPH_TREENODE_HPP) */
|
||||
|
|
@ -247,6 +247,8 @@ libclboss_la_SOURCES = \
|
|||
Ev/start.hpp \
|
||||
Ev/yield.cpp \
|
||||
Ev/yield.hpp \
|
||||
Graph/Dijkstra.hpp \
|
||||
Graph/TreeNode.hpp \
|
||||
Jsmn/Detail/EndAdvancer.cpp \
|
||||
Jsmn/Detail/EndAdvancer.hpp \
|
||||
Jsmn/Detail/ParseResult.hpp \
|
||||
|
|
@ -390,6 +392,7 @@ TESTS = \
|
|||
tests/ev/test_plus \
|
||||
tests/ev/test_runcmd \
|
||||
tests/ev/test_throw_in_then \
|
||||
tests/graph/test_dijkstra \
|
||||
tests/json/test_out_simple \
|
||||
tests/ln/test_amount \
|
||||
tests/ln/test_nodeid \
|
||||
|
|
|
|||
116
tests/graph/test_dijkstra.cpp
Normal file
116
tests/graph/test_dijkstra.cpp
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
#undef NDEBUG
|
||||
#include"Graph/Dijkstra.hpp"
|
||||
#include"Ln/NodeId.hpp"
|
||||
#include<assert.h>
|
||||
|
||||
namespace {
|
||||
|
||||
auto const A = Ln::NodeId("020000000000000000000000000000000000000000000000000000000000000000");
|
||||
auto const B = Ln::NodeId("020000000000000000000000000000000000000000000000000000000000000001");
|
||||
auto const C = Ln::NodeId("020000000000000000000000000000000000000000000000000000000000000002");
|
||||
|
||||
template<typename a>
|
||||
void test_copy(Graph::Dijkstra<a> const& dijkstra) {
|
||||
auto copy = dijkstra;
|
||||
if (dijkstra.current()) {
|
||||
assert(copy.current());
|
||||
assert(*copy.current() == *dijkstra.current());
|
||||
} else {
|
||||
assert(!copy.current());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main() {
|
||||
auto dijkstra = Graph::Dijkstra<Ln::NodeId>(A);
|
||||
assert(dijkstra.current());
|
||||
assert(*dijkstra.current() == A);
|
||||
|
||||
test_copy(dijkstra);
|
||||
|
||||
{
|
||||
/* We can finalize at any time, at the cost of
|
||||
* creating a copy of the shortest-path tree. */
|
||||
auto tmp = Graph::Dijkstra<Ln::NodeId>(dijkstra).finalize();
|
||||
assert(tmp.size() == 1);
|
||||
assert(tmp.find(A) != tmp.end());
|
||||
assert(tmp[A]->data.second == 0);
|
||||
}
|
||||
|
||||
/*
|
||||
* A --5-- C
|
||||
* \ /
|
||||
* 1 1
|
||||
* \ /
|
||||
* B
|
||||
*/
|
||||
dijkstra.neighbor(B, 1);
|
||||
dijkstra.neighbor(C, 5);
|
||||
dijkstra.end_neighbors();
|
||||
|
||||
{
|
||||
auto tmp = Graph::Dijkstra<Ln::NodeId>(dijkstra).finalize();
|
||||
assert(tmp.size() == 3);
|
||||
assert(tmp.find(A) != tmp.end());
|
||||
assert(tmp.find(B) != tmp.end());
|
||||
assert(tmp.find(C) != tmp.end());
|
||||
assert(tmp[A]->data.second == 0);
|
||||
assert(tmp[B]->data.second == 1);
|
||||
assert(tmp[C]->data.second == 5);
|
||||
assert(!tmp[A]->parent);
|
||||
assert(*tmp[B]->parent->data.first == A);
|
||||
assert(*tmp[C]->parent->data.first == A);
|
||||
}
|
||||
|
||||
/* B is nearer, so it should come first. */
|
||||
assert(dijkstra.current());
|
||||
assert(*dijkstra.current() == B);
|
||||
/* Give its neighbors. */
|
||||
dijkstra.neighbor(A, 1);
|
||||
dijkstra.neighbor(C, 1);
|
||||
dijkstra.end_neighbors();
|
||||
|
||||
{
|
||||
auto tmp = Graph::Dijkstra<Ln::NodeId>(dijkstra).finalize();
|
||||
assert(tmp.size() == 3);
|
||||
assert(tmp.find(A) != tmp.end());
|
||||
assert(tmp.find(B) != tmp.end());
|
||||
assert(tmp.find(C) != tmp.end());
|
||||
assert(tmp[A]->data.second == 0);
|
||||
assert(tmp[B]->data.second == 1);
|
||||
/* C should now be reached by route A->B->C. */
|
||||
assert(tmp[C]->data.second == 2);
|
||||
assert(!tmp[A]->parent);
|
||||
assert(*tmp[B]->parent->data.first == A);
|
||||
assert(*tmp[C]->parent->data.first == B);
|
||||
}
|
||||
|
||||
/* C should now be what is queried. */
|
||||
assert(dijkstra.current());
|
||||
assert(*dijkstra.current() == C);
|
||||
/* Give its neighbors. */
|
||||
dijkstra.neighbor(A, 5);
|
||||
dijkstra.neighbor(B, 1);
|
||||
dijkstra.end_neighbors();
|
||||
|
||||
/* Same result as above. */
|
||||
{
|
||||
auto tmp = Graph::Dijkstra<Ln::NodeId>(dijkstra).finalize();
|
||||
assert(tmp.size() == 3);
|
||||
assert(tmp.find(A) != tmp.end());
|
||||
assert(tmp.find(B) != tmp.end());
|
||||
assert(tmp.find(C) != tmp.end());
|
||||
assert(tmp[A]->data.second == 0);
|
||||
assert(tmp[B]->data.second == 1);
|
||||
assert(tmp[C]->data.second == 2);
|
||||
assert(!tmp[A]->parent);
|
||||
assert(*tmp[B]->parent->data.first == A);
|
||||
assert(*tmp[C]->parent->data.first == B);
|
||||
}
|
||||
|
||||
/* Dijkstra should have completed. */
|
||||
assert(!dijkstra.current());
|
||||
|
||||
return 0;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue