Skip to main content
Rollback to Revision 3
Source Link
Jamal
  • 35.2k
  • 13
  • 134
  • 238

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 = &target;

        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 

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(std::string &&name);
    void add_child(DirectedGraphNode& child);
    bool has_child(DirectedGraphNode& query);
    void remove_child(DirectedGraphNode& child);

    // Child iterators.
    typename VecContainer::const_iterator begin();
    /* typename */ VecContainer::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);
        VecContainer::const_iterator begin();
        VecContainer::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.
    VecContainer m_in_vec;
    VecContainer m_out_vec;
};

#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(std::string &&name) : 
m_name(std::move(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_set.insert(&child);
        child.m_in_set.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_set.find(&query) == m_out_set.end()) {
        if (query.m_in_set.find(this) != query.m_in_set.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_set.find(this) == query.m_in_set.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_set.erase(&child);
    child.m_in_set.erase(this);

    typename VecContainer::const_iterator it = m_out_vec.begin();

    while (*it != &child) {
        it++;
    }

    m_out_vec.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 == other.m_name;
}

/*******************************************************************************
* Returns a const iterator to a child node of this node.                       *
*******************************************************************************/
DirectedGraphNode::VecContainer::const_iterator DirectedGraphNode::begin() {
    return m_out_vec.begin();
}

/*******************************************************************************
* Returns a const iterator to the end of child list.                           *
*******************************************************************************/
DirectedGraphNode::VecContainer::const_iterator DirectedGraphNode::end() {
    return m_out_vec.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.          *
*******************************************************************************/
DirectedGraphNode::VecContainer::const_iterator 
DirectedGraphNode::ParentIteratorProxy::begin() {
    return mp_owner->m_in_vec.begin();
}

/*******************************************************************************
* Returns an iterator pointing to the end of owner node's parent list.         *
*******************************************************************************/
DirectedGraphNode::VecContainer::const_iterator
DirectedGraphNode::ParentIteratorProxy::end() {
    return mp_owner->m_in_vec.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*>;
using PathContainer = std::vector<DirectedGraphNode*>;

class PathFinder {
public:
 
    virtual ~PathFinder() = default;

    virtual std::unique_ptr<PathContainer> 
        search(DirectedGraphNode& source,
               DirectedGraphNode& target) = 0;

    // The path finders allocate using 'new' the structure describing the path,
    // and it is the responsibility of the caller to 'delete' it after use.
    std::unique_ptr<PathContainer> 
         construct_path(DirectedGraphNode& touch,
                       ParentMap& pmf,
                       ParentMap& pmb) {
        PathContainer* p_path = new PathContainer;
        DirectedGraphNode* u = &touch;

        while (u) {
            p_path->push_back(u);
            u = pmf[u];
        }

        std::reverse(p_path->begin(), p_path->end());
        u = pmb[&touch];

        while (u) {
            p_path->push_back(u);
            u = pmb[u];
        }

        return std::unique_ptr<PathContainer>(p_path);
    }

    std::unique_ptr<PathContainer> 
     construct_path(DirectedGraphNode& target, ParentMap& pm) {
        PathContainer* p_path = new PathContainer;
        DirectedGraphNode* u = &target;

        while (u) {
            p_path->push_back(u);
            u = pm[u];
        }
 
        std::reverse(p_path->begin(), p_path->end());
        return std::unique_ptr<PathContainer>(p_path);
    }
};

#endif // PATHFINDER_H
#ifndef BFS_PATH_FINDER_H
#define BFS_PATH_FINDER_H

#include <deque>
#include <memory>
#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> 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_current, m_parent_map);
            }

            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);
    }

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>
#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> 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_touch,
                                      m_parent_map1,
                                      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);
    }

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 
#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:
    DirectedGraphNode(const std::string name);
    void add_child(DirectedGraphNode& child);
    bool has_child(DirectedGraphNode& query);
    void remove_child(DirectedGraphNode& child);

    // Child iterators.
    std::unordered_set<DirectedGraphNode*>::const_iterator begin();
    std::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);
        std::unordered_set<DirectedGraphNode*>::const_iterator begin();
        std::unordered_set<DirectedGraphNode*>::const_iterator end();

    private:
        const DirectedGraphNode* mp_owner;
    };

private:
    std::string m_name;
    std::unordered_set<DirectedGraphNode*> m_in;
    std::unordered_set<DirectedGraphNode*> m_out;
};

