// compile with: g++ --std=c++11 multipath.cpp
//
// Problem Statement:
// find the n shortest paths from a to b
// Answer:
// The proposer of the question seemed to think this was hard
// and even stated many more limitations to the question(no cycles etc)..
// but i took it as dead easy. Since this inst a challenge of efficiency
// ill stop at the first pass naive recursive answer..
#include <utility>
#include <stdint.h>
#include <vector>
#include <map>
#include <memory>
#include <iostream>
struct Node
{
uint32_t id_;
std::vector<std::pair<Node*,uint32_t> > kids_;
Node(uint32_t id) :
id_(id)
{}
};
struct Graph
{
// meh should be better..
std::map<uint32_t, std::shared_ptr<Node> > nodes_;
Node* get(uint32_t id)
{
Node* n = nodes_[id].get();
if (n == NULL)
{
nodes_[id] = std::make_shared<Node>(id);
n = nodes_[id].get();
}
return n;
}
void link(uint32_t fromId,
uint32_t toId,
uint32_t weight)
{
Node* from = nodes_[fromId].get();
if (from == NULL)
{
nodes_[fromId] = std::make_shared<Node>(fromId);
from = nodes_[fromId].get();
}
Node* to = nodes_[toId].get();
if (to == NULL)
{
nodes_[toId] = std::make_shared<Node>(toId);
to = nodes_[toId].get();
}
from->kids_.emplace_back(std::make_pair(to,weight));
}
};
struct PathNode
{
std::shared_ptr<PathNode> parent_;
Node* node_;
uint32_t cost_;
PathNode(std::shared_ptr<PathNode> parent,
Node* node,
uint32_t cost) :
parent_(parent),
node_(node),
cost_(cost)
{}
};
std::ostream& operator<<(std::ostream& os,
const PathNode& path)
{
// inverted.. but it meets my needs
if (path.parent_.get() != NULL)
{
const PathNode& p = *(path.parent_);
os << p;
os << "-";
}
if (path.node_ != NULL)
os << path.node_->id_;
return os;
}
// ************ NAIVE APPROACH ***********
// WARNING do be careful here.. there are some large weaknesses
// in the Naive version:
// - negative weights will break the shortest first ordering
// - cycles that trap the search in disconnected areas will never terminate
//
// A stronger algo would fix this by checking reachability from the node and
// pruning the impossible branches from the tree (in fact that would be my next
// step in optimization as it also saves cycles.. but im here for a different
// target so not going there.. a more simple fix would be to add a cycle
// limit and terminate on that..)
std::vector<std::shared_ptr<PathNode> > findPaths(Node* from,
Node* to,
uint32_t limit)
{
std::vector<std::shared_ptr<PathNode> > results;
auto comp = [] (const std::shared_ptr<PathNode>& a,
const std::shared_ptr<PathNode>& b) -> bool
{ return a->cost_ > b->cost_; };
// I was using a std::priority_queue.. problem is i
// cant print it to demonstrate the stack action to
// the question proposer
std::vector<std::shared_ptr<PathNode> > stack;
// ok start the algo by adding the *from* node into a path.. and stacking it
stack.push_back(std::make_shared<PathNode>(nullptr,
from,
0));
while (results.size() < limit and not stack.empty())
{
// show the stack...
// std::cout << " stack: \n";
// for (const std::shared_ptr<PathNode>& path : stack)
// std::cout << " -- c:" << path->cost_ << " p:" << *path << "\n";
std::pop_heap(stack.begin(), stack.end(), comp);
std::shared_ptr<PathNode> current = stack.back();
stack.pop_back();
// check if node was at the terminal
if (current->node_ == to)
{
// register it as the next result
results.push_back(current);
// and end if we have our "limit" of solutions
if (results.size() > limit)
return results;
}
// and then expand search tree using the current node
for (const std::pair<Node*,uint32_t>& edge : current->node_->kids_)
{
std::shared_ptr<PathNode> step =
std::make_shared<PathNode>(current,
edge.first,
current->cost_ + edge.second);
// now here is a tricky part.. the next shortest route may
// include the current one even if it is a *terminal* (ie self loop)
// hence we *always* push even if its the *end*!
stack.push_back(step);
std::push_heap(stack.begin(), stack.end(), comp);
}
}
// ok we fell of the end of the stack that means there are not "limit" solutions
return results;
}
void printPaths(std::ostream& os,
const std::vector<std::shared_ptr<PathNode> >& paths)
{
os << "Results...\n";
for (const std::shared_ptr<PathNode>& path : paths)
{
os << "d:" << path->cost_ << " p:" << *path << "\n";
}
}
void test()
{
{
// warning naive algo death - dont do this with a cycle it will never terminate
Graph disconnected;
Node* to = disconnected.get(0);
Node* from = disconnected.get(1);
printPaths(std::cout, findPaths(from,to,3));
}
{
// self
Graph self;
self.link(0,0,1);
Node* zero = self.get(0);
printPaths(std::cout, findPaths(zero,zero,3));
}
{
// cycle
Graph cycle;
cycle.link(0,1,1);
cycle.link(1,0,1);
Node* from = cycle.get(0);
Node* to = cycle.get(1);
printPaths(std::cout, findPaths(from,to,3));
}
{
// a line of nodes ( asking for 3 anwers.. impossible)
Graph oneLine;
oneLine.link(0,1,1);
oneLine.link(1,2,1);
oneLine.link(2,3,1);
Node* from = oneLine.get(0);
Node* to = oneLine.get(3);
printPaths(std::cout, findPaths(from,to,3));
}
{
// 2 lines of nodes ( asking for 3 anwers.. impossible)
Graph twoLine;
twoLine.link(0,1,1);
twoLine.link(1,2,1);
twoLine.link(2,3,1);
twoLine.link(0,4,2);
twoLine.link(4,3,2);
Node* from = twoLine.get(0);
Node* to = twoLine.get(3);
printPaths(std::cout, findPaths(from,to,3));
}
{
// the questioners challenge graph
Graph triangle;
triangle.link(1,2,1);
triangle.link(1,3,2);
triangle.link(2,1,1);
triangle.link(2,3,3);
triangle.link(2,4,2);
triangle.link(2,5,1);
triangle.link(3,1,2);
triangle.link(3,2,3);
triangle.link(3,5,2);
triangle.link(3,6,1);
triangle.link(4,2,2);
triangle.link(4,5,3);
triangle.link(5,2,1);
triangle.link(5,3,2);
triangle.link(5,4,3);
triangle.link(5,6,3);
triangle.link(6,5,3);
triangle.link(6,3,1);
Node* from = triangle.get(1);
Node* to = triangle.get(6);
printPaths(std::cout, findPaths(from,to,10));
}
}
int main()
{
test();
}
And output will look like this:
Results... Results... d:0 p:0 d:1 p:0-0 d:2 p:0-0-0 Results... d:1 p:0-1 d:3 p:0-1-0-1 d:5 p:0-1-0-1-0-1 Results... d:3 p:0-1-2-3 Results... d:3 p:0-1-2-3 d:4 p:0-4-3 Results... d:3 p:1-3-6 d:5 p:1-2-3-6 d:5 p:1-2-1-3-6 d:5 p:1-2-5-6 d:5 p:1-3-6-3-6 d:5 p:1-2-5-3-6 d:7 p:1-2-1-2-5-6 d:7 p:1-2-1-2-1-3-6 d:7 p:1-2-3-6-3-6 d:7 p:1-2-5-2-1-3-6
No comments:
Post a Comment