Take the 2-minute tour ×
Code Review Stack Exchange is a question and answer site for peer programmer code reviews. It's 100% free, no registration required.

(This is a follow on to this question).

Having given an implementation for some of the graph structures, here is a portion of the algorithms that are defined to work over them:

graph_degree.hpp

/*! \file graph_degree.hpp
 *  \brief Algorithms relating to node degree.
 *
 *  Defines core algorithms to investigate indegree and 
 *  outdegrees concerning a given graph. This includes finding minimum, 
 *  maximum, average, and the distribution over a whole graph. It also has 
 *  algorithms specifically for finding disonnected nodes (defined as nodes
 *  with 0 indegree, that is, inaccessible nodes), and sink nodes (defined as
 *  nodes with 0 outdegree, that is, nodes one cannot leave). 
 *
 *  \addtogroup graph_algorithm
 *  @{
*/

#ifndef GRAPH_DEGREE_SGL_HPP_
#define GRAPH_DEGREE_SGL_HPP_

#include <functional>
#include <limits>
#include <map>
#include <utility>
#include <vector>

#include "boost/optional.hpp"

namespace simplegl
{
namespace graph
{

namespace
{

//Since max and min outdegree differ only by the comparator they use,
//and indegree and outdegree differ only by functon call,
//this is the core functionality abstracted out.
template <typename Graph, typename Compare, typename Selector>
boost::optional<
    std::pair<typename Graph::node_type, typename Graph::size_type>
>
find_degree(const Graph& g, Compare comparator, Selector degree_type)
{
    using node_type = typename Graph::node_type;
    using optional_t = 
        boost::optional<
            std::pair<typename Graph::node_type, typename Graph::size_type>
        >;

    if(g.empty()) { 
        return optional_t();
    }

    auto t = g.begin()->first;
    auto current = degree_type(t);
    auto degree = current;

    auto begin = g.begin();
    std::advance(begin, 1);

    for(auto it = begin; it != g.end(); ++it) {
        current = degree_type(it->first);
        if(comparator(current, degree)) {
            degree = current;
            t = it->first;
        }
    }

    return optional_t(std::make_pair(t, degree));
}

template <typename Graph>
using optional_t = 
    boost::optional<
        std::pair<typename Graph::node_type, typename Graph::size_type>
    >;

template <typename Graph, typename Compare>
optional_t<Graph> find_outdegree(const Graph& g, Compare comparator)
{
    using node_type = typename Graph::node_type;
    auto select_type = [&](const node_type& n) { return g.outdegree(n); };
    return find_degree(g, comparator, select_type); 
}

template <typename Graph, typename Compare>
optional_t<Graph> find_indegree(const Graph& g, Compare comparator)
{
    using node_type = typename Graph::node_type;
    auto select_type = [&](const node_type& n) { return g.indegree(n); };
    return find_degree(g, comparator, select_type); 
}

} // end unnamed namespace

/*! \brief Finds the node with maximum outdegree in the Graph g.
 *  
 *  \param g The graph to search over.
 *  \return A std::pair containing the node with maximum outdegree as the first member,
 *          and the degree count as the second member. For graphs with many nodes of equal
 *          maximum degree, it is implementation dependent as to which node is chosen.
*/
template <typename Graph>
optional_t<Graph> max_outdegree(const Graph& g)
{
    using size_type = typename Graph::size_type;
    return find_outdegree(g, std::greater<size_type>());
}

/*! \brief Finds the node with minimum outdegree in the Graph g.
 *  
 *  \param g The graph to search over.
 *  \return A std::pair containing the node with minimum outdegree as the first member,
 *          and the degree count as the second member. For graphs with many nodes of equal
 *          minimum degree, it is implementation dependent as to which node is chosen.
*/
template <typename Graph>
optional_t<Graph> min_outdegree(const Graph& g)
{
    using size_type = typename Graph::size_type;
    return find_outdegree(g, std::less<size_type>());
}

/*! \brief Finds the node with maximum indegree in the Graph g.
 *  
 *  \param g The graph to search over.
 *  \return A std::pair containing the node with maximum indegree as the first member,
 *          and the degree count as the second member. For graphs with many nodes of equal
 *          maximum degree, it is implementation dependent as to which node is chosen.
*/
template <typename Graph>
optional_t<Graph> max_indegree(const Graph& g)
{
    using size_type = typename Graph::size_type;
    return find_indegree(g, std::greater<size_type>());
}

/*! \brief Finds the node with minimum indegree in the Graph g.
 *  
 *  \param g The graph to search over.
 *  \return A std::pair containing the node with minimum indegree as the first member,
 *          and the degree count as the second member. For graphs with many nodes of equal
 *          minimum degree, it is implementation dependent as to which node is chosen.
*/
template <typename Graph>
optional_t<Graph> min_indegree(const Graph& g)
{
    using size_type = typename Graph::size_type;
    return find_indegree(g, std::less<size_type>());
}

/*! \brief Returns the average indegree of all nodes in the graph.
 *  
 *  \param g The graph to search over.
 *  \return A double, represending the average indegree of <B>g</B>.
*/
template <typename Graph>
double average_indegree(const Graph& g)
{
    double start = 0;
    typename Graph::size_type total_nodes = 0;

    for(auto it = g.begin(); it != g.end(); ++it) {
        start += g.indegree(it->first);
        ++total_nodes;
    }

    return start/total_nodes;
}

/*! \brief Returns the average outdegree of all nodes in the graph.
 *  
 *  \param g The graph to search over.
 *  \return A double, represending the average outdegree of <B>g</B>.
*/
template <typename Graph>
double average_outdegree(const Graph& g)
{
    double start = 0;
    typename Graph::size_type total_nodes = 0;

    for(auto it = g.begin(); it != g.end(); ++it) {
        start += g.indegree(it->first);
        ++total_nodes;
    }

    return start/total_nodes;
}

/*! \brief Returns the distribution of outdegrees over of all nodes in the graph.
 *  
 *  \param g The graph to search over.
 *  \return A std::map<typename Graph::size_type, typename Graph::size_type>
 *          containing pairs of type <I>{outdegree : count of nodes with given outdegree}</I>.
*/
template <typename Graph>
std::map<typename Graph::size_type, typename Graph::size_type>
outdegree_distribution(const Graph& g)
{
    std::map<typename Graph::size_type, typename Graph::size_type> mp;
    for(auto it = g.begin(); it != g.end(); ++it) {
        mp[g.outdegree(it->first)] += 1;
    }
    return mp;
}

/*! \brief Returns the distribution of indegrees over of all nodes in the graph.
 *  
 *  \param g The graph to search over.
 *  \return A std::map<typename Graph::size_type, typename Graph::size_type>
 *          containing pairs of type <I>{indegree : count of nodes with given indegree}</I>.
*/
template <typename Graph>
std::map<typename Graph::size_type, typename Graph::size_type>
indegree_distribution(const Graph& g)
{
    std::map<typename Graph::size_type, typename Graph::size_type> mp;
    for(auto it = g.begin(); it != g.end(); ++it) {
        mp[g.indegree(it->first)] += 1;
    }
    return mp;
}

/*! \brief Finds all disconnected nodes of the given graph, inserting them into ii.
 *  
 *  \param g The graph to search over.
 *  \param ii An input iterator into a container that will hold the nodes that are
              found to be disconnected.
 *  \return The number of disconnected nodes found.
*/ 
template <typename Graph, typename Iterator>
std::size_t disconnected_nodes(const Graph& g, Iterator ii)
{
    std::size_t num_disconnected = 0;
    for(auto it = g.begin(); it != g.end(); ++it) {
        if(g.indegree(it->first) == 0) {
            ii = it->first;
            ++num_disconnected;
        }
    }
    return num_disconnected;
}

/*! \brief Finds all sink nodes (nodes with outdegree 0) of the given graph, 
 *         inserting them into ii.
 *  
 *  \param g The graph to search over.
 *  \param ii An input iterator into a container that will hold the nodes that are
              found to be sinks.
 *  \return The number of sink nodes found.
*/ 
template <typename Graph, typename Iterator>
std::size_t sink_nodes(const Graph& g, Iterator ii)
{
    std::size_t num_sink = 0;
    for(auto it = g.begin(); it != g.end(); ++it) {
        if(g.outdegree(it->first) == 0) {
            ii = it->first;
            ++num_sink;
        }
    }
    return num_sink;
}

} //end namespace graph
} //end namespace simplegl

