Files
fennec/include/fennec/containers/graph.h
Medusa Slockbower ff27caab4f - Fixed some variable naming with graph and it's PrettyPrinter
- Added boost-atomic and boost-thread as dependencies for concurrency support
2025-08-21 06:44:22 -04:00

518 lines
15 KiB
C++

// =====================================================================================================================
// fennec, a free and open source game engine
// Copyright © 2025 Medusa Slockbower
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// =====================================================================================================================
///
/// \file graph.h
/// \brief A header containing the definition for a graph of vertices connected by edges
///
///
/// \details
/// \author Medusa Slockbower
///
/// \copyright Copyright © 2025 Medusa Slockbower ([GPLv3](https://www.gnu.org/licenses/gpl-3.0.en.html))
///
///
#ifndef FENNEC_CONTAINERS_GRAPH_H
#define FENNEC_CONTAINERS_GRAPH_H
#include <fennec/containers/dynarray.h>
#include <fennec/containers/list.h>
#include <fennec/containers/map.h>
#include <fennec/containers/object_pool.h>
#include <fennec/containers/set.h>
/*
* With the directed tree we were able to cheat a little, the structure has more rules to it which allows
* tighter constraints. A graph is basically no rules whatsoever. Some variants, such as weighted graphs, assign
* properties or rules to edges which can simply be an extension to this graph.
*
* The most effective way to do this is to have a dynarray of lists, however this results in double the
* memory being used. This can also result in two edge objects being created.
*
* There is no nice way to avoid the problem of mapping vertices to edges
*/
namespace fennec
{
///
/// \brief Graph Data Structure, describes sets of arbitrarily connected vertices
///
/// \details
/// | Property | Value |
/// |:----------:|:----------:|
/// | stable | ⛔ |
/// | dynamic | ✅ |
/// | homogenous | ✅ |
/// | distinct | ⛔ |
/// | ordered | ⛔ |
/// | space | \f$O(N)\f$ |
/// | linear | ✅ |
/// | access | \f$O(1)\f$ |
/// | find | \f$O(1)\f$ |
/// | insertion | \f$O(1)\f$ |
/// | deletion | \f$O(N)\f$ |
///
/// Graphs contain vertices and edges. Graphs are either directed
/// or undirected. This structure allows the creation of both directed and undirected edges. As
/// far as what that means; a directed graph means that edges have direction, where there are edges
/// that are "to" and "from," rather than "between" which is used in undirected graphs.
///
/// An undirected graph is connected if there is a path between every pair of vertices in the graph.
///
/// A directed graph is weakly connected if replacing all of its directed edges with undirected edges would
/// produce a connected graph. We will call this "disjointed"
///
/// A directed graph is semi-connected if there is a directed path p for `u` &rarr; `v` *or* `v` &rarr; `u` for every
/// pair of vertices `u, v`. We will call this "unilateral"
///
/// A directed graph is strongly-connected if there is a directed path p for `u` &rarr; `v` *and* `v` &rarr; `u` for every pair
/// of vertices `u, v`. We will call this "connected"
///
/// \tparam VertexT The type associated with each vertex
/// \tparam EdgeT The type associated with each edge
template<typename VertexT, typename EdgeT = empty_t>
struct graph {
public:
// Definitions =========================================================================================================
using edge_t = EdgeT; ///< Alias for the edge type
using vertex_t = VertexT; ///< Alias for the vertex type
using vertex_pool_t = object_pool<vertex_t>; ///< Alias for a pool of vertices
using edge_map_t = dynarray<map<size_t, size_t>>; ///< Alias for edge mapping
using edge_pool_t = object_pool<edge_t>; ///< Alias for a pool of edges
static constexpr size_t npos = -1; ///< Constant for a non-existent vertex
// Constructors ========================================================================================================
/// \name Constructors & Destructor
/// @{
///
/// \brief Default Constructor, initializes empty graph
constexpr graph() = default;
///
/// \brief Destructor
constexpr ~graph() = default;
/// @}
/// \name Assignment Operators
/// @{
///
/// \brief Copy Assignment Operator
/// \param g The graph to copy
/// \returns A reference to this after assigning g
constexpr graph& operator=(const graph& g) = default;
///
/// \brief Move Assignment Operator
/// \param g The graph to copy
/// \returns A reference to this after assigning g
constexpr graph& operator=(graph&& g) = default;
/// @}
// Properties ==========================================================================================================
/// \name Properties
/// @{
///
/// \returns The number of vertices in the graph
constexpr size_t num_vertices() const {
return _vertex_pool.size();
}
///
/// \returns The number of edges in the graph
constexpr size_t num_edges() const {
return _edge_pool.size();
}
///
/// \returns The capacity of the vertex pool
constexpr size_t capacity() const {
return _vertex_pool.capacity();
}
///
/// \returns `true` when there are no vertices in the graph, `false` otherwise
constexpr bool empty() const {
return num_vertices() == 0;
}
///
/// \brief Checks if there exists an edge `e` that starts from `a` and ends at `b`
/// \param a The first vertex
/// \param b The second vertex
/// \returns `true` if the edge exists, `false` otherwise
constexpr bool exists(size_t a, size_t b) const {
return _edge_map[a][b] != nullptr;
}
///
/// \brief Checks if there exists an edge `e0` that starts from `a` and ends at `b` and `e1` that starts from `b`
/// and ends at `a`
/// \param a The first vertex
/// \param b The second vertex
/// \returns `true` if both edges exist, `false` otherwise
constexpr bool is_symmetric(size_t a, size_t b) const {
return exists(a, b) and exists(b, a);
}
///
/// \brief Checks if there exists an edge `e` between `a` and `b`
/// \param a The first vertex
/// \param b The second vertex
/// \returns `true` if both edges exist, `false` otherwise
constexpr bool is_undirected(size_t a, size_t b) const {
const auto* e0 = _edge_map[a][b];
const auto* e1 = _edge_map[b][a];
if (not (e0 != nullptr && e1 != nullptr)) {
return false;
}
return *e0 == *e1;
}
// TODO: connected, disjoint, unilateral, get_component
/// @}
// Access ==============================================================================================================
/// \name Access
/// @{
///
/// \brief vertex Access Operator
/// \param vertex The id of the vertex
/// \returns A reference to the value stored in the vertex
constexpr vertex_t& operator[](size_t vertex) {
return _vertex_pool[vertex];
}
///
/// \brief vertex Const Access Operator
/// \param vertex The id of the vertex
/// \returns A reference to the value stored in the vertex
constexpr const vertex_t& operator[](size_t vertex) const {
return _vertex_pool[vertex];
}
///
/// \brief edge Access Operator
/// \param a The id of the first vertex
/// \param b The id of the second vertex
/// \returns A pointer to the value stored in the edge, `nullptr` if not found
constexpr edge_t* operator[](size_t a, size_t b) {
if (empty()) {
return nullptr;
}
edge_t* it = _edge_map[a][b];
if (it) {
return _edge_pool[*it];
}
return nullptr;
}
///
/// \brief edge Const Access Operator
/// \param a The id of the first vertex
/// \param b The id of the second vertex
/// \returns A const-qualified pointer to the value stored in the edge, `nullptr` if not found
constexpr const edge_t* operator[](size_t a, size_t b) const {
if (empty()) {
return nullptr;
}
const edge_t* it = _edge_map[a][b];
if (it) {
return _edge_pool[*it];
}
return nullptr;
}
///
/// \brief Getter for a list of vertices `x` that `vertex` has an edge to `x...`
/// \param vertex The id of the vertex
/// \returns A list containing all vertices `x` with edges from `vertex` to `x...`
list<size_t> outgoing(size_t vertex) {
list<size_t> res;
if (empty() || vertex >= _edge_map.size()) {
return res;
}
for (const auto& it : _edge_map[vertex]) {
res.push_back(it.first);
}
return res;
}
///
/// \brief Getter for a list of vertices `x` that `vertex` has an edge from `x...`
/// \param vertex The id of the vertex
/// \returns A list containing all vertices `x` with edges from `x...` to `vertex`
list<size_t> incoming(size_t vertex) {
list<size_t> res;
if (empty() || vertex >= _edge_map.size()) {
return res;
}
for (size_t n = 0; n < _edge_map.size(); ++n) {
if (_edge_map[n][vertex]) {
res.push_back(n);
}
}
return res;
}
///
/// \brief Getter for a list of vertices `x` that `vertex` has an edge to and from `x...`
/// \param vertex The id of the vertex
/// \returns A list containing all vertices `x` that have symmetric edges with `vertex`
list<size_t> symmetric(size_t vertex) {
list<size_t> res;
if (empty() || vertex >= _edge_map.size()) {
return res;
}
for (const auto& it : _edge_map[vertex]) {
if (_edge_map[it.first][vertex]) {
res.push_back(it.first);
}
}
return res;
}
///
/// \brief Getter for a list of vertices `x` that `vertex` has an edge to and from `x...` and share the same value
/// \details
/// "Joined" edges may also be referred to as "undirected." A joined, or undirected, edge may be
/// turned into a directed edge by changing the weight object associated with the edge, or by
/// removing one of the sub-edges.
/// \param vertex The id of the vertex
/// \returns A list containing all vertices `x` that have symmetric edges with `vertex`
list<size_t> undirected(size_t vertex) {
list<size_t> res;
if (empty() || vertex >= _edge_map.size()) {
return res;
}
for (const auto& it : _edge_map[vertex]) {
const auto* at = _edge_map[it.first][vertex];
if (at != nullptr && *at == it.second) {
res.push_back(it.first);
}
}
return res;
}
///
/// \brief Getter for the internal storage of mapped edges from this vertex.
/// Use this when you want to iterate over edges that start from this vertex.
/// \param vertex The id of the vertex
/// \returns A pointer to a map containing edges mapped from this vertex
const auto* edges(size_t vertex) {
if (empty() || vertex >= _edge_map.size()) {
return nullptr;
}
return &_edge_map[vertex];
}
/// @}
// Modifiers ===========================================================================================================
/// \name Modifiers
/// @{
///
/// \brief Move a new vertex into the graph
/// \param vertex The vertex to move into the graph
/// \returns The id of the new vertex
constexpr size_t insert(vertex_t&& vertex) {
return this->_insert(fennec::forward<vertex_t>(vertex));
}
///
/// \brief Copy a new vertex into the graph
/// \param vertex The vertex to copy into the graph
/// \returns The id of the new vertex
constexpr size_t insert(const vertex_t& vertex) {
return this->_insert(vertex);
}
///
/// \brief Construct a new vertex in the graph
/// \tparam ArgsT The types of the arguments
/// \param args The arguments to construct the vertex with
/// \returns The id of the new vertex
template<typename...ArgsT>
constexpr size_t emplace(ArgsT&&...args) {
return this->_insert(fennec::forward<ArgsT>(args)...);
}
///
/// \brief Erase a vertex from the graph
/// \param vertex The id of the vertex to erase
constexpr void erase(size_t vertex) {
cut(vertex);
_vertex_pool.erase(vertex);
}
///
/// \brief Form an edge from vertex `a` to vertex `b`
/// \tparam ArgsT The argument types
/// \param a The first vertex id
/// \param b The second vertex id
/// \param args The arguments to construct the edge with
template<typename...ArgsT>
constexpr void make_edge(size_t a, size_t b, ArgsT&&...args) {
if (a == b) {
return;
}
if (_edge_map.size() < _vertex_pool.capacity()) {
_edge_map.resize(_vertex_pool.capacity());
}
auto it = _edge_map[a][b];
size_t conn;
if (it != nullptr) {
conn = *it;
_edge_pool[conn] = vertex_t(fennec::forward<ArgsT>(args)...);
} else {
conn = _edge_pool.emplace(fennec::forward<ArgsT>(args)...);
}
_edge_map[a].emplace(b, conn);
}
///
/// \brief Form an undirected edge between vertex `a` and vertex `b`
/// \tparam ArgsT The argument types
/// \param a The first vertex id
/// \param b The second vertex id
/// \param args The arguments to construct the edge with
template<typename...ArgsT>
constexpr void make_edge2(size_t a, size_t b, ArgsT&&...args) {
if (a == b) {
return;
}
if (_edge_map.size() < _vertex_pool.capacity()) {
_edge_map.resize(_vertex_pool.capacity());
}
auto it = _edge_map[a][b];
size_t conn;
if (it != nullptr) {
conn = *it;
_edge_pool[conn] = vertex_t(fennec::forward<ArgsT>(args)...);
} else {
conn = _edge_pool.emplace(fennec::forward<ArgsT>(args)...);
}
_edge_map[a].emplace(b, conn);
_edge_map[b].emplace(a, conn);
}
///
/// \brief Disconnect an edge from vertex `a` to vertex `b`
/// \param a The first vertex id
/// \param b The second vertex id
constexpr void cut_edge(size_t a, size_t b) {
// Find the edge object
const auto* it = _edge_map[a][b];
if (not it) {
return;
}
size_t c = *it;
// Check if undirected
const auto* at = _edge_map[b][a];
if (not at || *at != c) {
_edge_pool.erase(c);
}
// Erase the edge mapping
_edge_map[a].erase(b);
}
///
/// \brief Disconnect both directed edges between vertices `a` and `b`
/// \param a The first vertex id
/// \param b The second vertex id
constexpr void cut_edge2(size_t a, size_t b) {
const auto* ita = _edge_map[a][b];
const auto* itb = _edge_map[a][b];
if (not (ita || itb)) {
return;
}
if (ita) _edge_pool.erase(*ita);
if (itb) _edge_pool.erase(*itb);
_edge_map[a].erase(b);
_edge_map[b].erase(a);
}
///
/// \brief Break *all* edges to and from `n`
/// \param n The vertex id
void cut(size_t n) {
for (const auto it : outgoing(n)) {
cut_edge(n, it);
}
for (const auto it : incoming(n)) {
cut_edge(it, n);
}
}
///
/// \brief Clear the graph, destructing all vertices and edges.
void clear() {
_vertex_pool.clear();
_edge_pool.clear();
_edge_map.clear();
}
/// @}
// edges =========================================================================================================
private:
vertex_pool_t _vertex_pool;
edge_pool_t _edge_pool;
edge_map_t _edge_map;
template<typename...ArgsT>
size_t _insert(ArgsT&&...args) {
return _vertex_pool.emplace(fennec::forward<ArgsT>(args)...);
}
};
}
#endif // FENNEC_CONTAINERS_GRAPH_H