2023-11-04 17:12:18 +01:00
|
|
|
// A so called include guard uses the preprocessor to make sure nothing happens when
|
|
|
|
// this header is include a second time. This becomes important if there are many headers
|
|
|
|
// including each other, as undirected cycles can usually not be avoided.
|
|
|
|
#ifndef GRAPH_HPP
|
|
|
|
#define GRAPH_HPP
|
|
|
|
|
|
|
|
/**
|
|
|
|
@file graph.hpp
|
|
|
|
|
|
|
|
@brief This file provides a simple class @c Graph to model unweighted undirected graphs.
|
|
|
|
**/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* In and output in the standard library is done using streams.
|
|
|
|
* In this header we only need to know std::istream and std::ostream are classes,
|
|
|
|
* since we only declare the function which read write our graph
|
|
|
|
* from an std::istream or to an std::ostream, so we only include the forward declaration.
|
|
|
|
*/
|
|
|
|
#include <iosfwd>
|
|
|
|
|
|
|
|
/**
|
|
|
|
* This header defined many different integer types,
|
|
|
|
* enabling us to choose what integers we want to use.
|
|
|
|
*/
|
|
|
|
#include <cstdint>
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Limits are provided by the standard library to check
|
|
|
|
* e.g. if some value can be represented in a certain integer type.
|
|
|
|
*/
|
|
|
|
#include <limits>
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Vectors are implemented in the standard library as std::vector.
|
|
|
|
* They encapsulate an array of dynamic size,
|
|
|
|
* so that you don't have to know about the exact implementation.
|
|
|
|
* If you add an element in the end (aka push_back) but the dynamic array is full,
|
|
|
|
* it will automatically be resized.
|
|
|
|
* See https://en.cppreference.com/w/cpp/container/vector for documentation.
|
|
|
|
*/
|
|
|
|
#include <vector>
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Namespaces can be used in order to make sure different modules,
|
|
|
|
* possibly implemented by different people don't have classes/functions/...
|
|
|
|
* with the same name. If you want to refer to some symbol S,
|
|
|
|
* which is defined in a namespace N from outside of that namespace,
|
|
|
|
* you need to write N::S.
|
|
|
|
* The most prominent example is std, the namespace used by the standard library.
|
|
|
|
* This namespace is intended to be used for Edmonds.
|
|
|
|
* */
|
|
|
|
namespace ED
|
|
|
|
{
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Using names for types has many advantages.
|
|
|
|
* One of them is being able to switch type with very little effort.
|
|
|
|
* For now, we are going to use unsigned (i.e. non negative)
|
|
|
|
* 32 bit integers for all sizes and indices.
|
|
|
|
* But if there was some large graph for which we need 64 bit indices,
|
|
|
|
* we would only need to change the type once, right here!
|
|
|
|
*/
|
|
|
|
using size_type = uint32_t;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Another advantage of naming types is making your code more readable.
|
|
|
|
* For example an Id is usually a light weight object (read: few bits)
|
|
|
|
* which uniquely determines some object, in this case a node in our graph.
|
|
|
|
* Note the same Id may be used by different graphs though!
|
|
|
|
*/
|
|
|
|
using NodeId = size_type;
|
2023-11-04 17:26:17 +01:00
|
|
|
constexpr NodeId invalid_node_id = std::numeric_limits<NodeId>::max();
|
2023-11-04 17:12:18 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
@class Node
|
|
|
|
|
|
|
|
@brief A @c Node stores an array of neighbors (via their ids).
|
|
|
|
|
|
|
|
@note The neighbors are not necessarily ordered, so searching for a specific neighbor takes O(degree)-time.
|
|
|
|
**/
|
|
|
|
class Node
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
/** @brief Create an isolated node (you can add neighbors later). **/
|
|
|
|
Node() = default;
|
|
|
|
|
|
|
|
/** @return The number of neighbors of this node. **/
|
|
|
|
size_type degree() const;
|
|
|
|
|
|
|
|
/** @return The array of ids of the neighbors of this node. **/
|
|
|
|
std::vector<NodeId> const & neighbors() const;
|
|
|
|
|
2023-11-04 17:41:17 +01:00
|
|
|
public:
|
|
|
|
NodeId matched_neighbor {invalid_node_id};
|
|
|
|
NodeId ear_or_root_neighbor {invalid_node_id};
|
|
|
|
NodeId root_of_ear_component {invalid_node_id};
|
|
|
|
|
2023-11-04 17:12:18 +01:00
|
|
|
private:
|
|
|
|
// This allows each Graph to access private members of this class,
|
|
|
|
// in our case the add_neighbor function
|
|
|
|
friend class Graph;
|
|
|
|
|
|
|
|
/**
|
|
|
|
@brief Adds @c id to the list of neighbors of this node.
|
|
|
|
@warning Does not check whether @c id is already in the list of neighbors (a repeated neighbor is legal, and
|
|
|
|
models parallel edges).
|
|
|
|
@warning Does not check whether @c id is the identity of the node itself (which would create a loop!).
|
|
|
|
**/
|
|
|
|
void add_neighbor(NodeId const id);
|
|
|
|
|
|
|
|
std::vector<NodeId> _neighbors;
|
|
|
|
}; // class Node
|
|
|
|
|
|
|
|
/**
|
|
|
|
@class Graph
|
|
|
|
|
|
|
|
@brief A @c Graph stores an array of @c Node s, but no array of edges. The list of edges is implicitly given
|
|
|
|
by the fact that the nodes know their neighbors.
|
|
|
|
|
|
|
|
This class models undirected graphs only (in the sense that the method @c add_edge(node1, node2) adds both @c node1
|
|
|
|
as a neighbor of @c node2 and @c node2 as a neighbor of @c node1). It also forbids loops, but parallel edges are
|
|
|
|
legal.
|
|
|
|
|
|
|
|
@warning Nodes are numbered starting at 0, as is usually done in programming,
|
|
|
|
instead starting at 1, as is done in the DIMACS format that your program should take as input!
|
|
|
|
Be careful.
|
|
|
|
**/
|
|
|
|
class Graph
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
/**
|
|
|
|
@brief Creates a @c Graph with @c num_nodes isolated nodes.
|
|
|
|
|
|
|
|
The number of nodes in the graph currently cannot be changed. You can only add edges between the existing nodes.
|
|
|
|
**/
|
2023-11-04 17:29:15 +01:00
|
|
|
explicit Graph(NodeId const num_nodes);
|
2023-11-04 17:12:18 +01:00
|
|
|
|
|
|
|
/** @return The number of nodes in the graph. **/
|
|
|
|
NodeId num_nodes() const;
|
|
|
|
|
|
|
|
/** @return The number of edges in the graph. **/
|
|
|
|
size_type num_edges() const;
|
|
|
|
|
|
|
|
/**
|
|
|
|
@return A reference to the id-th entry in the array of @c Node s of this graph.
|
|
|
|
**/
|
|
|
|
Node const & node(NodeId const id) const;
|
|
|
|
|
|
|
|
/**
|
|
|
|
@brief Adds the edge <tt> {node1_id, node2_id} </tt> to this graph.
|
|
|
|
|
|
|
|
Checks that @c node1_id and @c node2_id are distinct and throws an exception otherwise.
|
|
|
|
This method adds both @c node1_id as a neighbor of @c node2_id and @c node2_id as a neighbor of @c node1_id.
|
|
|
|
|
|
|
|
@warning Does not check that the edge does not already exist, so this class can be used to model non-simple graphs.
|
|
|
|
**/
|
|
|
|
void add_edge(NodeId node1_id, NodeId node2_id);
|
|
|
|
|
|
|
|
// Static functions are not called on an object of the class, but on the class itself.
|
|
|
|
/**
|
|
|
|
* Reads a graph in DIMACS format from the given istream and returns that graph.
|
|
|
|
*/
|
|
|
|
static Graph read_dimacs(std::istream & str);
|
|
|
|
/**
|
|
|
|
@brief Prints the graph to the given ostream in DIMACS format.
|
|
|
|
**/
|
|
|
|
friend std::ostream & operator<<(std::ostream & str, Graph const & graph);
|
2023-11-04 17:26:17 +01:00
|
|
|
|
|
|
|
NodeId matched_neighbor(NodeId const id) const;
|
|
|
|
|
|
|
|
NodeId ear_or_root_neighbor(NodeId const id) const;
|
|
|
|
|
|
|
|
NodeId root_of_ear_component(NodeId const id) const;
|
|
|
|
|
|
|
|
bool is_outer(NodeId const id) const;
|
|
|
|
|
|
|
|
bool is_inner(NodeId const id) const;
|
|
|
|
|
|
|
|
bool is_out_of_forest(NodeId const id) const;
|
2023-11-04 17:34:17 +01:00
|
|
|
|
|
|
|
void reset_forest();
|
2023-11-04 17:12:18 +01:00
|
|
|
private:
|
|
|
|
std::vector<Node> _nodes;
|
|
|
|
size_type _num_edges;
|
|
|
|
}; // class Graph
|
|
|
|
|
|
|
|
// Calling a function usually has some constant time overhead.
|
|
|
|
// The compiler is capable of "inlining" function calls,
|
|
|
|
// which means when your code calls this function,
|
|
|
|
// the compiler will instead insert the content of the function.
|
|
|
|
// This has no affect on your code, but will get rid of this overhead.
|
|
|
|
// The inline keywoard recommends the compiler to inline a function.
|
|
|
|
// If you use it for some, you must implement that function
|
|
|
|
// in the header! For readablility, we put all implementations
|
|
|
|
// of inline function into the following inline section.
|
|
|
|
//BEGIN: Inline section
|
|
|
|
|
|
|
|
inline
|
|
|
|
size_type Node::degree() const
|
|
|
|
{
|
|
|
|
return neighbors().size();
|
|
|
|
}
|
|
|
|
|
|
|
|
inline
|
|
|
|
std::vector<NodeId> const & Node::neighbors() const
|
|
|
|
{
|
|
|
|
return _neighbors;
|
|
|
|
}
|
|
|
|
|
|
|
|
inline
|
|
|
|
NodeId Graph::num_nodes() const
|
|
|
|
{
|
|
|
|
return _nodes.size();
|
|
|
|
}
|
|
|
|
|
|
|
|
inline
|
|
|
|
size_type Graph::num_edges() const
|
|
|
|
{
|
|
|
|
return _num_edges;
|
|
|
|
}
|
|
|
|
|
|
|
|
inline
|
|
|
|
Node const & Graph::node(NodeId const id) const
|
|
|
|
{
|
|
|
|
return _nodes[id];
|
|
|
|
}
|
|
|
|
//END: Inline section
|
|
|
|
|
2023-11-04 17:26:17 +01:00
|
|
|
|
|
|
|
inline
|
|
|
|
NodeId Graph::matched_neighbor(NodeId const id) const
|
|
|
|
{
|
2023-11-04 17:41:17 +01:00
|
|
|
return _nodes[id].matched_neighbor;
|
2023-11-04 17:26:17 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
inline
|
|
|
|
NodeId Graph::ear_or_root_neighbor(const ED::NodeId id) const
|
|
|
|
{
|
2023-11-04 17:41:17 +01:00
|
|
|
return _nodes[id].ear_or_root_neighbor;
|
2023-11-04 17:26:17 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
inline
|
|
|
|
NodeId Graph::root_of_ear_component(const ED::NodeId id) const
|
|
|
|
{
|
2023-11-04 17:41:17 +01:00
|
|
|
return _nodes[id].root_of_ear_component;
|
2023-11-04 17:26:17 +01:00
|
|
|
}
|
|
|
|
|
2023-11-04 17:12:18 +01:00
|
|
|
} // namespace ED
|
|
|
|
|
|
|
|
#endif /* GRAPH_HPP */
|