-
Notifications
You must be signed in to change notification settings - Fork 64
overview
From a very high level, the project is structured in two parts:
- The graph classes and core data structures
- Algorithms and additional functionality
The core data structures are designed to store the graph data without being concerned with any business logic. All additional functionality is built on top of these core classes on a higher abstraction level.
The main class of the library is the graph class. This class handles directedness of graphs (undirected vs. directed)
and weightedness of the edges. Each graph is an instance of the graph class:
enum class graph_type { DIRECTED, UNDIRECTED };
template <typename VERTEX_T, typename EDGE_T, graph_type GRAPH_TYPE_V>
class graph {...};The template parameter GRAPH_TYPE_V indicates whether the graph is directed or undirected. This cannot be dynamically
changed after construction of the graph. A graph can have arbitrary user provided types for the vertices and edges,
as indicated by the VERTEX_T and EDGE_T template parameters.
Internally, the graph is stored as an adjacency list. Separate containers hold the values for vertices and edges.
// N.B. These types are a bit more abstracted in the codebase behind using
// declarations, but for clarity I have left this out.
// Adjacency information is stored in a set for fast existence checks and fast removal
std::unordered_map<vertex_id_t, std::unordered_set<vertex_id_t>> adjacency_list_{};
// Storing vertex and edge values in a separate container has the advantage that
// vertices and edges are only in memory once
std::unordered_map<vertex_id_t, VERTEX_T> vertices_{};
std::unordered_map<edge_id_t, EDGE_T> edges_{};Two using declarations are provided to make it easier to instantiate a directed or undirected graph:
template <typename VERTEX_T, typename EDGE_T>
using directed_graph = graph<VERTEX_T, EDGE_T, graph_type::DIRECTED>;
template <typename VERTEX_T, typename EDGE_T>
using undirected_graph = graph<VERTEX_T, EDGE_T, graph_type::UNDIRECTED>;Certain algorithms (such as A*) operate on weighted graphs. Since the edge type can be any user provided type, we need to be careful in defining what a weighted graph is. Therefore, we distinguish three:
- Any graph for which the edge type is derived from the pure virtual class
weighted_edgeis a weighted graph. More details on theweighted_edgeclass below. The weight of an edge is given by theget_weightfunction of an edge. - Any graph with a primitive numeric type for the edge type (
int,float,doubleetc.) is a weighted graph. The weight of an edge is simply given by the numeric value. - In all other cases, the graph is unweighted. In contexts where a weighted graph is required, each edge defaults to a unit weight automatically.
The weight of an edge can be queried using the following overload set of get_weight functions in the graaf
namespace.
Weighted Edge
The pure virtual weighted_edge class provides an interface for user provided edge classes which should carry a
weight.
template <typename WEIGHT_T = int>
class weighted_edge {
public:
using weight_t = WEIGHT_T;
virtual ~weighted_edge() = default;
[[nodiscard]] virtual WEIGHT_T get_weight() const noexcept = 0;
};The idea here is to keep the graph classes as general-purpose as possible, and to not include use case specific logic (such as dot serialization) as member functions. Therefore, each algorithm/utility function is implemented as a free function.
The design goal is to keep the algorithms as general purpose as possible, such that their functionality can be reused in future algorithms. For example, once we defined BFS/DFS traversal of graphs, search algorithms could be defined in terms of these traversal algorithms.
Welcome to the Graaf wiki!
In case anything is unclear, or you encounter any other problems, please reach out on Discord or open an issue.