#endif //GRAPH_DEGREE_SGL_HPP_

/*! @} End of Doxygen Group graph_algorithm */

graph_search.hpp

/*! \file graph_search.hpp
 *  \brief Algorithms for searching over graphs and finding connected components.
 *
 *  Algorithms to investigate graph connectedness. Contains basic breadth-first 
 *  and depth-first search functionality. In addition, contains tests for
 *  connectedness, finding connected components, and determining whether or
 *  not a graph contains a cycle from a given source node.
 *
 *  \addtogroup graph_algorithm
 *  @{
*/

#ifndef GRAPH_SEARCH_SGL_HPP_
#define GRAPH_SEARCH_SGL_HPP_

#include <algorithm>
#include <queue>
#include <set>
#include <stack>

namespace simplegl
{
namespace graph
{

namespace
{
//There are obvious similarities between breadth first and depth first
//searches; in fact, the algorithms are almost identical. To take advantage
//of this, we just need to wrap queue and stack with a begin() function
//which will return a reference to the first element in each. This is required
//because they use different member functions to access the first element,
//that is, front() vs top(). 

template <typename Type>
Type& begin(std::queue<Type>& q)
{
    return q.front();
}

template <typename Type>
Type& begin(std::stack<Type>& s)
{
    return s.top();
}

//Base search, which is used by both DFS and BFS
template <typename Graph, typename Iterator, typename Container>
void search_base
(const Graph& g, const typename Graph::node_type& root, Iterator ii, Container& cont)
{
    using node_type = typename Graph::node_type;
    using adj_iter  = typename Graph::const_adjacency_iterator;

    std::set<node_type> examined_nodes;

    cont.push(root);
    examined_nodes.insert(root);
    ii = root;

    while(!cont.empty()) {
        node_type current_node = begin(cont);
        cont.pop();
        auto adj_nodes = g.adjacent_nodes(current_node);
        for(auto it = adj_nodes.first; it != adj_nodes.second; ++it) {
            if(examined_nodes.find(*it) == examined_nodes.end()) {
                ii = *it;
                cont.push(*it);
                examined_nodes.insert(*it);
            }
        }
    }
}

} // end unnamed namespace

/*! \brief A standard breadth first search. 
 *  
 *  \param g A graph to search over.
 *  \param root The root node from which to begin the search.
 *  \param ii An insert iterator ii into a container, which will hold all reachable nodes
 *            from <B>root</B>.
 *
*/
template <typename Graph, typename Iterator>
void 
breadth_first_search(const Graph& g, const typename Graph::node_type& root, Iterator ii)
{
    std::queue<typename Graph::node_type> q;
    search_base(g, root, ii, q);
}

/*! \brief A standard depth first search. 
 *  
 *  \param g A graph to search over.
 *  \param root The root node from which to begin the search.
 *  \param ii An insert iterator ii into a container, which will hold all reachable nodes
 *            from <B>root</B>.
 *
*/
template <typename Graph, typename Iterator>
void depth_first_search(const Graph& g, const typename Graph::node_type& root, Iterator ii)
{
    std::stack<typename Graph::node_type> s;
    search_base(g, root, ii, s);
}

/*! \brief Tests two nodes to see if there is a path between them in the given graph. 
 *  
 *  \param g A graph to search over.
 *  \param from The node from which to begin the search.
 *  \param to The node to find a path to.
 *  
 *  \return true if there is a path connecting (from, to), false otherwise.
*/
template <typename Graph>
bool is_connected(const Graph& g, const typename Graph::node_type& from, 
                  const typename Graph::node_type& to)
{
    //Pathological case, if they're the same node, then they must
    //be connected.
    using key_less = typename Graph::key_compare;
    auto comp = key_less();
    if(!(comp(from, to) && !(comp(to, from)))) {
        return true;
    }

    //Otherwise, utilize a BFS. 
    using node_type = typename Graph::node_type;
    using adj_iter  = typename Graph::const_adjacency_iterator;

    std::queue<node_type> to_examine;
    std::set<node_type> examined_nodes;

    to_examine.push(from);
    examined_nodes.insert(from);

    while(!to_examine.empty()) {
        node_type current_node = to_examine.front();
        to_examine.pop();
        auto adj_nodes = g.adjacent_nodes(current_node);
        for(auto it = adj_nodes.first; it != adj_nodes.second; ++it) {
            if(examined_nodes.find(*it) == examined_nodes.end()) {
                if(*it == to) {
                    return true;
                }
                to_examine.push(*it);
                examined_nodes.insert(*it);
            }
        }
    }
    return false;
}

/*! \brief Tests to see if there is a (non-trivial) cyclic path from a given node. 
 *  
 *  \param g A graph to search over.
 *  \param node The node from which to begin the search.
 *  
 *  \return true if there is a cyclic path from <B>node</B> to itself, false otherwise.
*/
template <typename Graph>
bool is_cyclical(const Graph& g, const typename Graph::node_type& node)
{   
    //Utilize a BFS to see if we can get from node back to node
    using node_type = typename Graph::node_type;
    using adj_iter  = typename Graph::const_adjacency_iterator;

    std::queue<node_type> to_examine;
    std::set<node_type> examined_nodes;

    to_examine.push(node);

    while(!to_examine.empty()) {
        node_type current_node = to_examine.front();
        to_examine.pop();
        auto adj_nodes = g.adjacent_nodes(current_node);
        for(auto it = adj_nodes.first; it != adj_nodes.second; ++it) {
            if(examined_nodes.find(*it) == examined_nodes.end()) {
                if(*it == node) {
                    return true;
                }
                to_examine.push(*it);
                examined_nodes.insert(*it);
            }
        }
    }
    return false;
}

/*! \brief Finds all nodes reachable from a given node. Equivalent to running a depth
 *         or bredth-first search on that node. 
 *  
 *  \param g A graph to search over.
 *  \param node The node from which to begin the search.
 *  
 *  \return A std::set containing all the nodes reachable from <B>node</B>.
*/
template <typename Graph>
std::set<typename Graph::node_type> 
connected_component(const Graph& g, const typename Graph::node_type& node)
{
    using node_type = typename Graph::node_type;
    std::set<node_type> connected;
    std::insert_iterator<std::set<node_type>> ins_it(connected, connected.begin());
    depth_first_search(g, node, ins_it);
    return connected;
}

/*! \brief Finds all connected components of a given graph.
 *  
 *  Connected components partition the graph into equivalence classes based on
 *  reachability. That is, any node in a given connected component is reachable
 *  by any other node in the same connected component, and any node not in that same
 *  connected component is not reachable. 
 *   
 *  \param g A graph to search over.
 *  
 *  \return A std::set of std::sets, each consisting of one connected component.
*/
template <typename Graph>
std::set<std::set<typename Graph::node_type>> 
all_connected_components(const Graph& g)
{
    using node_type = typename Graph::node_type;
    std::set<std::set<node_type>> cc;
    bool found = false;

    for(auto it = g.begin(); it != g.end(); ++it) {
        for(auto cc_subset = cc.begin(); cc_subset != cc.end(); ++cc_subset) {
            if(cc_subset->find(it->first) != cc_subset->end()) {
                found = true;
                break;
            }
        }
        if(!found) {
            cc.insert(connected_component(g,it->first));
        }
        found = false;
    }
    return cc;
}

/*! \brief Determines if the graph is fully connected.
 *  
 *  A fully connected graph is one in which there is a path from any node
 *  to any other node.
 *   
 *  \param g A graph to search over.
 *  
 *  \return true if the graph is full connected, false otherwise.
*/
template <typename Graph>
bool is_fully_connected(const Graph& g)
{
    auto s = all_connected_components(g);
    return s.size() == 1;
}

} //end namespace graph
} //end namespace simplegl