#endif  // DIRECTED_GRAPH_NODE_H
#include <iostream>
#include <stdexcept>
#include <unordered_set>

#include "directed_graph_node.h"

/*******************************************************************************
* Constructs a new DirectedGraphNode with the given name.                      *
*******************************************************************************/
DirectedGraphNode::DirectedGraphNode(const std::string name) {
    m_name = name;
}

/*******************************************************************************
* Creates a directed edge from this node to node 'child'.                      *
*******************************************************************************/ 
void DirectedGraphNode::add_child(DirectedGraphNode& child) {
    m_out.insert(&child);
    child.m_in.insert(this);
}

/*******************************************************************************
* Queries whether there is an arc (this, query).                               *
*******************************************************************************/
bool DirectedGraphNode::has_child(DirectedGraphNode& query) {
    if (m_out.find(&query) == m_out.end()) {
        if (query.m_in.find(this) != query.m_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.find(this) == query.m_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.erase(&child);
    child.m_in.erase(this);
}

/*******************************************************************************
* Compares by name this node to 'other'.                                       *
*******************************************************************************/
bool DirectedGraphNode::operator ==(const DirectedGraphNode& other) const {
    return this->m_name.compare(other.m_name) == 0;
}

/*******************************************************************************
* Returns a const iterator to a child node of this node.                       *
*******************************************************************************/
std::unordered_set<DirectedGraphNode*>::const_iterator  
DirectedGraphNode::begin() {
    return m_out.begin();
}

/*******************************************************************************
* Returns a const iterator to the end of child list.                           *
*******************************************************************************/
std::unordered_set<DirectedGraphNode*>::const_iterator 
DirectedGraphNode::end() {
    return m_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.          *
*******************************************************************************/
std::unordered_set<DirectedGraphNode*>::const_iterator
DirectedGraphNode::ParentIteratorProxy::begin() {
    return mp_owner->m_in.begin();
}

/*******************************************************************************
* Returns an iterator pointing to the end of owner node's parent list.         *
*******************************************************************************/
std::unordered_set<DirectedGraphNode*>::const_iterator
DirectedGraphNode::ParentIteratorProxy::end() {
    return mp_owner->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 <unordered_map>
#include <vector>

#include "directed_graph_node.h"

using std::unordered_map;
using std::vector;

class PathFinder {
public:
    virtual std::vector<DirectedGraphNode*>* 
        search(DirectedGraphNode& source,
               DirectedGraphNode& target) = 0;

    vector<DirectedGraphNode*>*  
        construct_path(DirectedGraphNode* touch,
                       unordered_map<DirectedGraphNode*,
                                     DirectedGraphNode*>* p_parents_f,
                       unordered_map<DirectedGraphNode*,
                                     DirectedGraphNode*>* p_parents_b) {
        vector<DirectedGraphNode*>* p_path = new vector<DirectedGraphNode*>;
        DirectedGraphNode* u = const_cast<DirectedGraphNode*>(touch);

        while (u) {
            p_path->push_back(u);
            u = (*p_parents_f)[u];
        }

        std::reverse(p_path->begin(), p_path->end());

        if (p_parents_b) {
            u = (*p_parents_b)[touch];
            while (u) {
                p_path->push_back(u);
                u = (*p_parents_b)[u];
            }
        }

        return p_path;
    }
};

#endif // PATHFINDER_H
#ifndef BFS_PATH_FINDER_H
#define BFS_PATH_FINDER_H

#include <deque>
#include <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::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_current, &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 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 <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::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.
        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_touch,
                                      &m_parent_map1,
                                      &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 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 
Incorporated most of improvement suggestions.
Source Link
coderodde
  • 32.3k
  • 15
  • 79
  • 205

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 name&&name);
    void add_child(DirectedGraphNode& child);
    bool has_child(DirectedGraphNode& query);
    void remove_child(DirectedGraphNode& child);

    // Child iterators.
    std::unordered_set<DirectedGraphNode*>typename VecContainer::const_iterator begin();
    std::unordered_set<DirectedGraphNode*>/* typename */ VecContainer::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);
        std::unordered_set<DirectedGraphNode*>VecContainer::const_iterator begin();
        std::unordered_set<DirectedGraphNode*>VecContainer::const_iterator end();

    private:
        const DirectedGraphNode* mp_owner;
    };

private:
    // m_name specifies the identity of a node.
    std::string m_name; 

    std::unordered_set<DirectedGraphNode*>// m_in;Sets for fast arc queries.
    std::unordered_set<DirectedGraphNode*>SetContainer m_out;m_in_set;
    SetContainer m_out_set;

    // Vectors for faster iteration.
    VecContainer m_in_vec;
    VecContainer m_out_vec;
};

