Edit: I have incorporated most of the suggestions into the code below. Is there anything else I could do in order to improve quality of my implementation?
#ifndef DIRECTED_GRAPH_NODE_H
#define DIRECTED_GRAPH_NODE_H
#include <string>
#include <unordered_set>
#include <vector>
/*******************************************************************************
* This class implements a node in directed graphs. The adjacency list *
* invariant is that if there is a directed edge from u to v, u.m_out has a *
* pointer to v, and v.m_in has a pointer to u. *
*******************************************************************************/
class DirectedGraphNode {
public:
using VecContainer = std::vector<DirectedGraphNode*>;
using SetContainer = std::unordered_set<DirectedGraphNode*>;
DirectedGraphNode(const std::string &&namename);
void add_child(DirectedGraphNode& child);
bool has_child(DirectedGraphNode& query);
void remove_child(DirectedGraphNode& child);
// Child iterators.
typename VecContainerstd::unordered_set<DirectedGraphNode*>::const_iterator begin();
/* typename */ VecContainerstd::unordered_set<DirectedGraphNode*>::const_iterator end();
bool operator==(const DirectedGraphNode& other) const;
friend std::ostream& operator<<(std::ostream& os,
const DirectedGraphNode& node);
// Forward declaration.
class ParentIteratorProxy;
ParentIteratorProxy parents();
class ParentIteratorProxy {
public:
ParentIteratorProxy(const DirectedGraphNode* owner);
VecContainerstd::unordered_set<DirectedGraphNode*>::const_iterator begin();
VecContainerstd::unordered_set<DirectedGraphNode*>::const_iterator end();
private:
const DirectedGraphNode* mp_owner;
};
private:
// m_name specifies the identity of a node.
std::string m_name;
// Sets for fast arc queries.
SetContainer m_in_set;
SetContainer m_out_set;
// Vectors for faster iteration.
VecContainerstd::unordered_set<DirectedGraphNode*> m_in_vec;m_in;
VecContainerstd::unordered_set<DirectedGraphNode*> m_out_vec;m_out;
};
#endif // DIRECTED_GRAPH_NODE_H
#include <iostream>
#include <stdexcept>
#include <unordered_set>
#include <utility>
#include "directed_graph_node.h"
/*******************************************************************************
* Constructs a new DirectedGraphNode with the given name. *
*******************************************************************************/
DirectedGraphNode::DirectedGraphNode(const std::string &&namename) : {
m_name(std::move(name)) {= name;
}
/*******************************************************************************
* Creates a directed edge from this node to node 'child'. *
*******************************************************************************/
void DirectedGraphNode::add_child(DirectedGraphNode& child) {
if (m_out_set.find(&child) == m_out_set.end()) {
// 'child' is not yet in the set of children.
m_out_setm_out.insert(&child);
child.m_in_setm_in.insert(this);
// Add to vectors as well.
m_out_vec.push_back(&child);
child.m_in_vec.push_back(this);
}
}
/*******************************************************************************
* Queries whether there is an arc (this, query). *
*******************************************************************************/
bool DirectedGraphNode::has_child(DirectedGraphNode& query) {
// Broken adjacency list invariant is the situation when an arc is
// recorded only in the vector/set of only one node:
// Suppose we have added a directed arc (u, v). Now, (1) v is mentioned in
// the "out"-versions of both set and vector of node u, and (2) u is
// mentioned in the "in"-versions of both set and vector of node v.
// If one of those fail to hold while the other is o.k., adjacency list
// invariant is broken.
if (m_out_setm_out.find(&query) == m_out_setm_out.end()) {
if (query.m_in_setm_in.find(this) != query.m_in_setm_in.end()) {
// The adjacency list invariant is broken. See the header.
throw std::runtime_error("Adjacency list invariant broken. 1/2.");
}
return false;
} else if (query.m_in_setm_in.find(this) == query.m_in_setm_in.end()) {
throw std::runtime_error("Adjacency list invariant broken. 2/2.");
}
return true;
}
/*******************************************************************************
* Removes the edge (this, child). *
*******************************************************************************/
void DirectedGraphNode::remove_child(DirectedGraphNode& child) {
m_out_setm_out.erase(&child);
child.m_in_set.erase(this);
typename VecContainer::const_iterator it = m_out_vec.begin();
while (*it != &child) {
it++;
}
m_out_vecm_in.erase(it);
it = child.m_in_vec.begin();
while (*it != this) {
it++;
}
child.m_in_vec.erase(it);
}
/*******************************************************************************
* Compares by name this node to 'other'. *
*******************************************************************************/
bool DirectedGraphNode::operator ==(const DirectedGraphNode& other) const {
return this->m_name.compare(other.m_name) == other.m_name;0;
}
/*******************************************************************************
* Returns a const iterator to a child node of this node. *
*******************************************************************************/
DirectedGraphNodestd::VecContainerunordered_set<DirectedGraphNode*>::const_iterator
DirectedGraphNode::begin() {
return m_out_vecm_out.begin();
}
/*******************************************************************************
* Returns a const iterator to the end of child list. *
*******************************************************************************/
DirectedGraphNodestd::VecContainerunordered_set<DirectedGraphNode*>::const_iterator
DirectedGraphNode::end() {
return m_out_vecm_out.end();
}
/*******************************************************************************
* Returns a proxy iterator over a node's parent nodes. *
*******************************************************************************/
DirectedGraphNode::ParentIteratorProxy
::ParentIteratorProxy(const DirectedGraphNode* p_owner) :
mp_owner(p_owner) {}
/*******************************************************************************
* Returns the first parent node in the parent list of the owner node. *
*******************************************************************************/
DirectedGraphNodestd::VecContainerunordered_set<DirectedGraphNode*>::const_iterator
DirectedGraphNode::ParentIteratorProxy::begin() {
return mp_owner->m_in_vec>m_in.begin();
}
/*******************************************************************************
* Returns an iterator pointing to the end of owner node's parent list. *
*******************************************************************************/
DirectedGraphNodestd::VecContainerunordered_set<DirectedGraphNode*>::const_iterator
DirectedGraphNode::ParentIteratorProxy::end() {
return mp_owner->m_in_vec>m_in.end();
}
/*******************************************************************************
* Returns an iterator over owner node's parent list. *
*******************************************************************************/
DirectedGraphNode::ParentIteratorProxy DirectedGraphNode::parents() {
return ParentIteratorProxy(this);
}
/*******************************************************************************
* Neatly prints a node. *
*******************************************************************************/
std::ostream& operator<<(std::ostream& os, const DirectedGraphNode& node) {
return os << "[DirectedGraphNode " << node.m_name << "]";
}
#ifndef PATHFINDER_H
#define PATHFINDER_H
#include <algorithm>
#include <memory>
#include <unordered_map>
#include <vector>
#include "directed_graph_node.h"
using ParentMap = std::unordered_map<DirectedGraphNode*, DirectedGraphNode*>;unordered_map;
using PathContainer = std::vector<DirectedGraphNode*>;vector;
class PathFinder {
public:
virtual ~PathFinder() = default;
virtual std::unique_ptr<PathContainer>vector<DirectedGraphNode*>*
search(DirectedGraphNode& source,
DirectedGraphNode& target) = 0;
//vector<DirectedGraphNode*>* The
path finders allocate using 'new' the structure describing theconstruct_path(DirectedGraphNode* pathtouch,
// and it is the responsibility of the caller to 'delete' it after use.
std::unique_ptr<PathContainer>
construct_path(DirectedGraphNode& touchunordered_map<DirectedGraphNode*,
ParentMap& pmf,
ParentMap& pmb)DirectedGraphNode*>* {p_parents_f,
PathContainer* p_path = new PathContainer;
DirectedGraphNode* u = &touch;
unordered_map<DirectedGraphNode*,
while (u) {
p_path->push_back(u);
u = pmf[u];
DirectedGraphNode*>* p_parents_b) {
}
vector<DirectedGraphNode*>* p_path = std::reverse(p_path->begin(),new p_path->end());vector<DirectedGraphNode*>;
DirectedGraphNode* u = pmb[&touch];const_cast<DirectedGraphNode*>(touch);
while (u) {
p_path->push_back(u);
u = pmb[u];(*p_parents_f)[u];
}
return std::unique_ptr<PathContainer>reverse(p_path->begin();
, }p_path->end());
std::unique_ptr<PathContainer>
if construct_path(DirectedGraphNode& target, ParentMap& pmp_parents_b) {
PathContainer* p_path = new PathContainer;
u = (*p_parents_b)[touch];
DirectedGraphNode* u = ⌖
while (u) {
p_path->push_back(u);
u = pm[u];(*p_parents_b)[u];
}
std::reverse(p_path->begin(), p_path->end());}
return std::unique_ptr<PathContainer>(p_path);p_path;
}
};
#endif // PATHFINDER_H
#ifndef BFS_PATH_FINDER_H
#define BFS_PATH_FINDER_H
#include <deque>
#include <memory><iostream>
#include <vector>
#include <unordered_map>
#include "directed_graph_node.h"
#include "path_finder.h"
/*******************************************************************************
* Implements a path finder using breadth-first search in unweighted digraphs. *
*******************************************************************************/
class BFSPathFinder : public PathFinder {
public:
std::unique_ptr<PathContainer>vector<DirectedGraphNode*>* search(DirectedGraphNode& source,
DirectedGraphNode& target) {
m_queue.clear();
m_parent_map.clear();
// Initialize the state.
m_queue.push_back(&source);
m_parent_map[&source] = nullptr;
while (!m_queue.empty()) {
DirectedGraphNode* p_current = m_queue.front();
if (*p_current == target) {
// Reached the target.
return construct_path(*p_currentp_current, m_parent_map&m_parent_map, nullptr);
}
m_queue.pop_front();
for (auto p_child : *p_current) {
if (m_parent_map.find(p_child) == m_parent_map.end()) {
m_parent_map.insert({p_child, p_current});
m_queue.push_back(p_child);
}
}
}
return std::unique_ptr<PathContainer>(nullptr);nullptr;
}
private:
std::deque<DirectedGraphNode*> m_queue;
std::unordered_map<DirectedGraphNode*,
DirectedGraphNode*> m_parent_map;
};
#endif // BFS_PATH_FINDER_H
#ifndef BIBFS_PATH_FINDER_H
#define BIBFS_PATH_FINDER_H
#include <deque>
#include <memory><iostream>
#include <vector>
#include <unordered_map>
#include "directed_graph_node.h"
#include "path_finder.h"
/*******************************************************************************
* Implements a path finder using bidirectional breadth-first search for *
* unweighted digraphs. *
*******************************************************************************/
class BidirectionalBFSPathFinder : public PathFinder {
public:
std::unique_ptr<PathContainer>vector<DirectedGraphNode*>* search(DirectedGraphNode& source,
DirectedGraphNode& target) {
m_queue1.clear();
m_queue2.clear();
m_parent_map1.clear();
m_parent_map2.clear();
m_distance_map1.clear();
m_distance_map2.clear();
// Initialize the state.
m_queue1.push_back(&source);
m_queue2.push_back(&target);
m_parent_map1[&source] = nullptr;
m_parent_map2[&target] = nullptr;
m_distance_map1[&source] = 0;
m_distance_map2[&target] = 0;
// A node where the two search frontiers "meet".
DirectedGraphNode* p_touch = nullptr;
// The best known cost of a shortest path through 'p_touch' if it is not
// nullptr.
size_t best_cost = std::numeric_limits<std::size_t>::max();
while (m_queue1.size() > 0 && m_queue2.size() > 0) {
if (p_touch != nullptr
&& m_distance_map1[m_queue1.front()]
+ m_distance_map2[m_queue2.front()] >= best_cost) {
// Termination condition met.
return construct_path(*p_touchp_touch,
m_parent_map1&m_parent_map1,
m_parent_map2&m_parent_map2);
}
// A trivial load balancing.
if (m_queue1.size() < m_queue2.size()) {
// Once here, expand the forward search frontier.
DirectedGraphNode* p_current = m_queue1.front();
if (m_parent_map2.find(p_current) != m_parent_map2.end()) {
// Here, update to the shortest path is possible.
const size_t tmp = m_distance_map1[p_current] +
m_distance_map2[p_current];
if (best_cost > tmp) {
// Update the current best touch node.
best_cost = tmp;
p_touch = p_current;
}
}
m_queue1.pop_front();
// Expand the forward search frontier.
for (auto p_child : *p_current) {
if (m_parent_map1.find(p_child) == m_parent_map1.end()) {
m_parent_map1.insert({p_child, p_current});
m_distance_map1.insert({
p_child,
m_distance_map1[p_current] + 1
});
m_queue1.push_back(p_child);
}
}
} else {
// Once here, expand the backward search frontier.
DirectedGraphNode* p_current = m_queue2.front();
if (m_parent_map1.find(p_current) != m_parent_map1.end()) {
// Here, update to the shortest path is possible.
const size_t tmp = m_distance_map1[p_current] +
m_distance_map2[p_current];
if (best_cost > tmp) {
// Update the current best touch node.
best_cost = tmp;
p_touch = p_current;
}
}
m_queue2.pop_front();
// Expand the backward search.
for (auto p_parent : p_current->parents()) {
if (m_parent_map2.find(p_parent) == m_parent_map2.end()) {
m_parent_map2.insert({p_parent, p_current});
m_distance_map2.insert({
p_parent,
m_distance_map2[p_current] + 1
});
m_queue2.push_back(p_parent);
}
}
}
}
return std::unique_ptr<PathContainer>(nullptr);nullptr;
}
private:
std::deque<DirectedGraphNode*> m_queue1;
std::deque<DirectedGraphNode*> m_queue2;
std::unordered_map<DirectedGraphNode*,
DirectedGraphNode*> m_parent_map1;
std::unordered_map<DirectedGraphNode*,
DirectedGraphNode*> m_parent_map2;
std::unordered_map<DirectedGraphNode*,
size_t> m_distance_map1;
std::unordered_map<DirectedGraphNode*,
size_t> m_distance_map2;
};
#endif // BIBFS_PATH_FINDER_H