#endif //GRAPH_SEARCH_SGL_HPP_

/*! @} End of Doxygen Group graph_algorithm */

topological_sort.hpp

#ifndef TOPOLOGICAL_SORT_SGL_HPP_
#define TOPOLOGICAL_SORT_SGL_HPP_

#include <deque>
#include <iostream>

#include "graph/graph_traits.hpp"

namespace simplegl
{
namespace graph
{

// Returns true iff the given graph can be topologically sorted,
// false otherwise.
template <typename Graph>
bool is_topologically_sortable(const Graph& g)
{
    using node_type = typename Graph::node_type;

    if(!is_directed<Graph>::value) { return false; }

    Graph possible_dag(g); 
    std::deque<node_type> zero_degree;

    for(auto it = possible_dag.begin(); it != possible_dag.end(); ++it) {
        if(possible_dag.indegree(it->first) == 0) {
            zero_degree.push_back(it->first);
        }
    }

    while(!zero_degree.empty()) {
        node_type& w = zero_degree.front();
        auto adjacent = possible_dag.adjacent_nodes(w);
        for(auto it = adjacent.first; it != adjacent.second; ++it) {
            possible_dag.remove_edge(w, *it);
            if(possible_dag.indegree(*it) == 0) {
                zero_degree.push_back(*it);
            }
        }
        zero_degree.pop_front();
    }

    return possible_dag.edge_size() == 0;
}

// Topologically sorts the given graph, if possible.
// Returns a std::deque containing the topological sorting, if 
// it exists, or an empty deque otherwise.
template <typename Graph>
std::deque<typename Graph::node_type> topologically_sort(const Graph& g)
{
    using node_type = typename Graph::node_type;

    if(!is_directed<Graph>::value) { return std::deque<node_type>(); }

    Graph possible_dag(g); 
    std::deque<node_type> zero_degree;
    std::deque<node_type> sorted;

    for(auto it = possible_dag.begin(); it != possible_dag.end(); ++it) {
        if(possible_dag.indegree(it->first) == 0) {
            zero_degree.push_back(it->first);
        }
    }

    while(!zero_degree.empty()) {
        node_type& w = zero_degree.front();
        sorted.push_back(w);
        auto adjacent = possible_dag.adjacent_nodes(w);
        for(auto it = adjacent.first; it != adjacent.second; ++it) {
            possible_dag.remove_edge(w, *it);
            if(possible_dag.indegree(*it) == 0) {
                zero_degree.push_back(*it);
            }
        }
        zero_degree.pop_front();
    }

    if(possible_dag.edge_size() == 0) {
        return sorted;
    }

    return std::deque<node_type>();
}

} // end namespace graph
} // end namespace simplegl