#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 name&&name) {
   :  
m_name =(std::move(name)) name;
{}

/*******************************************************************************
* Creates a directed edge from this node to node 'child'.                      *
*******************************************************************************/ 
void DirectedGraphNode::add_child(DirectedGraphNode& child) {
    m_outif (m_out_set.find(&child) == m_out_set.end()) {
        // 'child' is not yet in the set of children.
        m_out_set.insert(&child);
        child.m_inm_in_set.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_outm_out_set.find(&query) == m_outm_out_set.end()) {
        if (query.m_inm_in_set.find(this) != query.m_inm_in_set.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_inm_in_set.find(this) == query.m_inm_in_set.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_outm_out_set.erase(&child);
    child.m_inm_in_set.erase(this);

    typename VecContainer::const_iterator it = m_out_vec.begin();

    while (*it != &child) {
        it++;
    }

    m_out_vec.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) == 0;other.m_name;
}

/*******************************************************************************
* Returns a const iterator to a child node of this node.                       *
*******************************************************************************/
stdDirectedGraphNode::unordered_set<DirectedGraphNode*>VecContainer::const_iterator 
 DirectedGraphNode::begin() {
    return m_outm_out_vec.begin();
}

/*******************************************************************************
* Returns a const iterator to the end of child list.                           *
*******************************************************************************/
stdDirectedGraphNode::unordered_set<DirectedGraphNode*>VecContainer::const_iterator
  DirectedGraphNode::end() {
    return m_outm_out_vec.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.          *
*******************************************************************************/
stdDirectedGraphNode::unordered_set<DirectedGraphNode*>VecContainer::const_iterator 
DirectedGraphNode::ParentIteratorProxy::begin() {
    return mp_owner->m_in>m_in_vec.begin();
}

/*******************************************************************************
* Returns an iterator pointing to the end of owner node's parent list.         *
*******************************************************************************/
stdDirectedGraphNode::unordered_set<DirectedGraphNode*>VecContainer::const_iterator
DirectedGraphNode::ParentIteratorProxy::end() {
    return mp_owner->m_in>m_in_vec.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;unordered_map<DirectedGraphNode*, DirectedGraphNode*>;
using PathContainer = std::vector;vector<DirectedGraphNode*>;

class PathFinder {
public: 

    virtual ~PathFinder() = default;

    virtual std::vector<DirectedGraphNode*>*unique_ptr<PathContainer> 
        search(DirectedGraphNode& source,
               DirectedGraphNode& target) = 0;

    vector<DirectedGraphNode*>*// 
 The path finders allocate using 'new' the structure construct_path(DirectedGraphNode*describing touchthe path,
    // and it is the responsibility of the caller to 'delete' it after use.
    std::unique_ptr<PathContainer>  
        construct_path(DirectedGraphNode& unordered_map<DirectedGraphNode*touch,
                       ParentMap& pmf,
             DirectedGraphNode*>* p_parents_f,
         ParentMap& pmb) {
        PathContainer* p_path = new unordered_map<DirectedGraphNode*,PathContainer;
        DirectedGraphNode* u = &touch;

        while (u) {
            p_path->push_back(u);
    DirectedGraphNode*>* p_parents_b) {
      u = vector<DirectedGraphNode*>*pmf[u];
 p_path = new vector<DirectedGraphNode*>;
    }

      DirectedGraphNode* u =std::reverse(p_path->begin(), const_cast<DirectedGraphNode*>p_path->end(touch));
        u = pmb[&touch];

        while (u) {
            p_path->push_back(u);
            u = (*p_parents_f)[u];pmb[u];
        }

        return std::reverse(p_path->beginunique_ptr<PathContainer>(), p_path->end());
    }

    std::unique_ptr<PathContainer>  
   if construct_path(p_parents_bDirectedGraphNode& target, ParentMap& pm) {
        PathContainer* p_path = new uPathContainer;
 = (*p_parents_b)[touch];
      DirectedGraphNode* u = &target;

        while (u) {
                p_path->push_back(u);
                u = (*p_parents_b)[u];pm[u];
            } 

        }
std::reverse(p_path->begin(), p_path->end());
        return p_path;std::unique_ptr<PathContainer>(p_path);
    }
};

#endif // PATHFINDER_H
#ifndef BFS_PATH_FINDER_H
#define BFS_PATH_FINDER_H

#include <deque>
#include <iostream>
#include <vector><memory>
#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::vector<DirectedGraphNode*>*unique_ptr<PathContainer> 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_current, &m_parent_map*p_current, nullptrm_parent_map);
            }

            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 nullptr;std::unique_ptr<PathContainer>(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 <iostream>
#include <vector><memory>
#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::vector<DirectedGraphNode*>*unique_ptr<PathContainer> 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_touch*p_touch,
                                      &m_parent_map1m_parent_map1,
                                      &m_parent_map2m_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 nullptr;std::unique_ptr<PathContainer>(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 
#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:
    DirectedGraphNode(const std::string name);
    void add_child(DirectedGraphNode& child);
    bool has_child(DirectedGraphNode& query);
    void remove_child(DirectedGraphNode& child);

    // Child iterators.
    std::unordered_set<DirectedGraphNode*>::const_iterator begin();
    std::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);
        std::unordered_set<DirectedGraphNode*>::const_iterator begin();
        std::unordered_set<DirectedGraphNode*>::const_iterator end();

    private:
        const DirectedGraphNode* mp_owner;
    };

private:
    std::string m_name;
    std::unordered_set<DirectedGraphNode*> m_in;
    std::unordered_set<DirectedGraphNode*> m_out;
};

#endif  // DIRECTED_GRAPH_NODE_H
#include <iostream>
#include <stdexcept>
#include <unordered_set>

#include "directed_graph_node.h"

/*******************************************************************************
* Constructs a new DirectedGraphNode with the given name.                      *
*******************************************************************************/
DirectedGraphNode::DirectedGraphNode(const std::string name) {
    m_name = name;
}

/*******************************************************************************
* Creates a directed edge from this node to node 'child'.                      *
*******************************************************************************/ 
void DirectedGraphNode::add_child(DirectedGraphNode& child) {
    m_out.insert(&child);
    child.m_in.insert(this);
}

/*******************************************************************************
* Queries whether there is an arc (this, query).                               *
*******************************************************************************/
bool DirectedGraphNode::has_child(DirectedGraphNode& query) {
    if (m_out.find(&query) == m_out.end()) {
        if (query.m_in.find(this) != query.m_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.find(this) == query.m_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.erase(&child);
    child.m_in.erase(this);
}

/*******************************************************************************
* Compares by name this node to 'other'.                                       *
*******************************************************************************/
bool DirectedGraphNode::operator ==(const DirectedGraphNode& other) const {
    return this->m_name.compare(other.m_name) == 0;
}

/*******************************************************************************
* Returns a const iterator to a child node of this node.                       *
*******************************************************************************/
std::unordered_set<DirectedGraphNode*>::const_iterator 
 DirectedGraphNode::begin() {
    return m_out.begin();
}

/*******************************************************************************
* Returns a const iterator to the end of child list.                           *
*******************************************************************************/
std::unordered_set<DirectedGraphNode*>::const_iterator
 DirectedGraphNode::end() {
    return m_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.          *
*******************************************************************************/
std::unordered_set<DirectedGraphNode*>::const_iterator
DirectedGraphNode::ParentIteratorProxy::begin() {
    return mp_owner->m_in.begin();
}

/*******************************************************************************
* Returns an iterator pointing to the end of owner node's parent list.         *
*******************************************************************************/
std::unordered_set<DirectedGraphNode*>::const_iterator
DirectedGraphNode::ParentIteratorProxy::end() {
    return mp_owner->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 <unordered_map>
#include <vector>

#include "directed_graph_node.h"

using std::unordered_map;
using std::vector;

class PathFinder {
public:
    virtual std::vector<DirectedGraphNode*>* 
        search(DirectedGraphNode& source,
               DirectedGraphNode& target) = 0;

    vector<DirectedGraphNode*>* 
         construct_path(DirectedGraphNode* touch,
                       unordered_map<DirectedGraphNode*,
                                     DirectedGraphNode*>* p_parents_f,
                       unordered_map<DirectedGraphNode*,
                                     DirectedGraphNode*>* p_parents_b) {
        vector<DirectedGraphNode*>* p_path = new vector<DirectedGraphNode*>;
        DirectedGraphNode* u = const_cast<DirectedGraphNode*>(touch);

        while (u) {
            p_path->push_back(u);
            u = (*p_parents_f)[u];
        }

        std::reverse(p_path->begin(), p_path->end());

        if (p_parents_b) {
            u = (*p_parents_b)[touch];
            while (u) {
                p_path->push_back(u);
                u = (*p_parents_b)[u];
            }
        }

        return p_path;
    }
};

#endif // PATHFINDER_H
#ifndef BFS_PATH_FINDER_H
#define BFS_PATH_FINDER_H

#include <deque>
#include <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::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_current, &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 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 <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::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.
        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_touch,
                                      &m_parent_map1,
                                      &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 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 

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(std::string &&name);
    void add_child(DirectedGraphNode& child);
    bool has_child(DirectedGraphNode& query);
    void remove_child(DirectedGraphNode& child);

    // Child iterators.
    typename VecContainer::const_iterator begin();
    /* typename */ VecContainer::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);
        VecContainer::const_iterator begin();
        VecContainer::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.
    VecContainer m_in_vec;
    VecContainer m_out_vec;
};

#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(std::string &&name) :  
m_name(std::move(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_set.insert(&child);
        child.m_in_set.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_set.find(&query) == m_out_set.end()) {
        if (query.m_in_set.find(this) != query.m_in_set.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_set.find(this) == query.m_in_set.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_set.erase(&child);
    child.m_in_set.erase(this);

    typename VecContainer::const_iterator it = m_out_vec.begin();

    while (*it != &child) {
        it++;
    }

    m_out_vec.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 == other.m_name;
}

/*******************************************************************************
* Returns a const iterator to a child node of this node.                       *
*******************************************************************************/
DirectedGraphNode::VecContainer::const_iterator DirectedGraphNode::begin() {
    return m_out_vec.begin();
}

/*******************************************************************************
* Returns a const iterator to the end of child list.                           *
*******************************************************************************/
DirectedGraphNode::VecContainer::const_iterator DirectedGraphNode::end() {
    return m_out_vec.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.          *
*******************************************************************************/
DirectedGraphNode::VecContainer::const_iterator 
DirectedGraphNode::ParentIteratorProxy::begin() {
    return mp_owner->m_in_vec.begin();
}

/*******************************************************************************
* Returns an iterator pointing to the end of owner node's parent list.         *
*******************************************************************************/
DirectedGraphNode::VecContainer::const_iterator
DirectedGraphNode::ParentIteratorProxy::end() {
    return mp_owner->m_in_vec.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*>;
using PathContainer = std::vector<DirectedGraphNode*>;

class PathFinder {
public: 

    virtual ~PathFinder() = default;

    virtual std::unique_ptr<PathContainer> 
        search(DirectedGraphNode& source,
               DirectedGraphNode& target) = 0;

    // The path finders allocate using 'new' the structure describing the path,
    // and it is the responsibility of the caller to 'delete' it after use.
    std::unique_ptr<PathContainer>  
        construct_path(DirectedGraphNode& touch,
                       ParentMap& pmf,
                       ParentMap& pmb) {
        PathContainer* p_path = new PathContainer;
        DirectedGraphNode* u = &touch;

        while (u) {
            p_path->push_back(u);
            u = pmf[u];
        }

        std::reverse(p_path->begin(), p_path->end());
        u = pmb[&touch];

        while (u) {
            p_path->push_back(u);
            u = pmb[u];
        }

        return std::unique_ptr<PathContainer>(p_path);
    }

    std::unique_ptr<PathContainer>  
    construct_path(DirectedGraphNode& target, ParentMap& pm) {
        PathContainer* p_path = new PathContainer;
        DirectedGraphNode* u = &target;

        while (u) {
            p_path->push_back(u);
            u = pm[u];
        } 

        std::reverse(p_path->begin(), p_path->end());
        return std::unique_ptr<PathContainer>(p_path);
    }
};

#endif // PATHFINDER_H
#ifndef BFS_PATH_FINDER_H
#define BFS_PATH_FINDER_H

#include <deque>
#include <memory>
#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> 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_current, m_parent_map);
            }

            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);
    }

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>
#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> 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_touch,
                                      m_parent_map1,
                                      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);
    }

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 
edited title
Link
200_success
  • 145.7k
  • 22
  • 191
  • 481

Is this an adult way of designing an algorithm framework in C++ for finding Finding shortest paths in digraphs?directed graphs

Tweeted twitter.com/#!/StackCodeReview/status/534858572908486656
Added two more tags.
Link
coderodde
  • 32.3k
  • 15
  • 79
  • 205
Loading
Source Link
coderodde
  • 32.3k
  • 15
  • 79
  • 205
Loading