- Added boost-atomic and boost-thread as dependencies for concurrency support
518 lines
15 KiB
C++
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` → `v` *or* `v` → `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` → `v` *and* `v` → `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
|