#endif // TOPOLOGICAL_SORT_SGL_HPP_
share|improve this question

1 Answer 1

up vote 3 down vote accepted

There are several places where you could improve the readability of your code:

  • First of all, you also use iterator-based for loops while you could use range-based for loop instead. Consider this function:

    template <typename Graph>
    double average_indegree(const Graph& g)
    {
        double start = 0;
        typename Graph::size_type total_nodes = 0;
    
        for(auto it = g.begin(); it != g.end(); ++it) {
            start += g.indegree(it->first);
            ++total_nodes;
        }
    
        return start/total_nodes;
    }
    

    With a range-based for loop, it becomes:

    template <typename Graph>
    double average_indegree(const Graph& g)
    {
        double start = 0;
        typename Graph::size_type total_nodes = 0;
    
        for(auto& elem: g) {
            start += g.indegree(elem.first);
            ++total_nodes;
        }
    
        return start/total_nodes;
    }
    
  • You can simplify some return statements thanks to list initialization by not typing the return type a second time. You can rewrite find_degree this way:

    template <typename Graph, typename Compare, typename Selector>
    boost::optional<
        std::pair<typename Graph::node_type, typename Graph::size_type>
    >
    find_degree(const Graph& g, Compare comparator, Selector degree_type)
    {
        using node_type = typename Graph::node_type;
    
        if(g.empty()) { 
            return {};
        }
    
        auto t = g.begin()->first;
        auto current = degree_type(t);
        auto degree = current;
    
        auto begin = g.begin();
        std::advance(begin, 1);
    
        for(auto it = begin; it != g.end(); ++it) {
            current = degree_type(it->first);
            if(comparator(current, degree)) {
                degree = current;
                t = it->first;
            }
        }
    
        return { std::make_pair(t, degree) };
    }
    
  • Instead of writing = 0 to zero-initialize an "unknown" type, you can use the new syntax for zero initialization (4th syntax in the given link):

    template <typename Graph>
    double average_indegree(const Graph& g)
    {
        double start = 0.0;
        typename Graph::size_type total_nodes{};
    
        for(auto& elem: g) {
            start += g.indegree(elem.first);
            ++total_nodes;
        }
    
        return start/total_nodes;
    }
    

You can also give more power to the user:

  • In average_indegree, you fixed the return type to double. You cuold have your function take a ReturnType template parameter defaulted to double instead:

    template <typename Graph
              typename ReturnType = double>
    ReturnType average_indegree(const Graph& g)
    {
        ReturnType start{};
        typename Graph::size_type total_nodes{};
    
        for(auto& elem: g) {
            start += g.indegree(elem.first);
            ++total_nodes;
        }
    
        return start/total_nodes;
    }
    

    Note that if you choose an int type smaller than Graph::size_type, the result type may be too small to contain the actual result. You could change the return type to typename std::common_type<ReturnType, typename Graph::size_type>::type to ensure that a big enough return type will be picked.

I still have some remarks about the names of your functions:

  • I am not very fond of the function begin to take the first element of a collection. begin is already widely used in the standard library to take an iterator to the first element. While the function in itself may be fine, I would change its name to first or something akin. That would also prevent potential name clashes with std::begin if somebody likes to use some bad old using namespace in their code.
share|improve this answer
    
Thanks, all good feedback. –  Yuushi Jul 1 at 23:28

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.