1
0
Fork 0

Add struct Graph to represent input graph

This commit is contained in:
Joris van Rantwijk 2023-05-27 23:29:05 +02:00
parent 9b064de4d8
commit 4b7fab3f43
1 changed files with 171 additions and 134 deletions

View File

@ -148,6 +148,42 @@ void check_input_graph(const std::vector<Edge<WeightType>>& edges)
}
/* **************************************************
* ** struct Graph **
* ************************************************** */
/**
* Representation of the input graph.
*/
template <typename WeightType>
struct Graph
{
using EdgeT = Edge<WeightType>;
/** Graph edges. */
const std::vector<EdgeT> edges;
/** Number of vertices. */
const VertexId num_vertex;
/** For each vertex, a vector of pointers to its incident edges. */
const std::vector<std::vector<const EdgeT*>> adjacent_edges;
/**
* Initialize the graph representation and prepare adjacent edge lists.
*
* This function takes time O(n + m).
*/
explicit Graph(const std::vector<EdgeT>& edges_in)
: edges(remove_negative_weight_edges(edges_in)),
num_vertex(count_num_vertex(edges)),
adjacent_edges(build_adjacent_edges(edges, num_vertex))
{ }
// Prevent copying.
Graph(const Graph&) = delete;
Graph& operator=(const Graph&) = delete;
/**
* Copy edges and remove edges with negative weight.
*
@ -156,14 +192,13 @@ void check_input_graph(const std::vector<Edge<WeightType>>& edges)
* @param edges Vector of weighted edges defining the graph.
* @return Vector of edges with non-negative weight.
*/
template <typename WeightType>
std::vector<Edge<WeightType>> remove_negative_weight_edges(
const std::vector<Edge<WeightType>>& edges)
static std::vector<EdgeT> remove_negative_weight_edges(
const std::vector<EdgeT>& edges_in)
{
std::vector<Edge<WeightType>> real_edges;
real_edges.reserve(edges.size());
std::vector<EdgeT> real_edges;
real_edges.reserve(edges_in.size());
for (const Edge<WeightType>& edge : edges) {
for (const Edge<WeightType>& edge : edges_in) {
if (edge.weight >= 0) {
real_edges.push_back(edge);
}
@ -172,6 +207,59 @@ std::vector<Edge<WeightType>> remove_negative_weight_edges(
return real_edges;
}
/** Count the number of vertices in the graph. */
static VertexId count_num_vertex(const std::vector<EdgeT>& edges)
{
VertexId num_vertex = 0;
for (const Edge<WeightType>& edge : edges) {
VertexId m = std::max(edge.vt.first, edge.vt.second);
assert(m < std::numeric_limits<VertexId>::max());
num_vertex = std::max(num_vertex, m + 1);
}
return num_vertex;
}
/**
* Build adjacency lists for a graph.
*
* Note that the adjacency lists contain pointers to edge instances,
* and therefore become invalid if the input edge vector is modified
* in any way that invalidates its iterators.
*
* This function takes time O(m).
*
* @param num_vertex Number of vertices in the graph.
* @param edges List of edges in the graph.
* @return Vector of incident edges for each vertex.
*/
static std::vector<std::vector<const EdgeT*>> build_adjacent_edges(
const std::vector<EdgeT>& edges,
VertexId num_vertex)
{
// Count the number of incident edges per vertex.
std::vector<VertexId> vertex_degree(num_vertex);
for (const EdgeT& edge : edges) {
assert(edge.vt.first < num_vertex);
assert(edge.vt.second < num_vertex);
vertex_degree[edge.vt.first]++;
vertex_degree[edge.vt.second]++;
}
// Build adjacency lists.
std::vector<std::vector<const EdgeT*>> adjacent_edges(num_vertex);
for (VertexId i = 0; i < num_vertex; ++i) {
adjacent_edges[i].reserve(vertex_degree[i]);
}
for (const EdgeT& edge : edges) {
adjacent_edges[edge.vt.first].push_back(&edge);
adjacent_edges[edge.vt.second].push_back(&edge);
}
return adjacent_edges;
}
};
/* **************************************************
* ** struct Blossom **
@ -410,8 +498,10 @@ struct AlternatingPath
* auxiliary data structures.
*/
template <typename WeightType>
struct MatchingContext
class MatchingContext
{
public:
using EdgeT = Edge<WeightType>;
using BlossomT = Blossom<WeightType>;
using NonTrivialBlossomT = NonTrivialBlossom<WeightType>;
@ -436,14 +526,8 @@ struct MatchingContext
static constexpr WeightType weight_factor =
std::numeric_limits<WeightType>::is_integer ? 2 : 1;
/** Graph edges. */
const std::vector<EdgeT> edges;
/** Number of vertices. */
const VertexId num_vertex;
/** For each vertex, a vector of pointers to its incident edges. */
const std::vector<std::vector<const EdgeT*>> adjacent_edges;
/** Input graph. */
const Graph<WeightType> graph;
/**
* Current state of the matching.
@ -506,99 +590,43 @@ struct MatchingContext
* This function takes time O(n + m).
*/
explicit MatchingContext(const std::vector<EdgeT>& edges_in)
: edges(remove_negative_weight_edges(edges_in)),
num_vertex(count_num_vertex(edges)),
adjacent_edges(build_adjacent_edges(edges, num_vertex))
: graph(edges_in)
{
// Initially all vertices are unmatched.
vertex_mate.resize(num_vertex, NO_VERTEX);
vertex_mate.resize(graph.num_vertex, NO_VERTEX);
// Create a trivial blossom for each vertex.
trivial_blossom.reserve(num_vertex);
for (VertexId x = 0; x < num_vertex; ++x) {
trivial_blossom.reserve(graph.num_vertex);
for (VertexId x = 0; x < graph.num_vertex; ++x) {
trivial_blossom.emplace_back(x);
}
// Initially all vertices are trivial top-level blossoms.
vertex_top_blossom.reserve(num_vertex);
for (VertexId x = 0; x < num_vertex; ++x) {
vertex_top_blossom.reserve(graph.num_vertex);
for (VertexId x = 0; x < graph.num_vertex; ++x) {
vertex_top_blossom.push_back(&trivial_blossom[x]);
}
// Vertex duals are initialized to half the maximum edge weight.
WeightType max_weight = 0;
for (const EdgeT& edge : edges) {
for (const EdgeT& edge : graph.edges) {
max_weight = std::max(max_weight, edge.weight);
}
WeightType init_vertex_dual = max_weight * (weight_factor / 2);
vertex_dual.resize(num_vertex, init_vertex_dual);
vertex_dual.resize(graph.num_vertex, init_vertex_dual);
// Initialize "vertex_best_edge".
vertex_best_edge.resize(num_vertex, nullptr);
vertex_best_edge.resize(graph.num_vertex, nullptr);
// Allocate temporary arrays for path tracing.
vertex_marker.resize(num_vertex);
marked_vertex.reserve(num_vertex);
vertex_marker.resize(graph.num_vertex);
marked_vertex.reserve(graph.num_vertex);
}
// Prevent copying.
MatchingContext(const MatchingContext&) = delete;
MatchingContext& operator=(const MatchingContext&) = delete;
/* ********** Static methods used by the constructor: ********** */
/** Count the number of vertices in the graph. */
static VertexId count_num_vertex(const std::vector<EdgeT>& edges)
{
VertexId num_vertex = 0;
for (const Edge<WeightType>& edge : edges) {
VertexId m = std::max(edge.vt.first, edge.vt.second);
assert(m < std::numeric_limits<VertexId>::max());
num_vertex = std::max(num_vertex, m + 1);
}
return num_vertex;
}
/**
* Build adjacency lists for a graph.
*
* Note that the adjacency lists contain pointers to edge instances,
* and therefore become invalid if the input edge vector is modified
* in any way that invalidates its iterators.
*
* This function takes time O(m).
*
* @param num_vertex Number of vertices in the graph.
* @param edges List of edges in the graph.
* @return Vector of incident edges for each vertex.
*/
static std::vector<std::vector<const EdgeT*>> build_adjacent_edges(
const std::vector<EdgeT>& edges,
VertexId num_vertex)
{
// Count the number of incident edges per vertex.
std::vector<VertexId> vertex_degree(num_vertex);
for (const EdgeT& edge : edges) {
assert(edge.vt.first < num_vertex);
assert(edge.vt.second < num_vertex);
vertex_degree[edge.vt.first]++;
vertex_degree[edge.vt.second]++;
}
// Build adjacency lists.
std::vector<std::vector<const EdgeT*>> adjacent_edges(num_vertex);
for (VertexId i = 0; i < num_vertex; ++i) {
adjacent_edges[i].reserve(vertex_degree[i]);
}
for (const EdgeT& edge : edges) {
adjacent_edges[edge.vt.first].push_back(&edge);
adjacent_edges[edge.vt.second].push_back(&edge);
}
return adjacent_edges;
}
/* ********** General support routines: ********** */
/** Calculate edge slack. */
@ -648,7 +676,7 @@ struct MatchingContext
*/
void lset_reset()
{
for (VertexId x = 0; x < num_vertex; ++x) {
for (VertexId x = 0; x < graph.num_vertex; ++x) {
vertex_best_edge[x] = nullptr;
}
@ -692,7 +720,7 @@ struct MatchingContext
const EdgeT* best_edge = nullptr;
WeightType best_slack = 0;
for (VertexId x = 0; x < num_vertex; ++x) {
for (VertexId x = 0; x < graph.num_vertex; ++x) {
if (vertex_top_blossom[x]->label == LABEL_NONE) {
const EdgeT* edge = vertex_best_edge[x];
if (edge != nullptr) {
@ -762,7 +790,7 @@ struct MatchingContext
} else {
// Trivial blossoms don't maintain a least-slack edge set.
// Just consider all incident edges.
for (const EdgeT* edge : adjacent_edges[sub->base_vertex]) {
for (const EdgeT* edge : graph.adjacent_edges[sub->base_vertex]) {
// Only take edges between different S-blossoms.
VertexId x = edge->vt.first;
VertexId y = edge->vt.second;
@ -782,7 +810,7 @@ struct MatchingContext
// each top-level S-blossom. This array is indexed by the base vertex
// of the blossoms.
std::vector<std::pair<const EdgeT*, WeightType>>
best_edge_to_blossom(num_vertex);
best_edge_to_blossom(graph.num_vertex);
for (const EdgeT* edge : blossom->best_edge_set) {
BlossomT* bx = vertex_top_blossom[edge->vt.first];
@ -1467,7 +1495,7 @@ struct MatchingContext
// Scan the edges that are incident on "x".
// This loop runs through O(m) iterations per stage.
for (const EdgeT* edge : adjacent_edges[x]) {
for (const EdgeT* edge : graph.adjacent_edges[x]) {
VertexId y = (edge->vt.first != x) ? edge->vt.first
: edge->vt.second;
@ -1540,7 +1568,7 @@ struct MatchingContext
// Compute delta1: minimum dual variable of any S-vertex.
delta.kind = 1;
delta.value = std::numeric_limits<WeightType>::max();
for (VertexId x = 0; x < num_vertex; ++x) {
for (VertexId x = 0; x < graph.num_vertex; ++x) {
if (vertex_top_blossom[x]->label == LABEL_S) {
delta.value = std::min(delta.value, vertex_dual[x]);
}
@ -1584,7 +1612,7 @@ struct MatchingContext
void substage_apply_delta_step(WeightType delta)
{
// Apply delta to dual variables of all vertices.
for (VertexId x = 0; x < num_vertex; ++x) {
for (VertexId x = 0; x < graph.num_vertex; ++x) {
BlossomLabel xlabel = vertex_top_blossom[x]->label;
if (xlabel == LABEL_S) {
// S-vertex: subtract delta from dual variable.
@ -1650,7 +1678,7 @@ struct MatchingContext
bool run_stage()
{
// Assign label S to all unmatched vertices and put them in the queue.
for (VertexId x = 0; x < num_vertex; ++x) {
for (VertexId x = 0; x < graph.num_vertex; ++x) {
if (vertex_mate[x] == NO_VERTEX) {
assign_label_s(x);
}
@ -1747,24 +1775,36 @@ struct MatchingContext
/** Helper class to verify that an optimal solution has been found. */
template <typename WeightType>
struct MatchingVerifier
class MatchingVerifier
{
public:
MatchingVerifier(const MatchingContext<WeightType>& ctx)
: ctx(ctx),
graph(ctx.graph),
edge_duals(ctx.graph.edges.size())
{ }
/**
* Verify that the optimum solution has been found.
*
* This function takes time O(n**2).
*
* @return True if the solution is optimal; otherwise false.
*/
bool verify()
{
return (verify_vertex_mate()
&& verify_vertex_duals()
&& verify_blossom_duals()
&& verify_blossoms_and_calc_edge_duals()
&& verify_edge_slack());
}
private:
using EdgeT = Edge<WeightType>;
using BlossomT = Blossom<WeightType>;
using NonTrivialBlossomT = NonTrivialBlossom<WeightType>;
// Reference to the MatchingContext instance.
const MatchingContext<WeightType>& ctx;
// For each edge, the sum of duals of its incident vertices
// and duals of all blossoms that contain the edge.
std::vector<WeightType> edge_duals;
MatchingVerifier(const MatchingContext<WeightType>& ctx)
: ctx(ctx),
edge_duals(ctx.edges.size())
{ }
static bool checked_add(WeightType& result, WeightType a, WeightType b)
{
if (a > std::numeric_limits<WeightType>::max() - b) {
@ -1778,7 +1818,7 @@ struct MatchingVerifier
/** Convert edge pointer to its index in the vector "edges". */
std::size_t edge_index(const EdgeT* edge)
{
return edge - ctx.edges.data();
return edge - graph.edges.data();
}
/** Check that the array "vertex_mate" is consistent. */
@ -1786,7 +1826,7 @@ struct MatchingVerifier
{
// Count matched vertices and check symmetry of "vertex_mate".
VertexId num_matched_vertex = 0;
for (VertexId x = 0; x < ctx.num_vertex; ++x) {
for (VertexId x = 0; x < graph.num_vertex; ++x) {
VertexId y = ctx.vertex_mate[x];
if (y != NO_VERTEX) {
++num_matched_vertex;
@ -1798,7 +1838,7 @@ struct MatchingVerifier
// Count matched edges.
VertexId num_matched_edge = 0;
for (const EdgeT& edge : ctx.edges) {
for (const EdgeT& edge : graph.edges) {
if (ctx.vertex_mate[edge.vt.first] == edge.vt.second) {
++num_matched_edge;
}
@ -1814,7 +1854,7 @@ struct MatchingVerifier
*/
bool verify_vertex_duals()
{
for (VertexId x = 0; x < ctx.num_vertex; ++x) {
for (VertexId x = 0; x < graph.num_vertex; ++x) {
if (ctx.vertex_dual[x] < 0) {
return false;
}
@ -1859,7 +1899,7 @@ struct MatchingVerifier
// For each vertex "x",
// "vertex_depth[x]" is the depth of the smallest blossom on
// the current descent path that contains "x".
std::vector<VertexId> vertex_depth(ctx.num_vertex);
std::vector<VertexId> vertex_depth(graph.num_vertex);
// At each depth, keep track of the sum of blossom duals
// along the current descent path.
@ -1922,7 +1962,7 @@ struct MatchingVerifier
// For each incident edge, find the smallest blossom
// that contains it.
VertexId x = sub->base_vertex;
for (const EdgeT* edge : ctx.adjacent_edges[x]) {
for (const EdgeT* edge : graph.adjacent_edges[x]) {
// Only consider edges pointing out from "x".
if (edge->vt.first == x) {
VertexId y = edge->vt.second;
@ -1991,7 +2031,7 @@ struct MatchingVerifier
bool verify_blossoms_and_calc_edge_duals()
{
// For each edge, calculate the sum of its vertex duals.
for (const EdgeT& edge : ctx.edges) {
for (const EdgeT& edge : graph.edges) {
if (checked_add(edge_duals[edge_index(&edge)],
ctx.vertex_dual[edge.vt.first],
ctx.vertex_dual[edge.vt.second])) {
@ -2022,7 +2062,7 @@ struct MatchingVerifier
*/
bool verify_edge_slack()
{
for (const EdgeT& edge : ctx.edges) {
for (const EdgeT& edge : graph.edges) {
WeightType duals = edge_duals[edge_index(&edge)];
WeightType weight = ctx.weight_factor * edge.weight;
@ -2040,21 +2080,18 @@ struct MatchingVerifier
return true;
}
private:
/** Reference to the MatchingContext instance. */
const MatchingContext<WeightType>& ctx;
/** Reference to the input graph. */
const Graph<WeightType>& graph;
/**
* Verify that the optimum solution has been found.
*
* This function takes time O(n**2).
*
* @return True if the solution is optimal; otherwise false.
* For each edge, the sum of duals of its incident vertices
* and duals of all blossoms that contain the edge.
*/
bool verify()
{
return (verify_vertex_mate()
&& verify_vertex_duals()
&& verify_blossom_duals()
&& verify_blossoms_and_calc_edge_duals()
&& verify_edge_slack());
}
std::vector<WeightType> edge_duals;
};
@ -2114,7 +2151,7 @@ std::vector<VertexPair> maximum_weight_matching(
// Extract the matched edges.
std::vector<VertexPair> solution;
for (const Edge<WeightType>& edge : matching.edges) {
for (const Edge<WeightType>& edge : matching.graph.edges) {
if (matching.vertex_mate[edge.vt.first] == edge.vt.second) {
solution.push_back(edge.vt);
}