2023-05-10 20:56:30 +02:00
|
|
|
/**
|
|
|
|
* Algorithm for finding a maximum weight matching in general graphs.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#ifndef MWMATCHING_H_
|
|
|
|
#define MWMATCHING_H_
|
|
|
|
|
|
|
|
#include <algorithm>
|
|
|
|
#include <cassert>
|
|
|
|
#include <cmath>
|
|
|
|
#include <cstddef>
|
|
|
|
#include <deque>
|
|
|
|
#include <limits>
|
|
|
|
#include <list>
|
|
|
|
#include <stack>
|
|
|
|
#include <stdexcept>
|
|
|
|
#include <tuple>
|
|
|
|
#include <utility>
|
|
|
|
#include <vector>
|
|
|
|
|
|
|
|
|
|
|
|
namespace mwmatching {
|
|
|
|
|
2023-05-16 22:19:55 +02:00
|
|
|
/* **************************************************
|
|
|
|
* ** public definitions **
|
|
|
|
* ************************************************** */
|
2023-05-10 20:56:30 +02:00
|
|
|
|
|
|
|
/** Type representing the unique ID of a vertex. */
|
|
|
|
using VertexId = unsigned int;
|
|
|
|
|
|
|
|
/** Type representing a pair of vertices. */
|
|
|
|
using VertexPair = std::pair<VertexId, VertexId>;
|
|
|
|
|
|
|
|
|
|
|
|
/** Type representing a weighted edge.
|
|
|
|
*
|
|
|
|
* @tparam WeightType Numeric type used to represent edge weights.
|
|
|
|
*/
|
|
|
|
template <typename WeightType>
|
|
|
|
struct Edge
|
|
|
|
{
|
|
|
|
static_assert(std::numeric_limits<WeightType>::is_specialized,
|
|
|
|
"Edge weight must be a numeric type");
|
|
|
|
|
|
|
|
/** Incident vertices. */
|
|
|
|
VertexPair vt;
|
|
|
|
|
|
|
|
/** Edge weight. */
|
|
|
|
WeightType weight;
|
|
|
|
|
|
|
|
Edge(VertexPair vt, WeightType w)
|
|
|
|
: vt(vt), weight(w)
|
|
|
|
{ }
|
|
|
|
|
|
|
|
Edge(VertexId x, VertexId y, WeightType w)
|
|
|
|
: vt(x, y), weight(w)
|
|
|
|
{ }
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
namespace impl {
|
|
|
|
|
2023-05-16 22:19:55 +02:00
|
|
|
/* **************************************************
|
|
|
|
* ** private definitions **
|
|
|
|
* ************************************************** */
|
2023-05-10 20:56:30 +02:00
|
|
|
|
|
|
|
/** Value used to mark an invalid or undefined vertex. */
|
|
|
|
constexpr VertexId NO_VERTEX = std::numeric_limits<VertexId>::max();
|
|
|
|
|
|
|
|
|
|
|
|
/** Top-level blossoms may be labeled "S" or "T" or unlabeled. */
|
|
|
|
enum BlossomLabel { LABEL_NONE = 0, LABEL_S = 1, LABEL_T = 2 };
|
|
|
|
|
|
|
|
|
2023-05-16 22:19:55 +02:00
|
|
|
/* **************************************************
|
|
|
|
* ** private helper functions **
|
|
|
|
* ************************************************** */
|
|
|
|
|
2023-05-10 20:56:30 +02:00
|
|
|
/** Return a pair of vertices in flipped order. */
|
|
|
|
inline VertexPair flip_vertex_pair(const VertexPair& vt)
|
|
|
|
{
|
|
|
|
return std::make_pair(vt.second, vt.first);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check that the input is a valid graph.
|
|
|
|
*
|
|
|
|
* The graph may not have self-edges or multiple edges between
|
|
|
|
* the same pair of vertices. Edge weights must be within a valid range.
|
|
|
|
*
|
2023-05-18 09:55:55 +02:00
|
|
|
* This function takes time O(m * log(m)).
|
2023-05-10 20:56:30 +02:00
|
|
|
*
|
|
|
|
* @param edges Vector of weighted edges defining the graph.
|
|
|
|
* @throw std::invalid_argument If the input graph is not valid.
|
|
|
|
*/
|
|
|
|
template <typename WeightType>
|
|
|
|
void check_input_graph(const std::vector<Edge<WeightType>>& edges)
|
|
|
|
{
|
|
|
|
const VertexId max_num_vertex = std::numeric_limits<VertexId>::max();
|
|
|
|
|
|
|
|
for (const Edge<WeightType>& edge : edges) {
|
|
|
|
|
|
|
|
// Check that vertex IDs are valid.
|
|
|
|
if ((edge.vt.first >= max_num_vertex)
|
|
|
|
|| (edge.vt.second >= max_num_vertex)) {
|
|
|
|
throw std::invalid_argument("Vertex ID out of range");
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check that edge weight is a finite number.
|
|
|
|
if (! std::numeric_limits<WeightType>::is_integer) {
|
|
|
|
if (! std::isfinite(edge.weight)) {
|
|
|
|
throw std::invalid_argument(
|
|
|
|
"Edge weights must be finite numbers");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check that edge weight will not cause overflow.
|
|
|
|
if (edge.weight > std::numeric_limits<WeightType>::max() / 4) {
|
|
|
|
throw std::invalid_argument(
|
|
|
|
"Edge weight exceeds maximum supported value");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check that the graph has no self-edges.
|
|
|
|
for (const Edge<WeightType>& edge : edges) {
|
|
|
|
if (edge.vt.first == edge.vt.second) {
|
|
|
|
throw std::invalid_argument("Self-edges are not supported");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check that the graph does not contain duplicate edges.
|
2023-05-18 09:55:55 +02:00
|
|
|
std::vector<VertexPair> edge_endpoints;
|
|
|
|
edge_endpoints.reserve(edges.size());
|
2023-05-10 20:56:30 +02:00
|
|
|
for (const Edge<WeightType>& edge : edges) {
|
|
|
|
VertexPair vt = edge.vt;
|
|
|
|
if (vt.first > vt.second) {
|
2023-05-18 09:55:55 +02:00
|
|
|
std::swap(vt.first, vt.second);
|
2023-05-10 20:56:30 +02:00
|
|
|
}
|
2023-05-18 09:55:55 +02:00
|
|
|
edge_endpoints.push_back(vt);
|
|
|
|
}
|
|
|
|
|
|
|
|
std::sort(edge_endpoints.begin(), edge_endpoints.end());
|
|
|
|
if (std::unique(edge_endpoints.begin(), edge_endpoints.end())
|
|
|
|
!= edge_endpoints.end()) {
|
|
|
|
throw std::invalid_argument("Duplicate edges are not supported");
|
2023-05-10 20:56:30 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2023-05-27 23:29:05 +02:00
|
|
|
/* **************************************************
|
|
|
|
* ** struct Graph **
|
|
|
|
* ************************************************** */
|
|
|
|
|
2023-05-10 20:56:30 +02:00
|
|
|
/**
|
2023-05-27 23:29:05 +02:00
|
|
|
* Representation of the input graph.
|
2023-05-10 20:56:30 +02:00
|
|
|
*/
|
|
|
|
template <typename WeightType>
|
2023-05-27 23:29:05 +02:00
|
|
|
struct Graph
|
2023-05-10 20:56:30 +02:00
|
|
|
{
|
2023-05-27 23:29:05 +02:00
|
|
|
using EdgeT = Edge<WeightType>;
|
2023-05-10 20:56:30 +02:00
|
|
|
|
2023-05-27 23:29:05 +02:00
|
|
|
/** 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.
|
|
|
|
*
|
|
|
|
* This function takes time O(m).
|
|
|
|
*
|
|
|
|
* @param edges Vector of weighted edges defining the graph.
|
|
|
|
* @return Vector of edges with non-negative weight.
|
|
|
|
*/
|
|
|
|
static std::vector<EdgeT> remove_negative_weight_edges(
|
|
|
|
const std::vector<EdgeT>& edges_in)
|
|
|
|
{
|
|
|
|
std::vector<EdgeT> real_edges;
|
|
|
|
real_edges.reserve(edges_in.size());
|
|
|
|
|
|
|
|
for (const Edge<WeightType>& edge : edges_in) {
|
|
|
|
if (edge.weight >= 0) {
|
|
|
|
real_edges.push_back(edge);
|
|
|
|
}
|
2023-05-10 20:56:30 +02:00
|
|
|
}
|
2023-05-27 23:29:05 +02:00
|
|
|
|
|
|
|
return real_edges;
|
2023-05-10 20:56:30 +02:00
|
|
|
}
|
|
|
|
|
2023-05-27 23:29:05 +02:00
|
|
|
/** 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;
|
|
|
|
}
|
|
|
|
};
|
2023-05-10 20:56:30 +02:00
|
|
|
|
|
|
|
|
2023-05-16 22:19:55 +02:00
|
|
|
/* **************************************************
|
|
|
|
* ** struct Blossom **
|
|
|
|
* ************************************************** */
|
|
|
|
|
2023-05-10 20:56:30 +02:00
|
|
|
// Forward declaration.
|
|
|
|
template <typename WeightType> struct NonTrivialBlossom;
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Represents a blossom.
|
|
|
|
*
|
|
|
|
* A blossom is an odd-length alternating cycle over sub-blossoms.
|
|
|
|
* An alternating path consists of alternating matched and unmatched edges.
|
|
|
|
* An alternating cycle is an alternating path that starts and ends in
|
|
|
|
* the same sub-blossom.
|
|
|
|
*
|
|
|
|
* A single vertex by itself is also a blossom: a "trivial blossom".
|
|
|
|
*
|
|
|
|
* An instance of this class represents either a trivial blossom,
|
|
|
|
* or a non-trivial blossom which consists of multiple sub-blossoms.
|
|
|
|
*
|
|
|
|
* Blossoms are recursive structures: A non-trivial blossoms contains
|
|
|
|
* sub-blossoms, which may themselves contain sub-blossoms etc.
|
|
|
|
*
|
|
|
|
* Each blossom contains exactly one vertex that is not matched to another
|
|
|
|
* vertex in the same blossom. This is the "base vertex" of the blossom.
|
|
|
|
*/
|
|
|
|
template <typename WeightType>
|
2023-05-12 21:50:07 +02:00
|
|
|
struct Blossom
|
|
|
|
{
|
2023-05-10 20:56:30 +02:00
|
|
|
/** Parent of this blossom, or "nullptr" if this blossom is top-level. */
|
|
|
|
NonTrivialBlossom<WeightType>* parent;
|
|
|
|
|
|
|
|
/** Index of the base vertex of this blossom. */
|
|
|
|
VertexId base_vertex;
|
|
|
|
|
|
|
|
/** Label S or T if this is a top-level blossom in an alternating tree. */
|
|
|
|
BlossomLabel label;
|
|
|
|
|
2023-05-13 15:22:21 +02:00
|
|
|
/** True if this is an instance of NonTrivialBlossom. */
|
|
|
|
const bool is_nontrivial_blossom;
|
|
|
|
|
2023-05-10 20:56:30 +02:00
|
|
|
/** Optional edge that attaches this blossom to the alternating tree. */
|
|
|
|
VertexPair tree_edge;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* In case of a top-level S-blossom, "best_edge" is the least-slack edge
|
|
|
|
* that links to a different S-blossom, or "nullptr" if no such edge
|
|
|
|
* has been found.
|
|
|
|
*/
|
|
|
|
const Edge<WeightType>* best_edge;
|
|
|
|
|
2023-05-13 15:22:21 +02:00
|
|
|
protected:
|
|
|
|
/** Initialize base class. */
|
|
|
|
Blossom(VertexId base_vertex, bool is_nontrivial_blossom)
|
2023-05-10 20:56:30 +02:00
|
|
|
: parent(nullptr),
|
|
|
|
base_vertex(base_vertex),
|
|
|
|
label(LABEL_NONE),
|
2023-05-13 15:22:21 +02:00
|
|
|
is_nontrivial_blossom(is_nontrivial_blossom),
|
2023-05-10 20:56:30 +02:00
|
|
|
best_edge(nullptr)
|
|
|
|
{ }
|
|
|
|
|
2023-05-13 15:22:21 +02:00
|
|
|
public:
|
|
|
|
/** Initialize a trivial (single-vertex) blossom. */
|
|
|
|
explicit Blossom(VertexId base_vertex)
|
|
|
|
: Blossom(base_vertex, false)
|
|
|
|
{ }
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Cast this Blossom instance to NonTrivialBlossom if possible,
|
|
|
|
* otherwise return "nullptr".
|
|
|
|
*/
|
|
|
|
NonTrivialBlossom<WeightType>* nontrivial()
|
|
|
|
{
|
|
|
|
return (is_nontrivial_blossom ?
|
|
|
|
static_cast<NonTrivialBlossom<WeightType>*>(this)
|
|
|
|
: nullptr);
|
|
|
|
}
|
2023-05-16 22:19:55 +02:00
|
|
|
|
|
|
|
const NonTrivialBlossom<WeightType>* nontrivial() const
|
|
|
|
{
|
|
|
|
return (is_nontrivial_blossom ?
|
|
|
|
static_cast<const NonTrivialBlossom<WeightType>*>(this)
|
|
|
|
: nullptr);
|
|
|
|
}
|
2023-05-10 20:56:30 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
2023-05-16 22:19:55 +02:00
|
|
|
/* **************************************************
|
|
|
|
* ** struct NonTrivialBlossom **
|
|
|
|
* ************************************************** */
|
|
|
|
|
2023-05-10 20:56:30 +02:00
|
|
|
/**
|
|
|
|
* Represents a non-trivial blossom.
|
|
|
|
*
|
|
|
|
* A non-trivial blossom is a blossom that contains multiple sub-blossoms
|
|
|
|
* (at least 3 sub-blossoms, since all blossoms have odd length).
|
|
|
|
*
|
|
|
|
* Non-trivial blossoms maintain a list of their sub-blossoms and the edges
|
|
|
|
* between their subblossoms.
|
|
|
|
*
|
|
|
|
* Unlike trivial blossoms, each non-trivial blossom is associated with
|
|
|
|
* a variable in the dual LPP problem.
|
|
|
|
*
|
|
|
|
* Non-trivial blossoms are created and destroyed by the matching algorithm.
|
|
|
|
* This implies that not every odd-length alternating cycle is a blossom;
|
|
|
|
* it only becomes a blossom through an explicit action of the algorithm.
|
|
|
|
* An existing blossom may change when the matching is augmented along
|
|
|
|
* a path that runs through the blossom.
|
|
|
|
*/
|
|
|
|
template <typename WeightType>
|
|
|
|
struct NonTrivialBlossom : public Blossom<WeightType>
|
|
|
|
{
|
|
|
|
struct SubBlossom {
|
|
|
|
/** Pointer to sub-blossom. */
|
|
|
|
Blossom<WeightType>* blossom;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Ordered pair of vertices (x, y) where "x" is a vertex in "blossom"
|
|
|
|
* and "y" is a vertex in the next sub-blossom.
|
|
|
|
*/
|
|
|
|
VertexPair edge;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* List of sub-blossoms.
|
|
|
|
* Ordered by their appearance in the alternating cycle.
|
|
|
|
* First blossom in the list is the start/end of the alternating cycle
|
|
|
|
* and contains the base vertex.
|
|
|
|
*/
|
|
|
|
std::list<SubBlossom> subblossoms;
|
|
|
|
|
|
|
|
/** Dual LPP variable for this blossom. */
|
|
|
|
WeightType dual_var;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* In case of a top-level S-blossom, "best_edge_set" is a list of
|
|
|
|
* least-slack edges between this blossom and other S-blossoms.
|
|
|
|
*/
|
|
|
|
std::list<const Edge<WeightType>*> best_edge_set;
|
|
|
|
|
|
|
|
/** Initialize a non-trivial blossom. */
|
|
|
|
NonTrivialBlossom(
|
|
|
|
const std::vector<Blossom<WeightType>*>& subblossoms,
|
|
|
|
const std::deque<VertexPair>& edges)
|
2023-05-13 15:22:21 +02:00
|
|
|
: Blossom<WeightType>(subblossoms.front()->base_vertex, true),
|
2023-05-10 20:56:30 +02:00
|
|
|
dual_var(0)
|
|
|
|
{
|
|
|
|
assert(subblossoms.size() == edges.size());
|
|
|
|
assert(subblossoms.size() % 2 == 1);
|
|
|
|
assert(subblossoms.size() >= 3);
|
|
|
|
|
|
|
|
auto blossom_it = subblossoms.begin();
|
|
|
|
auto blossom_end = subblossoms.end();
|
|
|
|
auto edge_it = edges.begin();
|
|
|
|
while (blossom_it != blossom_end) {
|
|
|
|
this->subblossoms.emplace_back();
|
|
|
|
this->subblossoms.back().blossom = *blossom_it;
|
|
|
|
this->subblossoms.back().edge = *edge_it;
|
|
|
|
++blossom_it;
|
|
|
|
++edge_it;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Find the position of the specified subblossom. */
|
|
|
|
std::pair<VertexId, typename std::list<SubBlossom>::iterator>
|
|
|
|
find_subblossom(Blossom<WeightType>* subblossom)
|
|
|
|
{
|
|
|
|
VertexId pos = 0;
|
|
|
|
auto it = subblossoms.begin();
|
|
|
|
while (it->blossom != subblossom) {
|
|
|
|
++it;
|
|
|
|
++pos;
|
|
|
|
assert(it != subblossoms.end());
|
|
|
|
}
|
|
|
|
return make_pair(pos, it);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
|
2023-05-16 22:19:55 +02:00
|
|
|
/** Call a function for every vertex inside the specified blossom. */
|
|
|
|
template <typename WeightType, typename Func>
|
|
|
|
inline void for_vertices_in_blossom(const Blossom<WeightType>* blossom, Func func)
|
|
|
|
{
|
|
|
|
const NonTrivialBlossom<WeightType>* ntb = blossom->nontrivial();
|
|
|
|
if (ntb) {
|
|
|
|
// Visit all vertices in the non-trivial blossom.
|
|
|
|
// Use an explicit stack to avoid deep call chains.
|
|
|
|
std::vector<const NonTrivialBlossom<WeightType>*> stack;
|
|
|
|
stack.push_back(ntb);
|
|
|
|
|
|
|
|
while (! stack.empty()) {
|
|
|
|
const NonTrivialBlossom<WeightType>* b = stack.back();
|
|
|
|
stack.pop_back();
|
|
|
|
|
|
|
|
for (const auto& sub : b->subblossoms) {
|
|
|
|
ntb = sub.blossom->nontrivial();
|
|
|
|
if (ntb) {
|
|
|
|
stack.push_back(ntb);
|
|
|
|
} else {
|
|
|
|
func(sub.blossom->base_vertex);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
// A trivial blossom contains just one vertex.
|
|
|
|
func(blossom->base_vertex);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2023-05-10 20:56:30 +02:00
|
|
|
/**
|
|
|
|
* Represents a list of edges forming an alternating path or
|
|
|
|
* an alternating cycle.
|
|
|
|
*
|
|
|
|
* The path is defined over top-level blossoms; it skips parts of the path
|
|
|
|
* that are internal to blossoms. Vertex pairs are oriented to match the
|
|
|
|
* direction of the path.
|
|
|
|
*/
|
|
|
|
struct AlternatingPath
|
|
|
|
{
|
|
|
|
std::deque<VertexPair> edges;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
2023-05-16 22:19:55 +02:00
|
|
|
/* **************************************************
|
|
|
|
* ** struct MatchingContext **
|
|
|
|
* ************************************************** */
|
|
|
|
|
2023-05-10 20:56:30 +02:00
|
|
|
/**
|
|
|
|
* This class holds all data used by the matching algorithm.
|
|
|
|
*
|
|
|
|
* It contains the input graph, a partial solution, and several
|
|
|
|
* auxiliary data structures.
|
|
|
|
*/
|
|
|
|
template <typename WeightType>
|
2023-05-27 23:29:05 +02:00
|
|
|
class MatchingContext
|
2023-05-10 20:56:30 +02:00
|
|
|
{
|
2023-05-27 23:29:05 +02:00
|
|
|
public:
|
|
|
|
|
2023-05-10 20:56:30 +02:00
|
|
|
using EdgeT = Edge<WeightType>;
|
|
|
|
using BlossomT = Blossom<WeightType>;
|
|
|
|
using NonTrivialBlossomT = NonTrivialBlossom<WeightType>;
|
|
|
|
|
|
|
|
/** Specification of a delta step. */
|
|
|
|
struct DeltaStep
|
|
|
|
{
|
|
|
|
/** Type of delta step: 1, 2, 3 or 4. */
|
|
|
|
int kind;
|
|
|
|
|
|
|
|
/** Delta value. */
|
|
|
|
WeightType value;
|
|
|
|
|
|
|
|
/** Edge on which the minimum delta occurs (for delta type 2 or 3). */
|
|
|
|
VertexPair edge;
|
|
|
|
|
|
|
|
/** Blossom on which the minimum delta occurs (for delta type 4). */
|
|
|
|
NonTrivialBlossomT* blossom;
|
|
|
|
};
|
|
|
|
|
|
|
|
/** Scale integer edge weights to enable integer calculations. */
|
|
|
|
static constexpr WeightType weight_factor =
|
|
|
|
std::numeric_limits<WeightType>::is_integer ? 2 : 1;
|
|
|
|
|
2023-05-27 23:29:05 +02:00
|
|
|
/** Input graph. */
|
|
|
|
const Graph<WeightType> graph;
|
2023-05-10 20:56:30 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Current state of the matching.
|
|
|
|
*
|
|
|
|
* vertex_mate[x] == y if vertex "x" is matched to vertex "y".
|
|
|
|
* vertex_mate[x] == NO_VERTEX if vertex "x" is unmatched.
|
|
|
|
*/
|
|
|
|
std::vector<VertexId> vertex_mate;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Each vertex is associated with a trivial blossom.
|
|
|
|
*
|
|
|
|
* "trivial_blossom[x]" is the trivial blossom that contains only
|
|
|
|
* vertex "x".
|
|
|
|
*/
|
|
|
|
std::vector<BlossomT> trivial_blossom;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Non-trivial blossoms may be created and destroyed during
|
|
|
|
* the course of the algorithm.
|
|
|
|
*/
|
|
|
|
// NOTE - this MUST be a list, because we delete items from it while keeping pointers to other items
|
|
|
|
std::list<NonTrivialBlossomT> nontrivial_blossom;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Every vertex is contained in exactly one top-level blossom
|
|
|
|
* (possibly the trivial blossom that contains just that vertex).
|
|
|
|
*
|
|
|
|
* "vertex_top_blossom[x]" is the top-level blossom that contains
|
|
|
|
* vertex "x".
|
|
|
|
*/
|
|
|
|
std::vector<BlossomT*> vertex_top_blossom;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Every vertex has a variable in the dual LPP.
|
|
|
|
*
|
|
|
|
* "vertex_dual[x]" is the dual variable of vertex "x".
|
|
|
|
*/
|
|
|
|
std::vector<WeightType> vertex_dual;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* In case of T-vertex or unlabeled vertex "x",
|
|
|
|
* "vertex_best_edge[x]" is the least-slack edge to any S-vertex,
|
|
|
|
* or "nullptr" if no such edge has been found.
|
|
|
|
*/
|
|
|
|
std::vector<const EdgeT*> vertex_best_edge;
|
|
|
|
|
|
|
|
/** Queue of S-vertices to be scanned. */
|
|
|
|
std::deque<VertexId> queue;
|
|
|
|
|
|
|
|
/** Markers placed while tracing an alternating path. */
|
|
|
|
std::vector<bool> vertex_marker;
|
|
|
|
|
|
|
|
/** Vertices marked while tracing an alternating path. */
|
|
|
|
std::vector<VertexId> marked_vertex;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Initialize the matching algorithm.
|
|
|
|
*
|
|
|
|
* This function takes time O(n + m).
|
|
|
|
*/
|
|
|
|
explicit MatchingContext(const std::vector<EdgeT>& edges_in)
|
2023-05-27 23:29:05 +02:00
|
|
|
: graph(edges_in)
|
2023-05-10 20:56:30 +02:00
|
|
|
{
|
|
|
|
// Initially all vertices are unmatched.
|
2023-05-27 23:29:05 +02:00
|
|
|
vertex_mate.resize(graph.num_vertex, NO_VERTEX);
|
2023-05-10 20:56:30 +02:00
|
|
|
|
|
|
|
// Create a trivial blossom for each vertex.
|
2023-05-27 23:29:05 +02:00
|
|
|
trivial_blossom.reserve(graph.num_vertex);
|
|
|
|
for (VertexId x = 0; x < graph.num_vertex; ++x) {
|
2023-05-10 20:56:30 +02:00
|
|
|
trivial_blossom.emplace_back(x);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Initially all vertices are trivial top-level blossoms.
|
2023-05-27 23:29:05 +02:00
|
|
|
vertex_top_blossom.reserve(graph.num_vertex);
|
|
|
|
for (VertexId x = 0; x < graph.num_vertex; ++x) {
|
2023-05-10 20:56:30 +02:00
|
|
|
vertex_top_blossom.push_back(&trivial_blossom[x]);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Vertex duals are initialized to half the maximum edge weight.
|
|
|
|
WeightType max_weight = 0;
|
2023-05-27 23:29:05 +02:00
|
|
|
for (const EdgeT& edge : graph.edges) {
|
2023-05-10 20:56:30 +02:00
|
|
|
max_weight = std::max(max_weight, edge.weight);
|
|
|
|
}
|
|
|
|
WeightType init_vertex_dual = max_weight * (weight_factor / 2);
|
2023-05-27 23:29:05 +02:00
|
|
|
vertex_dual.resize(graph.num_vertex, init_vertex_dual);
|
2023-05-10 20:56:30 +02:00
|
|
|
|
|
|
|
// Initialize "vertex_best_edge".
|
2023-05-27 23:29:05 +02:00
|
|
|
vertex_best_edge.resize(graph.num_vertex, nullptr);
|
2023-05-10 20:56:30 +02:00
|
|
|
|
|
|
|
// Allocate temporary arrays for path tracing.
|
2023-05-27 23:29:05 +02:00
|
|
|
vertex_marker.resize(graph.num_vertex);
|
|
|
|
marked_vertex.reserve(graph.num_vertex);
|
2023-05-10 20:56:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Prevent copying.
|
|
|
|
MatchingContext(const MatchingContext&) = delete;
|
|
|
|
MatchingContext& operator=(const MatchingContext&) = delete;
|
|
|
|
|
2023-05-12 21:50:07 +02:00
|
|
|
/* ********** General support routines: ********** */
|
2023-05-10 20:56:30 +02:00
|
|
|
|
|
|
|
/** Calculate edge slack. */
|
|
|
|
WeightType edge_slack(const EdgeT& edge) const
|
|
|
|
{
|
|
|
|
VertexId x = edge.vt.first;
|
|
|
|
VertexId y = edge.vt.second;
|
|
|
|
assert(vertex_top_blossom[x] != vertex_top_blossom[y]);
|
|
|
|
return vertex_dual[x] + vertex_dual[y] - weight_factor * edge.weight;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Least-slack edge tracking:
|
|
|
|
*
|
|
|
|
* To calculate delta steps, the matching algorithm needs to find
|
|
|
|
* - the least-slack edge between any S-vertex and an unlabeled vertex;
|
|
|
|
* - the least-slack edge between any pair of top-level S-blossoms.
|
|
|
|
*
|
|
|
|
* For each unlabeled vertex and each T-vertex, we keep track of the
|
|
|
|
* least-slack edge to any S-vertex. Tracking for unlabeled vertices
|
|
|
|
* serves to provide the least-slack edge for the delta step.
|
|
|
|
* Tracking for T-vertices is done because such vertices can turn into
|
|
|
|
* unlabeled vertices if they are part of a T-blossom that gets expanded.
|
|
|
|
*
|
|
|
|
* For each top-level S-blossom, we keep track of the least-slack edge
|
|
|
|
* to any S-vertex not in the same blossom.
|
|
|
|
*
|
|
|
|
* Furthermore, for each top-level S-blossom, we keep a list of
|
|
|
|
* least-slack edges to other top-level S-blossoms. For any pair of
|
|
|
|
* top-level S-blossoms, the least-slack edge between them is contained
|
|
|
|
* in the edge list of at least one of the blossoms. An edge list may
|
|
|
|
* contain multiple edges to the same S-blossom. Such redundant edges are
|
|
|
|
* pruned during blossom merging to limit the number of tracked edges.
|
|
|
|
*
|
|
|
|
* Note: For a given vertex or blossom, the identity of the least-slack
|
|
|
|
* edge to any S-blossom remains unchanged during a delta step.
|
|
|
|
* Although the delta step changes edge slacks, it changes the slack
|
|
|
|
* of every edge to an S-vertex by the same amount. Therefore the edge
|
|
|
|
* that had least slack before the delta step, will still have least slack
|
|
|
|
* after the delta step.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Reset least-slack edge tracking.
|
|
|
|
*
|
|
|
|
* This function takes time O(n + m).
|
|
|
|
*/
|
|
|
|
void lset_reset()
|
|
|
|
{
|
2023-05-27 23:29:05 +02:00
|
|
|
for (VertexId x = 0; x < graph.num_vertex; ++x) {
|
2023-05-10 20:56:30 +02:00
|
|
|
vertex_best_edge[x] = nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (BlossomT& blossom : trivial_blossom) {
|
|
|
|
blossom.best_edge = nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (NonTrivialBlossomT& blossom : nontrivial_blossom) {
|
|
|
|
blossom.best_edge = nullptr;
|
|
|
|
blossom.best_edge_set.clear();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Add edge "e" from an S-vertex to unlabeled vertex or T-vertex "y".
|
|
|
|
*
|
|
|
|
* This function takes time O(1) per call.
|
|
|
|
* This function is called O(m) times per stage.
|
|
|
|
*/
|
|
|
|
void lset_add_vertex_edge(VertexId y, const EdgeT* edge, WeightType slack)
|
|
|
|
{
|
|
|
|
const EdgeT* cur_best_edge = vertex_best_edge[y];
|
|
|
|
if ((cur_best_edge == nullptr)
|
|
|
|
|| (slack < edge_slack(*cur_best_edge))) {
|
|
|
|
vertex_best_edge[y] = edge;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return the index and slack of the least-slack edge between
|
|
|
|
* any S-vertex and unlabeled vertex.
|
|
|
|
*
|
|
|
|
* This function takes time O(n) per call.
|
|
|
|
* This function takes total time O(n**2) per stage.
|
|
|
|
*
|
|
|
|
* @return Pair (edge, slack) if there is a least-slack edge,
|
|
|
|
* or (nullptr, 0) if there is no suitable edge.
|
|
|
|
*/
|
|
|
|
std::pair<const EdgeT*, WeightType> lset_get_best_vertex_edge()
|
|
|
|
{
|
|
|
|
const EdgeT* best_edge = nullptr;
|
|
|
|
WeightType best_slack = 0;
|
|
|
|
|
2023-05-27 23:29:05 +02:00
|
|
|
for (VertexId x = 0; x < graph.num_vertex; ++x) {
|
2023-05-10 20:56:30 +02:00
|
|
|
if (vertex_top_blossom[x]->label == LABEL_NONE) {
|
|
|
|
const EdgeT* edge = vertex_best_edge[x];
|
|
|
|
if (edge != nullptr) {
|
|
|
|
WeightType slack = edge_slack(*edge);
|
|
|
|
if ((best_edge == nullptr) || (slack < best_slack)) {
|
|
|
|
best_edge = edge;
|
|
|
|
best_slack = slack;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return std::make_pair(best_edge, best_slack);
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Start tracking edges for a new S-blossom. */
|
|
|
|
void lset_new_blossom(BlossomT* blossom)
|
|
|
|
{
|
|
|
|
assert(blossom->best_edge == nullptr);
|
2023-05-13 20:35:08 +02:00
|
|
|
assert((blossom->nontrivial() == nullptr)
|
|
|
|
|| blossom->nontrivial()->best_edge_set.empty());
|
2023-05-10 20:56:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Add edge "e" between the specified S-blossom and another S-blossom.
|
|
|
|
*
|
|
|
|
* This function takes time O(1) per call.
|
|
|
|
* This function is called O(m) times per stage.
|
|
|
|
*/
|
|
|
|
void lset_add_blossom_edge(BlossomT* blossom,
|
|
|
|
const EdgeT* edge,
|
|
|
|
WeightType slack)
|
|
|
|
{
|
|
|
|
const EdgeT* cur_best_edge = blossom->best_edge;
|
|
|
|
if ((cur_best_edge == nullptr)
|
|
|
|
|| (slack < edge_slack(*cur_best_edge))) {
|
|
|
|
blossom->best_edge = edge;
|
|
|
|
}
|
|
|
|
|
2023-05-13 15:22:21 +02:00
|
|
|
NonTrivialBlossomT* ntb = blossom->nontrivial();
|
2023-05-10 20:56:30 +02:00
|
|
|
if (ntb) {
|
|
|
|
ntb->best_edge_set.push_back(edge);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Update least-slack edge tracking after merging sub-blossoms
|
|
|
|
* into a new S-blossom.
|
|
|
|
*
|
|
|
|
* This function takes total time O(n**2) per stage.
|
|
|
|
*/
|
|
|
|
void lset_merge_blossoms(NonTrivialBlossomT* blossom)
|
|
|
|
{
|
|
|
|
assert(blossom->best_edge == nullptr);
|
|
|
|
assert(blossom->best_edge_set.empty());
|
|
|
|
|
|
|
|
// Collect edges from the sub-blossoms that used to be S-blossoms.
|
|
|
|
for (auto& subblossom_item : blossom->subblossoms) {
|
|
|
|
BlossomT* sub = subblossom_item.blossom;
|
|
|
|
if (sub->label == LABEL_S) {
|
2023-05-13 15:22:21 +02:00
|
|
|
NonTrivialBlossomT* ntb = sub->nontrivial();
|
2023-05-10 20:56:30 +02:00
|
|
|
if (ntb) {
|
|
|
|
// Take least-slack edge set from this subblossom.
|
|
|
|
blossom->best_edge_set.splice(
|
|
|
|
blossom->best_edge_set.end(),
|
|
|
|
ntb->best_edge_set);
|
|
|
|
} else {
|
|
|
|
// Trivial blossoms don't maintain a least-slack edge set.
|
|
|
|
// Just consider all incident edges.
|
2023-05-27 23:29:05 +02:00
|
|
|
for (const EdgeT* edge : graph.adjacent_edges[sub->base_vertex]) {
|
2023-05-10 20:56:30 +02:00
|
|
|
// Only take edges between different S-blossoms.
|
|
|
|
VertexId x = edge->vt.first;
|
|
|
|
VertexId y = edge->vt.second;
|
|
|
|
BlossomT* bx = vertex_top_blossom[x];
|
|
|
|
BlossomT* by = vertex_top_blossom[y];
|
|
|
|
if ((bx != by)
|
|
|
|
&& (bx->label == LABEL_S)
|
|
|
|
&& (by->label == LABEL_S)) {
|
|
|
|
blossom->best_edge_set.push_back(edge);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Build a temporary array holding the least-slack edge index to
|
|
|
|
// each top-level S-blossom. This array is indexed by the base vertex
|
|
|
|
// of the blossoms.
|
|
|
|
std::vector<std::pair<const EdgeT*, WeightType>>
|
2023-05-27 23:29:05 +02:00
|
|
|
best_edge_to_blossom(graph.num_vertex);
|
2023-05-10 20:56:30 +02:00
|
|
|
|
|
|
|
for (const EdgeT* edge : blossom->best_edge_set) {
|
|
|
|
BlossomT* bx = vertex_top_blossom[edge->vt.first];
|
|
|
|
BlossomT* by = vertex_top_blossom[edge->vt.second];
|
|
|
|
assert(bx == blossom || by == blossom);
|
|
|
|
|
|
|
|
// Ignore internal edges.
|
|
|
|
if (bx != by) {
|
|
|
|
bx = (bx == blossom) ? by : bx;
|
|
|
|
|
|
|
|
// Only consider edges to S-blossoms.
|
|
|
|
if (bx->label == LABEL_S) {
|
|
|
|
|
|
|
|
// Keep only the least-slack edge to blossom "bx".
|
|
|
|
WeightType slack = edge_slack(*edge);
|
|
|
|
VertexId bx_base = bx->base_vertex;
|
|
|
|
auto& best_edge_item = best_edge_to_blossom[bx_base];
|
|
|
|
if ((best_edge_item.first == nullptr)
|
|
|
|
|| (slack < best_edge_item.second)) {
|
|
|
|
best_edge_item.first = edge;
|
|
|
|
best_edge_item.second = slack;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// Rebuild a compact list of least-slack edges.
|
|
|
|
// Also find the overall least-slack edge to any other S-blossom.
|
|
|
|
WeightType best_slack = 0;
|
|
|
|
blossom->best_edge_set.clear();
|
|
|
|
for (auto& best_edge_item : best_edge_to_blossom) {
|
|
|
|
const EdgeT* edge = best_edge_item.first;
|
|
|
|
if (edge != nullptr) {
|
|
|
|
blossom->best_edge_set.push_back(edge);
|
|
|
|
WeightType slack = best_edge_item.second;
|
|
|
|
if ((blossom->best_edge == nullptr) || (slack < best_slack)) {
|
|
|
|
blossom->best_edge = edge;
|
|
|
|
best_slack = slack;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return the index and slack of the least-slack edge between
|
|
|
|
* any pair of top-level S-blossoms.
|
|
|
|
*
|
|
|
|
* This function takes time O(n) per call.
|
|
|
|
* This function takes total time O(n**2) per stage.
|
|
|
|
*
|
|
|
|
* @return Pair (edge, slack) if there is a least-slack edge,
|
|
|
|
* or (nullptr, 0) if there is no suitable edge.
|
|
|
|
*/
|
|
|
|
std::pair<const EdgeT*, WeightType> lset_get_best_blossom_edge()
|
|
|
|
{
|
|
|
|
const EdgeT* best_edge = nullptr;
|
|
|
|
WeightType best_slack = 0;
|
|
|
|
|
|
|
|
auto consider_blossom =
|
|
|
|
[this,&best_edge,&best_slack](BlossomT* blossom)
|
|
|
|
{
|
|
|
|
if ((blossom->parent == nullptr) && (blossom->label == LABEL_S)) {
|
|
|
|
const EdgeT* edge = blossom->best_edge;
|
|
|
|
if (edge != nullptr) {
|
|
|
|
WeightType slack = edge_slack(*edge);
|
|
|
|
if ((best_edge == nullptr) || (slack < best_slack)) {
|
|
|
|
best_edge = edge;
|
|
|
|
best_slack = slack;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
for (BlossomT& blossom : trivial_blossom) {
|
|
|
|
consider_blossom(&blossom);
|
|
|
|
}
|
|
|
|
for (BlossomT& blossom : nontrivial_blossom) {
|
|
|
|
consider_blossom(&blossom);
|
|
|
|
}
|
|
|
|
|
|
|
|
return std::make_pair(best_edge, best_slack);
|
|
|
|
}
|
|
|
|
|
2023-05-12 21:50:07 +02:00
|
|
|
/* ********** Creating and expanding blossoms: ********** */
|
2023-05-10 20:56:30 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Trace back through the alternating trees from vertices "x" and "y".
|
|
|
|
*
|
|
|
|
* If both vertices are part of the same alternating tree, this function
|
|
|
|
* discovers a new blossom. In this case it returns an alternating path
|
|
|
|
* through the blossom that starts and ends in the same sub-blossom.
|
|
|
|
*
|
|
|
|
* If the vertices are part of different alternating trees, this function
|
|
|
|
* discovers an augmenting path. In this case it returns an alternating
|
|
|
|
* path that starts and ends in an unmatched vertex.
|
|
|
|
*
|
|
|
|
* This function takes time O(k) to discover a blossom, where "k" is the
|
|
|
|
* number of sub-blossoms, or time O(n) to discover an augmenting path.
|
|
|
|
*/
|
|
|
|
AlternatingPath trace_alternating_paths(VertexId x, VertexId y)
|
|
|
|
{
|
|
|
|
assert(vertex_top_blossom[x] != vertex_top_blossom[y]);
|
|
|
|
|
|
|
|
// Initialize a path containing only the edge (x, y).
|
|
|
|
AlternatingPath path;
|
|
|
|
path.edges.emplace_back(x, y);
|
|
|
|
|
|
|
|
// "first_common" is the first common ancestor of "x" and "y"
|
|
|
|
// in the alternating tree, or "nullptr" if no common ancestor
|
|
|
|
// has been found.
|
|
|
|
BlossomT* first_common = nullptr;
|
|
|
|
|
|
|
|
// Alternate between tracing the path from "x" and the path from "y".
|
|
|
|
// This ensures that the search time is bounded by the size of any
|
|
|
|
// newly found blossom.
|
|
|
|
while ((x != NO_VERTEX) || (y != NO_VERTEX)) {
|
|
|
|
|
|
|
|
// Trace path from vertex "x".
|
|
|
|
if (x != NO_VERTEX) {
|
|
|
|
|
|
|
|
// Stop if we found a common ancestor.
|
|
|
|
BlossomT* bx = vertex_top_blossom[x];
|
|
|
|
if (vertex_marker[bx->base_vertex]) {
|
|
|
|
first_common = bx;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Mark blossom as potential common ancestor.
|
|
|
|
vertex_marker[bx->base_vertex] = true;
|
|
|
|
marked_vertex.push_back(bx->base_vertex);
|
|
|
|
|
|
|
|
// Trace back to the parent in the alternating tree.
|
|
|
|
x = bx->tree_edge.first;
|
|
|
|
if (x != NO_VERTEX) {
|
|
|
|
path.edges.push_front(bx->tree_edge);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Trace path from vertex "y".
|
|
|
|
if (y != NO_VERTEX) {
|
|
|
|
|
|
|
|
// Stop if we found a common ancestor.
|
|
|
|
BlossomT* by = vertex_top_blossom[y];
|
|
|
|
if (vertex_marker[by->base_vertex]) {
|
|
|
|
first_common = by;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Mark blossom as potential common ancestor.
|
|
|
|
vertex_marker[by->base_vertex] = true;
|
|
|
|
marked_vertex.push_back(by->base_vertex);
|
|
|
|
|
|
|
|
// Trace back to the parent in the alternating tree.
|
|
|
|
y = by->tree_edge.first;
|
|
|
|
if (y != NO_VERTEX) {
|
|
|
|
path.edges.emplace_back(by->tree_edge.second, y);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remove all markers we placed.
|
|
|
|
for (VertexId k : marked_vertex) {
|
|
|
|
vertex_marker[k] = false;
|
|
|
|
}
|
|
|
|
marked_vertex.clear();
|
|
|
|
|
|
|
|
// If we found a common ancestor, trim the paths so they end there.
|
|
|
|
if (first_common) {
|
|
|
|
assert(first_common->label == LABEL_S);
|
|
|
|
while (vertex_top_blossom[path.edges.front().first]
|
|
|
|
!= first_common) {
|
|
|
|
path.edges.pop_front();
|
|
|
|
}
|
|
|
|
while (vertex_top_blossom[path.edges.back().second]
|
|
|
|
!= first_common) {
|
|
|
|
path.edges.pop_back();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Any alternating path between S-blossoms must have odd length.
|
|
|
|
assert(path.edges.size() % 2 == 1);
|
|
|
|
|
|
|
|
return path;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create a new blossom from an alternating cycle.
|
|
|
|
*
|
|
|
|
* Assign label S to the new blossom.
|
|
|
|
* Relabel all T-sub-blossoms as S and add their vertices to the queue.
|
|
|
|
*
|
|
|
|
* This function takes total time O(n**2) per stage.
|
|
|
|
*/
|
|
|
|
void make_blossom(const AlternatingPath& path)
|
|
|
|
{
|
|
|
|
assert(path.edges.size() % 2 == 1);
|
|
|
|
assert(path.edges.size() >= 3);
|
|
|
|
|
|
|
|
// Construct the list of sub-blossoms.
|
|
|
|
std::vector<BlossomT*> subblossoms;
|
|
|
|
subblossoms.reserve(path.edges.size());
|
|
|
|
for (VertexPair edge : path.edges) {
|
|
|
|
subblossoms.push_back(vertex_top_blossom[edge.first]);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check that the path is cyclic.
|
|
|
|
VertexId pos = 0;
|
|
|
|
for (VertexPair edge : path.edges) {
|
|
|
|
pos = (pos + 1) % subblossoms.size();
|
|
|
|
assert(vertex_top_blossom[edge.second] == subblossoms[pos]);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create the new blossom object.
|
|
|
|
nontrivial_blossom.emplace_back(subblossoms, path.edges);
|
|
|
|
NonTrivialBlossomT* blossom = &nontrivial_blossom.back();
|
|
|
|
|
|
|
|
// Link the subblossoms to the their new parent.
|
|
|
|
for (BlossomT* sub : subblossoms) {
|
|
|
|
sub->parent = blossom;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Mark vertices as belonging to the new blossom.
|
|
|
|
for_vertices_in_blossom(blossom, [this,blossom](VertexId x) {
|
|
|
|
vertex_top_blossom[x] = blossom;
|
|
|
|
});
|
|
|
|
|
|
|
|
// Assign label S to the new blossom and link to the alternating tree.
|
|
|
|
assert(subblossoms.front()->label == LABEL_S);
|
|
|
|
blossom->label = LABEL_S;
|
|
|
|
blossom->tree_edge = subblossoms.front()->tree_edge;
|
|
|
|
|
|
|
|
// Consider vertices inside former T-sub-blossoms which now
|
|
|
|
// became S-vertices; add them to the queue.
|
|
|
|
for (BlossomT* sub : subblossoms) {
|
|
|
|
if (sub->label == LABEL_T) {
|
|
|
|
for_vertices_in_blossom(sub,
|
|
|
|
[this](VertexId x) {
|
|
|
|
queue.push_back(x);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Merge least-slack edges for the S-sub-blossoms.
|
|
|
|
lset_merge_blossoms(blossom);
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Erase the specified non-trivial blossom. */
|
|
|
|
void erase_nontrivial_blossom(NonTrivialBlossomT* blossom)
|
|
|
|
{
|
|
|
|
auto blossom_it = std::find_if(
|
|
|
|
nontrivial_blossom.begin(),
|
|
|
|
nontrivial_blossom.end(),
|
|
|
|
[blossom](const NonTrivialBlossomT& b) {
|
|
|
|
return (&b == blossom);
|
|
|
|
});
|
|
|
|
assert(blossom_it != nontrivial_blossom.end());
|
|
|
|
nontrivial_blossom.erase(blossom_it);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Expand the specified T-blossom.
|
|
|
|
*
|
|
|
|
* This function takes time O(n).
|
|
|
|
*/
|
|
|
|
void expand_t_blossom(NonTrivialBlossomT* blossom)
|
|
|
|
{
|
|
|
|
assert(blossom->parent == nullptr);
|
|
|
|
assert(blossom->label == LABEL_T);
|
|
|
|
|
|
|
|
// Convert sub-blossoms into top-level blossoms.
|
|
|
|
for (const auto& sub : blossom->subblossoms) {
|
|
|
|
BlossomT* sub_blossom = sub.blossom;
|
|
|
|
assert(sub_blossom->parent == blossom);
|
|
|
|
assert(sub_blossom->label == LABEL_NONE);
|
|
|
|
sub_blossom->parent = nullptr;
|
|
|
|
for_vertices_in_blossom(sub_blossom,
|
|
|
|
[this,sub_blossom](VertexId x) {
|
|
|
|
vertex_top_blossom[x] = sub_blossom;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// The expanded blossom was part of an alternating tree.
|
|
|
|
// We must now reconstruct the part of the alternating tree
|
|
|
|
// that ran through this blossom by linking some of the sub-blossoms
|
|
|
|
// into the tree.
|
|
|
|
|
|
|
|
// Find the sub-blossom that was attached to the parent node
|
|
|
|
// in the alternating tree.
|
|
|
|
BlossomT* entry = vertex_top_blossom[blossom->tree_edge.second];
|
|
|
|
|
|
|
|
// Assign label T to that blossom and link to the alternating tree.
|
|
|
|
entry->label = LABEL_T;
|
|
|
|
entry->tree_edge = blossom->tree_edge;
|
|
|
|
|
|
|
|
// Find the position of this sub-blossom within the expanding blossom.
|
|
|
|
auto subblossom_loc = blossom->find_subblossom(entry);
|
|
|
|
VertexId entry_pos = subblossom_loc.first;
|
|
|
|
auto entry_it = subblossom_loc.second;
|
|
|
|
|
|
|
|
// Walk around the blossom from "entry" to the base
|
|
|
|
// in an even number of steps.
|
|
|
|
auto sub_it = entry_it;
|
|
|
|
if (entry_pos % 2 == 0) {
|
|
|
|
|
|
|
|
// Walk backward to the base.
|
|
|
|
auto sub_begin = blossom->subblossoms.begin();
|
|
|
|
while (sub_it != sub_begin) {
|
|
|
|
// Assign label S to the next node on the path.
|
|
|
|
--sub_it;
|
|
|
|
assign_label_s(sub_it->edge.first);
|
|
|
|
|
|
|
|
// Assign label T to the next node on the path.
|
|
|
|
assert(sub_it != sub_begin);
|
|
|
|
--sub_it;
|
|
|
|
sub_it->blossom->label = LABEL_T;
|
|
|
|
sub_it->blossom->tree_edge = flip_vertex_pair(sub_it->edge);
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
// Walk forward to the base.
|
|
|
|
auto sub_end = blossom->subblossoms.end();
|
|
|
|
while (sub_it != sub_end) {
|
|
|
|
// Assign label S to the next node on the path.
|
|
|
|
assign_label_s(sub_it->edge.second);
|
|
|
|
++sub_it;
|
|
|
|
|
|
|
|
// Assign label T to the next node on the path.
|
|
|
|
// We may wrap past the end of the subblossom list.
|
|
|
|
assert(sub_it != sub_end);
|
|
|
|
VertexPair& tree_edge = sub_it->edge;
|
|
|
|
++sub_it;
|
|
|
|
|
|
|
|
BlossomT *sub_blossom = (sub_it == sub_end) ?
|
|
|
|
blossom->subblossoms.front().blossom :
|
|
|
|
sub_it->blossom;
|
|
|
|
sub_blossom->label = LABEL_T;
|
|
|
|
sub_blossom->tree_edge = tree_edge;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Delete the expanded blossom.
|
|
|
|
erase_nontrivial_blossom(blossom);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Expand the specified unlabeled blossom.
|
|
|
|
*
|
|
|
|
* This function takes time O(n).
|
|
|
|
*/
|
|
|
|
void expand_unlabeled_blossom(NonTrivialBlossomT* blossom)
|
|
|
|
{
|
|
|
|
assert(blossom->parent == nullptr);
|
|
|
|
assert(blossom->label == LABEL_NONE);
|
|
|
|
|
|
|
|
// Convert sub-blossoms into top-level blossoms.
|
|
|
|
for (const auto& sub : blossom->subblossoms) {
|
|
|
|
BlossomT* sub_blossom = sub.blossom;
|
|
|
|
assert(sub_blossom->parent == blossom);
|
|
|
|
assert(sub_blossom->label == LABEL_NONE);
|
|
|
|
sub_blossom->parent = nullptr;
|
|
|
|
for_vertices_in_blossom(sub_blossom,
|
|
|
|
[this,sub_blossom](VertexId x) {
|
|
|
|
vertex_top_blossom[x] = sub_blossom;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// Delete the expanded blossom.
|
|
|
|
erase_nontrivial_blossom(blossom);
|
|
|
|
}
|
|
|
|
|
2023-05-12 21:50:07 +02:00
|
|
|
/* ********** Augmenting: ********** */
|
2023-05-10 20:56:30 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Augment along an alternating path through the specified blossom,
|
|
|
|
* from sub-blossom "entry" to the base vertex of the blossom.
|
|
|
|
*
|
|
|
|
* Modify the blossom to reflect that sub-blossom "entry" contains
|
|
|
|
* the base vertex after augmenting.
|
|
|
|
*/
|
|
|
|
void augment_blossom_rec(
|
|
|
|
NonTrivialBlossomT* blossom,
|
|
|
|
BlossomT* entry,
|
|
|
|
std::stack<std::pair<NonTrivialBlossomT*, BlossomT*>>& rec_stack)
|
|
|
|
{
|
|
|
|
// Find the position of "entry" within "blossom".
|
|
|
|
auto subblossom_loc = blossom->find_subblossom(entry);
|
|
|
|
VertexId entry_pos = subblossom_loc.first;
|
|
|
|
auto entry_it = subblossom_loc.second;
|
|
|
|
|
|
|
|
// Walk around the blossom from "entry" to the base
|
|
|
|
// in an even number of steps.
|
|
|
|
auto sub_begin = blossom->subblossoms.begin();
|
|
|
|
auto sub_end = blossom->subblossoms.end();
|
|
|
|
auto sub_it = entry_it;
|
|
|
|
while ((sub_it != sub_begin) && (sub_it != sub_end)) {
|
|
|
|
VertexId x, y;
|
|
|
|
BlossomT* bx;
|
|
|
|
BlossomT* by;
|
|
|
|
|
|
|
|
if (entry_pos % 2 == 0) {
|
|
|
|
// Walk backward to the base.
|
|
|
|
--sub_it;
|
|
|
|
by = sub_it->blossom;
|
|
|
|
assert(sub_it != sub_begin);
|
|
|
|
--sub_it;
|
|
|
|
bx = sub_it->blossom;
|
|
|
|
std::tie(x, y) = sub_it->edge;
|
|
|
|
} else {
|
|
|
|
// Walk forward to the base.
|
|
|
|
// We may wrap past the end of the subblossom list.
|
|
|
|
++sub_it;
|
|
|
|
assert(sub_it != sub_end);
|
|
|
|
std::tie(x, y) = sub_it->edge;
|
|
|
|
bx = sub_it->blossom;
|
|
|
|
++sub_it;
|
|
|
|
by = (sub_it == sub_end) ?
|
|
|
|
blossom->subblossoms.front().blossom :
|
|
|
|
sub_it->blossom;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Pull this edge into the matching.
|
|
|
|
vertex_mate[x] = y;
|
|
|
|
vertex_mate[y] = x;
|
|
|
|
|
|
|
|
// Augment through any non-trivial subblossoms touching this edge.
|
2023-05-13 15:22:21 +02:00
|
|
|
NonTrivialBlossomT* bx_ntb = bx->nontrivial();
|
|
|
|
if (bx_ntb != nullptr) {
|
|
|
|
rec_stack.emplace(bx_ntb, &trivial_blossom[x]);
|
2023-05-10 20:56:30 +02:00
|
|
|
}
|
|
|
|
|
2023-05-13 15:22:21 +02:00
|
|
|
NonTrivialBlossomT* by_ntb = by->nontrivial();
|
|
|
|
if (by_ntb != nullptr) {
|
|
|
|
rec_stack.emplace(by_ntb, &trivial_blossom[y]);
|
2023-05-10 20:56:30 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Re-orient the blossom.
|
|
|
|
if (entry_it != sub_begin) {
|
|
|
|
// Rotate the subblossom list.
|
|
|
|
blossom->subblossoms.splice(sub_begin,
|
|
|
|
blossom->subblossoms,
|
|
|
|
entry_it,
|
|
|
|
sub_end);
|
|
|
|
}
|
2023-05-12 00:43:57 +02:00
|
|
|
|
|
|
|
// Update the base vertex.
|
|
|
|
// We can pull the new base vertex from the entry sub-blossom
|
|
|
|
// since its augmentation has already finished.
|
|
|
|
blossom->base_vertex = entry->base_vertex;
|
2023-05-10 20:56:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Augment along an alternating path through the specified blossom,
|
|
|
|
* from sub-blossom "entry" to the base vertex of the blossom.
|
|
|
|
*
|
|
|
|
* Recursively handle sub-blossoms as needed.
|
|
|
|
*
|
|
|
|
* This function takes time O(n).
|
|
|
|
*/
|
|
|
|
void augment_blossom(NonTrivialBlossomT* blossom, BlossomT* entry)
|
|
|
|
{
|
|
|
|
// Use an explicit stack to avoid deep recursion.
|
|
|
|
std::stack<std::pair<NonTrivialBlossomT*, BlossomT*>> rec_stack;
|
|
|
|
rec_stack.emplace(blossom, entry);
|
|
|
|
|
|
|
|
while (!rec_stack.empty()) {
|
|
|
|
NonTrivialBlossomT* outer_blossom;
|
|
|
|
BlossomT* inner_entry;
|
|
|
|
std::tie(outer_blossom, inner_entry) = rec_stack.top();
|
|
|
|
|
|
|
|
NonTrivialBlossomT* inner_blossom = inner_entry->parent;
|
|
|
|
assert(inner_blossom != nullptr);
|
|
|
|
|
|
|
|
if (inner_blossom != outer_blossom) {
|
|
|
|
// After augmenting "inner_blossom",
|
|
|
|
// continue by augmenting its parent.
|
|
|
|
rec_stack.top() = std::make_pair(outer_blossom,
|
|
|
|
inner_blossom);
|
|
|
|
} else {
|
|
|
|
// After augmenting "inner_blossom",
|
|
|
|
// this entire "outer_blossom" will be finished.
|
|
|
|
rec_stack.pop();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Augment "inner_blossom".
|
|
|
|
augment_blossom_rec(inner_blossom, inner_entry, rec_stack);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Augment the matching through the specified augmenting path.
|
|
|
|
*
|
|
|
|
* This function takes time O(n).
|
|
|
|
*/
|
|
|
|
void augment_matching(const AlternatingPath& path)
|
|
|
|
{
|
|
|
|
// Check that the path starts and ends in an unmatched blossom.
|
|
|
|
assert(path.edges.size() % 2 == 1);
|
2023-05-12 00:43:57 +02:00
|
|
|
assert(vertex_mate[vertex_top_blossom[path.edges.front().first]->base_vertex] == NO_VERTEX);
|
|
|
|
assert(vertex_mate[vertex_top_blossom[path.edges.back().second]->base_vertex] == NO_VERTEX);
|
2023-05-10 20:56:30 +02:00
|
|
|
|
|
|
|
// Process the unmatched edges on the augmenting path.
|
|
|
|
auto edge_it = path.edges.begin();
|
|
|
|
auto edge_end = path.edges.end();
|
|
|
|
while (edge_it != edge_end) {
|
|
|
|
VertexId x = edge_it->first;
|
|
|
|
VertexId y = edge_it->second;
|
|
|
|
|
|
|
|
// Augment any non-trivial blossoms that touch this edge.
|
|
|
|
BlossomT* bx = vertex_top_blossom[x];
|
2023-05-13 15:22:21 +02:00
|
|
|
NonTrivialBlossomT* bx_ntb = bx->nontrivial();
|
|
|
|
if (bx_ntb != nullptr) {
|
|
|
|
augment_blossom(bx_ntb, &trivial_blossom[x]);
|
2023-05-10 20:56:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
BlossomT* by = vertex_top_blossom[y];
|
2023-05-13 15:22:21 +02:00
|
|
|
NonTrivialBlossomT* by_ntb = by->nontrivial();
|
|
|
|
if (by_ntb != nullptr) {
|
|
|
|
augment_blossom(by_ntb, &trivial_blossom[y]);
|
2023-05-10 20:56:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Pull this edge into the matching.
|
|
|
|
vertex_mate[x] = y;
|
|
|
|
vertex_mate[y] = x;
|
|
|
|
|
|
|
|
// Move forward through the augmenting path to
|
|
|
|
// the next edge to be matched.
|
|
|
|
++edge_it;
|
|
|
|
if (edge_it == edge_end) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
++edge_it;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-12 21:50:07 +02:00
|
|
|
/* ********** Labels and alternating tree: ********** */
|
2023-05-10 20:56:30 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Assign label S to the unlabeled blossom that contains vertex "x".
|
|
|
|
*
|
|
|
|
* If vertex "x" is matched, it becomes attached to the alternating tree
|
|
|
|
* via its matched edge. If vertex "x" is unmatched, it becomes the root
|
|
|
|
* of an alternating tree.
|
|
|
|
*
|
|
|
|
* All vertices in the newly labeled blossom are added to the scan queue.
|
|
|
|
*
|
|
|
|
* @pre "x" is an unlabeled vertex, either unmatched or matched to
|
|
|
|
* a T-vertex via a tight edge.
|
|
|
|
*/
|
|
|
|
void assign_label_s(VertexId x)
|
|
|
|
{
|
|
|
|
// Assign label S to the blossom that contains vertex "x".
|
|
|
|
BlossomT* bx = vertex_top_blossom[x];
|
2023-05-16 22:19:55 +02:00
|
|
|
assert(bx->label == LABEL_NONE);
|
2023-05-10 20:56:30 +02:00
|
|
|
bx->label = LABEL_S;
|
|
|
|
|
|
|
|
VertexId y = vertex_mate[x];
|
|
|
|
if (y == NO_VERTEX) {
|
|
|
|
// Vertex "x" is unmatched.
|
|
|
|
// It must be either a top-level vertex or the base vertex of
|
|
|
|
// a top-level blossom.
|
|
|
|
assert(bx->base_vertex == x);
|
|
|
|
|
|
|
|
// Mark the blossom as root of an alternating tree.
|
|
|
|
bx->tree_edge = std::make_pair(NO_VERTEX, x);
|
|
|
|
|
|
|
|
} else {
|
|
|
|
// Vertex "x" is matched to T-vertex "y".
|
|
|
|
assert(vertex_top_blossom[y]->label == LABEL_T);
|
|
|
|
|
|
|
|
// Attach the blossom to the alternating tree via vertex "y".
|
|
|
|
bx->tree_edge = std::make_pair(y, x);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Start least-slack edge tracking for the S-blossom.
|
|
|
|
lset_new_blossom(bx);
|
|
|
|
|
|
|
|
// Add all vertices inside the newly labeled S-blossom to the queue.
|
|
|
|
for_vertices_in_blossom(bx,
|
2023-05-12 20:59:11 +02:00
|
|
|
[this](VertexId v) {
|
|
|
|
queue.push_back(v);
|
2023-05-10 20:56:30 +02:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Assign label T to the unlabeled blossom that contains vertex "y".
|
|
|
|
*
|
|
|
|
* Attach it to the alternating tree via edge (x, y).
|
|
|
|
* Then immediately assign label S to the mate of vertex "y".
|
|
|
|
*
|
|
|
|
* Note that this function may expand blossoms that contain vertex "y".
|
|
|
|
*
|
|
|
|
* @pre "x" is an S-vertex.
|
|
|
|
* @pre "y" is an unlabeled, matched vertex.
|
|
|
|
* @pre There is a tight edge between vertices "x" and "y".
|
|
|
|
*/
|
|
|
|
void assign_label_t(VertexId x, VertexId y)
|
|
|
|
{
|
|
|
|
assert(vertex_top_blossom[x]->label == LABEL_S);
|
|
|
|
|
|
|
|
BlossomT* by = vertex_top_blossom[y];
|
|
|
|
assert(by->label == LABEL_NONE);
|
|
|
|
|
|
|
|
// If "y" is part of a zero-dual blossom, expand it.
|
|
|
|
// This would otherwise likely happen through a zero-delta4 step,
|
|
|
|
// so we can just do it now and avoid a substage.
|
2023-05-13 15:22:21 +02:00
|
|
|
NonTrivialBlossomT* ntb = by->nontrivial();
|
2023-05-10 20:56:30 +02:00
|
|
|
while (ntb != nullptr && ntb->dual_var == 0) {
|
|
|
|
expand_unlabeled_blossom(ntb);
|
|
|
|
by = vertex_top_blossom[y];
|
|
|
|
assert(by->label == LABEL_NONE);
|
2023-05-13 15:22:21 +02:00
|
|
|
ntb = by->nontrivial();
|
2023-05-10 20:56:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Assign label T to the top-level blossom that contains vertex "y".
|
|
|
|
by->label = LABEL_T;
|
|
|
|
by->tree_edge = std::make_pair(x, y);
|
|
|
|
|
|
|
|
// Assign label S to the blossom that is mated to the T-blossom.
|
|
|
|
VertexId z = vertex_mate[by->base_vertex];
|
|
|
|
assert(z != NO_VERTEX);
|
|
|
|
assign_label_s(z);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Add the edge between S-vertices "x" and "y".
|
|
|
|
*
|
|
|
|
* If the edge connects blossoms that are part of the same alternating
|
|
|
|
* tree, this function creates a new S-blossom and returns false.
|
|
|
|
*
|
|
|
|
* If the edge connects two different alternating trees, an augmenting
|
|
|
|
* path has been discovered. In this case the matching is augmented.
|
|
|
|
*
|
|
|
|
* @return True if the matching was augmented; otherwise false.
|
|
|
|
*/
|
|
|
|
bool add_s_to_s_edge(VertexId x, VertexId y)
|
|
|
|
{
|
|
|
|
assert(vertex_top_blossom[x]->label == LABEL_S);
|
|
|
|
assert(vertex_top_blossom[y]->label == LABEL_S);
|
|
|
|
|
|
|
|
// Trace back through the alternating trees from "x" and "y".
|
|
|
|
AlternatingPath path = trace_alternating_paths(x, y);
|
|
|
|
|
|
|
|
// If the path is a cycle, create a new blossom.
|
|
|
|
// Otherwise the path is an augmenting path.
|
|
|
|
// Note that an alternating starts and ends in the same blossom,
|
|
|
|
// but not necessarily in the same vertex within that blossom.
|
|
|
|
VertexId p = path.edges.front().first;
|
|
|
|
VertexId q = path.edges.back().second;
|
|
|
|
if (vertex_top_blossom[p] == vertex_top_blossom[q]) {
|
|
|
|
make_blossom(path);
|
|
|
|
return false;
|
|
|
|
} else {
|
|
|
|
augment_matching(path);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Scan queued S-vertices to expand the alternating trees.
|
|
|
|
*
|
|
|
|
* The scan proceeds until either an augmenting path is found,
|
|
|
|
* or the queue of S-vertices becomes empty.
|
|
|
|
* If an augmenting path is found, it is used to augment the matching.
|
|
|
|
*
|
|
|
|
* New blossoms may be created during the scan.
|
|
|
|
*
|
|
|
|
* @return True if the matching was augmented; otherwise false.
|
|
|
|
*/
|
|
|
|
bool substage_scan()
|
|
|
|
{
|
|
|
|
// Process the queue of S-vertices to be scanned.
|
|
|
|
// This loop runs through O(n) iterations per stage.
|
|
|
|
while (! queue.empty()) {
|
|
|
|
|
|
|
|
// Take one vertex from the queue.
|
|
|
|
VertexId x = queue.front();
|
|
|
|
queue.pop_front();
|
|
|
|
|
|
|
|
assert(vertex_top_blossom[x]->label == LABEL_S);
|
|
|
|
|
|
|
|
// Scan the edges that are incident on "x".
|
|
|
|
// This loop runs through O(m) iterations per stage.
|
2023-05-27 23:29:05 +02:00
|
|
|
for (const EdgeT* edge : graph.adjacent_edges[x]) {
|
2023-05-10 20:56:30 +02:00
|
|
|
VertexId y = (edge->vt.first != x) ? edge->vt.first
|
|
|
|
: edge->vt.second;
|
|
|
|
|
|
|
|
// Note: The top-level blossom of vertex "x" may change
|
|
|
|
// during this loop, so we need to refresh it in each pass.
|
|
|
|
BlossomT* bx = vertex_top_blossom[x];
|
|
|
|
|
|
|
|
// Ignore edges that are internal to a blossom.
|
|
|
|
if (bx == vertex_top_blossom[y]) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
BlossomLabel ylabel = vertex_top_blossom[y]->label;
|
|
|
|
|
|
|
|
// Check whether this edge is tight (has zero slack).
|
|
|
|
// Only tight edges may be part of an alternating tree.
|
|
|
|
WeightType slack = edge_slack(*edge);
|
|
|
|
if (slack <= 0) {
|
|
|
|
if (ylabel == LABEL_NONE) {
|
|
|
|
// Found a tight edge to an unlabeled blossom.
|
|
|
|
// Assign label T to the blossom that contains "y".
|
|
|
|
assign_label_t(x, y);
|
|
|
|
} else if (ylabel == LABEL_S) {
|
|
|
|
// Found a tight edge between two S-blossoms.
|
|
|
|
// Find either a new blossom or an augmenting path.
|
|
|
|
bool augmented = add_s_to_s_edge(x, y);
|
|
|
|
if (augmented) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (ylabel == LABEL_S) {
|
|
|
|
// Found a non-tight edge between two S-blossoms.
|
|
|
|
// Pass it to the least-slack edge tracker.
|
|
|
|
lset_add_blossom_edge(bx, edge, slack);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ylabel != LABEL_S) {
|
|
|
|
// Found an to a T-vertex or unlabeled vertex "y".
|
|
|
|
// Pass it to the least-slack edge tracker.
|
|
|
|
// Tight edges must also be tracked in this way.
|
|
|
|
lset_add_vertex_edge(y, edge, slack);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// No further S vertices to scan, and no augmenting path found.
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2023-05-12 21:50:07 +02:00
|
|
|
/* ********** Delta steps: ********** */
|
2023-05-10 20:56:30 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Calculate a delta step in the dual LPP problem.
|
|
|
|
*
|
|
|
|
* This function returns the minimum of the 4 types of delta values,
|
|
|
|
* and the type of delta which obtain the minimum, and the edge or
|
|
|
|
* blossom that produces the minimum delta, if applicable.
|
|
|
|
*
|
|
|
|
* This function takes time O(n).
|
|
|
|
*
|
|
|
|
* @pre There is at least one S-vertex.
|
|
|
|
*
|
|
|
|
* @return Tuple (delta_type, delta_value, delta_edge, delta_blossom)
|
|
|
|
*/
|
|
|
|
DeltaStep substage_calc_dual_delta()
|
|
|
|
{
|
|
|
|
DeltaStep delta;
|
|
|
|
delta.blossom = nullptr;
|
|
|
|
|
|
|
|
// Compute delta1: minimum dual variable of any S-vertex.
|
|
|
|
delta.kind = 1;
|
|
|
|
delta.value = std::numeric_limits<WeightType>::max();
|
2023-05-27 23:29:05 +02:00
|
|
|
for (VertexId x = 0; x < graph.num_vertex; ++x) {
|
2023-05-10 20:56:30 +02:00
|
|
|
if (vertex_top_blossom[x]->label == LABEL_S) {
|
|
|
|
delta.value = std::min(delta.value, vertex_dual[x]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Compute delta2: minimum slack of any edge between an S-vertex and
|
|
|
|
// an unlabeled vertex.
|
|
|
|
const EdgeT* edge;
|
|
|
|
WeightType slack;
|
|
|
|
std::tie(edge, slack) = lset_get_best_vertex_edge();
|
|
|
|
if ((edge != nullptr) && (slack <= delta.value)) {
|
|
|
|
delta.kind = 2;
|
|
|
|
delta.value = slack;
|
|
|
|
delta.edge = edge->vt;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Compute delta3: half minimum slack of any edge between two
|
|
|
|
// top-level S-blossoms.
|
|
|
|
std::tie(edge, slack) = lset_get_best_blossom_edge();
|
|
|
|
if ((edge != nullptr) && (slack / 2 <= delta.value)) {
|
|
|
|
delta.kind = 3;
|
|
|
|
delta.value = slack / 2;
|
|
|
|
delta.edge = edge->vt;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Compute delta4: half minimum dual of a top-level T-blossom.
|
|
|
|
for (NonTrivialBlossomT& blossom : nontrivial_blossom) {
|
|
|
|
if ((! blossom.parent) && (blossom.label == LABEL_T)) {
|
|
|
|
if (blossom.dual_var / 2 <= delta.value) {
|
|
|
|
delta.kind = 4;
|
|
|
|
delta.value = blossom.dual_var / 2;
|
|
|
|
delta.blossom = &blossom;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return delta;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Apply a delta step to the dual LPP variables. */
|
|
|
|
void substage_apply_delta_step(WeightType delta)
|
|
|
|
{
|
|
|
|
// Apply delta to dual variables of all vertices.
|
2023-05-27 23:29:05 +02:00
|
|
|
for (VertexId x = 0; x < graph.num_vertex; ++x) {
|
2023-05-10 20:56:30 +02:00
|
|
|
BlossomLabel xlabel = vertex_top_blossom[x]->label;
|
|
|
|
if (xlabel == LABEL_S) {
|
|
|
|
// S-vertex: subtract delta from dual variable.
|
|
|
|
vertex_dual[x] -= delta;
|
|
|
|
} else if (xlabel == LABEL_T) {
|
|
|
|
// T-vertex: add delta to dual variable.
|
|
|
|
vertex_dual[x] += delta;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Apply delta to dual variables of top-level non-trivial blossoms.
|
|
|
|
for (NonTrivialBlossomT& blossom : nontrivial_blossom) {
|
|
|
|
if (blossom.parent == nullptr) {
|
|
|
|
if (blossom.label == LABEL_S) {
|
|
|
|
// S-blossom: add 2*delta to dual variable.
|
|
|
|
blossom.dual_var += 2 * delta;
|
|
|
|
} else if (blossom.label == LABEL_T) {
|
|
|
|
// T-blossom: subtract 2*delta from dual variable.
|
|
|
|
blossom.dual_var -= 2 * delta;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-12 21:50:07 +02:00
|
|
|
/* ********** Main algorithm: ********** */
|
2023-05-10 20:56:30 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Reset data which are only valid during a stage.
|
|
|
|
*
|
|
|
|
* Mark all blossoms as unlabeled, clear the queue,
|
|
|
|
* reset least-slack edge tracking.
|
|
|
|
*/
|
|
|
|
void reset_stage()
|
|
|
|
{
|
|
|
|
// Remove blossom labels.
|
|
|
|
for (BlossomT& blossom : trivial_blossom) {
|
|
|
|
blossom.label = LABEL_NONE;
|
|
|
|
}
|
|
|
|
for (BlossomT& blossom : nontrivial_blossom) {
|
|
|
|
blossom.label = LABEL_NONE;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Clear the S-vertex queue.
|
|
|
|
queue.clear();
|
|
|
|
|
|
|
|
// Reset least-slack edge tracking.
|
|
|
|
lset_reset();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Run one stage of the matching algorithm.
|
|
|
|
*
|
|
|
|
* The stage searches a maximum-weight augmenting path.
|
|
|
|
* If this path is found, it is used to augment the matching,
|
|
|
|
* thereby increasing the number of matched edges by 1.
|
|
|
|
* If no such path is found, the matching must already be optimal.
|
|
|
|
*
|
|
|
|
* This function takes time O(n**2).
|
|
|
|
*
|
|
|
|
* @return True if the matching was successfully augmented;
|
|
|
|
* false if no further improvement is possible.
|
|
|
|
*/
|
|
|
|
bool run_stage()
|
|
|
|
{
|
|
|
|
// Assign label S to all unmatched vertices and put them in the queue.
|
2023-05-27 23:29:05 +02:00
|
|
|
for (VertexId x = 0; x < graph.num_vertex; ++x) {
|
2023-05-10 20:56:30 +02:00
|
|
|
if (vertex_mate[x] == NO_VERTEX) {
|
|
|
|
assign_label_s(x);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Stop if all vertices are matched.
|
|
|
|
// No further improvement is possible in that case.
|
|
|
|
// This avoids messy calculations of delta steps without any S-vertex.
|
|
|
|
if (queue.empty()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Each pass through the following loop is a "substage".
|
|
|
|
// The substage tries to find an augmenting path.
|
|
|
|
// If an augmenting path is found, we augment the matching and end
|
|
|
|
// the stage. Otherwise we update the dual LPP problem and enter the
|
|
|
|
// next substage, or stop if no further improvement is possible.
|
|
|
|
//
|
|
|
|
// This loop runs through at most O(n) iterations per stage.
|
|
|
|
bool augmented = false;
|
|
|
|
while (true) {
|
|
|
|
|
|
|
|
// Grow alternating trees.
|
|
|
|
// End the stage if an augmenting path is found.
|
|
|
|
augmented = substage_scan();
|
|
|
|
if (augmented) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Calculate delta step in the dual LPP problem.
|
|
|
|
DeltaStep delta = substage_calc_dual_delta();
|
|
|
|
|
|
|
|
// Apply the delta step to the dual variables.
|
|
|
|
substage_apply_delta_step(delta.value);
|
|
|
|
|
|
|
|
if (delta.kind == 2) {
|
|
|
|
// Use the edge from S-vertex to unlabeled vertex that got
|
|
|
|
// unlocked through the delta update.
|
|
|
|
VertexId x = delta.edge.first;
|
|
|
|
VertexId y = delta.edge.second;
|
|
|
|
if (vertex_top_blossom[x]->label != LABEL_S) {
|
|
|
|
std::swap(x, y);
|
|
|
|
}
|
|
|
|
assign_label_t(x, y);
|
|
|
|
|
|
|
|
} else if (delta.kind == 3) {
|
|
|
|
// Use the S-to-S edge that got unlocked by the delta update.
|
|
|
|
// This may reveal an augmenting path.
|
|
|
|
VertexId x = delta.edge.first;
|
|
|
|
VertexId y = delta.edge.second;
|
|
|
|
augmented = add_s_to_s_edge(x, y);
|
|
|
|
if (augmented) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
} else if (delta.kind == 4) {
|
|
|
|
// Expand the T-blossom that reached dual value 0 through
|
|
|
|
// the delta update.
|
|
|
|
assert(delta.blossom);
|
|
|
|
expand_t_blossom(delta.blossom);
|
|
|
|
|
|
|
|
} else {
|
|
|
|
// No further improvement possible. End the stage.
|
|
|
|
assert(delta.kind == 1);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remove all labels, clear queue.
|
|
|
|
reset_stage();
|
|
|
|
|
|
|
|
// Return True if the matching was augmented.
|
|
|
|
return augmented;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Run the matching algorithm. */
|
|
|
|
void run()
|
|
|
|
{
|
|
|
|
// Improve the solution until no further improvement is possible.
|
|
|
|
//
|
|
|
|
// Each successful pass through this loop increases the number
|
|
|
|
// of matched edges by 1.
|
|
|
|
//
|
|
|
|
// This loop runs through at most (n/2 + 1) iterations.
|
|
|
|
// Each iteration takes time O(n**2).
|
|
|
|
while (run_stage()) ;
|
|
|
|
}
|
2023-05-16 22:19:55 +02:00
|
|
|
};
|
|
|
|
|
2023-05-10 20:56:30 +02:00
|
|
|
|
2023-05-16 22:19:55 +02:00
|
|
|
/* **************************************************
|
|
|
|
* ** struct MatchingVerifier **
|
|
|
|
* ************************************************** */
|
|
|
|
|
|
|
|
/** Helper class to verify that an optimal solution has been found. */
|
|
|
|
template <typename WeightType>
|
2023-05-27 23:29:05 +02:00
|
|
|
class MatchingVerifier
|
2023-05-16 22:19:55 +02:00
|
|
|
{
|
2023-05-27 23:29:05 +02:00
|
|
|
public:
|
2023-05-16 22:19:55 +02:00
|
|
|
MatchingVerifier(const MatchingContext<WeightType>& ctx)
|
|
|
|
: ctx(ctx),
|
2023-05-27 23:29:05 +02:00
|
|
|
graph(ctx.graph),
|
|
|
|
edge_duals(ctx.graph.edges.size())
|
2023-05-16 22:19:55 +02:00
|
|
|
{ }
|
|
|
|
|
2023-05-27 23:29:05 +02:00
|
|
|
/**
|
|
|
|
* 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>;
|
|
|
|
|
2023-05-16 22:19:55 +02:00
|
|
|
static bool checked_add(WeightType& result, WeightType a, WeightType b)
|
|
|
|
{
|
|
|
|
if (a > std::numeric_limits<WeightType>::max() - b) {
|
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
result = a + b;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Convert edge pointer to its index in the vector "edges". */
|
|
|
|
std::size_t edge_index(const EdgeT* edge)
|
|
|
|
{
|
2023-05-27 23:29:05 +02:00
|
|
|
return edge - graph.edges.data();
|
2023-05-16 22:19:55 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/** Check that the array "vertex_mate" is consistent. */
|
|
|
|
bool verify_vertex_mate()
|
|
|
|
{
|
|
|
|
// Count matched vertices and check symmetry of "vertex_mate".
|
|
|
|
VertexId num_matched_vertex = 0;
|
2023-05-27 23:29:05 +02:00
|
|
|
for (VertexId x = 0; x < graph.num_vertex; ++x) {
|
2023-05-16 22:19:55 +02:00
|
|
|
VertexId y = ctx.vertex_mate[x];
|
|
|
|
if (y != NO_VERTEX) {
|
|
|
|
++num_matched_vertex;
|
|
|
|
if (ctx.vertex_mate[y] != x) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Count matched edges.
|
|
|
|
VertexId num_matched_edge = 0;
|
2023-05-27 23:29:05 +02:00
|
|
|
for (const EdgeT& edge : graph.edges) {
|
2023-05-16 22:19:55 +02:00
|
|
|
if (ctx.vertex_mate[edge.vt.first] == edge.vt.second) {
|
|
|
|
++num_matched_edge;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check that all matched vertices correspond to matched edges.
|
|
|
|
return (num_matched_vertex == 2 * num_matched_edge);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check that vertex dual variables are non-negative,
|
|
|
|
* and all unmatched vertices have zero dual.
|
|
|
|
*/
|
|
|
|
bool verify_vertex_duals()
|
|
|
|
{
|
2023-05-27 23:29:05 +02:00
|
|
|
for (VertexId x = 0; x < graph.num_vertex; ++x) {
|
2023-05-16 22:19:55 +02:00
|
|
|
if (ctx.vertex_dual[x] < 0) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if ((ctx.vertex_mate[x] == NO_VERTEX) && (ctx.vertex_dual[x] != 0)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Check that blossom dual variables are non-negative. */
|
|
|
|
bool verify_blossom_duals()
|
|
|
|
{
|
|
|
|
for (const NonTrivialBlossomT& blossom : ctx.nontrivial_blossom) {
|
|
|
|
if (blossom.dual_var < 0) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
2023-05-10 20:56:30 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Helper function for verifying the solution.
|
|
|
|
*
|
|
|
|
* Descend down the blossom tree to find edges that are contained
|
|
|
|
* in blossoms.
|
|
|
|
*
|
|
|
|
* On the way down, keep track of the sum of dual variables of
|
2023-05-16 22:19:55 +02:00
|
|
|
* containing blossoms. Add blossom duals to edges that are contained
|
|
|
|
* inside blossoms.
|
2023-05-10 20:56:30 +02:00
|
|
|
*
|
|
|
|
* On the way up, keep track of the total number of matched edges
|
2023-05-16 22:19:55 +02:00
|
|
|
* in subblossoms. Check that all blossoms with non-zero dual variables
|
|
|
|
* are "full".
|
2023-05-10 20:56:30 +02:00
|
|
|
*
|
|
|
|
* @return True if successful;
|
2023-05-16 22:19:55 +02:00
|
|
|
* false if a blossom with non-zero dual is not full;
|
|
|
|
* false if blossom dual calculations cause numeric overflow.
|
2023-05-10 20:56:30 +02:00
|
|
|
*/
|
2023-05-16 22:19:55 +02:00
|
|
|
bool check_blossom(const NonTrivialBlossomT* blossom)
|
2023-05-10 20:56:30 +02:00
|
|
|
{
|
|
|
|
// For each vertex "x",
|
|
|
|
// "vertex_depth[x]" is the depth of the smallest blossom on
|
|
|
|
// the current descent path that contains "x".
|
2023-05-27 23:29:05 +02:00
|
|
|
std::vector<VertexId> vertex_depth(graph.num_vertex);
|
2023-05-10 20:56:30 +02:00
|
|
|
|
|
|
|
// At each depth, keep track of the sum of blossom duals
|
|
|
|
// along the current descent path.
|
|
|
|
std::vector<WeightType> path_sum_dual = {0};
|
|
|
|
|
|
|
|
// At each depth, keep track of the number of matched edges
|
|
|
|
// along the current ascent path.
|
|
|
|
std::vector<VertexId> path_num_matched = {0};
|
|
|
|
|
|
|
|
// Use an explicit stack to avoid deep recursion.
|
2023-05-16 22:19:55 +02:00
|
|
|
using SubBlossomList = std::list<typename NonTrivialBlossomT::SubBlossom>;
|
|
|
|
std::stack<std::pair<const NonTrivialBlossomT*,
|
|
|
|
typename SubBlossomList::const_iterator>> stack;
|
2023-05-10 20:56:30 +02:00
|
|
|
stack.emplace(blossom, blossom->subblossoms.begin());
|
|
|
|
|
|
|
|
while (! stack.empty()) {
|
|
|
|
VertexId depth = stack.size();
|
|
|
|
auto& stack_elem = stack.top();
|
|
|
|
|
|
|
|
blossom = stack_elem.first;
|
|
|
|
auto subblossom_it = stack_elem.second;
|
|
|
|
|
|
|
|
if (subblossom_it == blossom->subblossoms.begin()) {
|
|
|
|
// We just entered this sub-blossom.
|
|
|
|
// Update the depth of all vertices in this sub-blossom.
|
|
|
|
for_vertices_in_blossom(blossom,
|
|
|
|
[&vertex_depth,depth](VertexId x) {
|
|
|
|
vertex_depth[x] = depth;
|
|
|
|
});
|
|
|
|
|
|
|
|
// Calculate the sum of blossom duals at the new depth.
|
2023-05-16 22:19:55 +02:00
|
|
|
path_sum_dual.push_back(path_sum_dual.back());
|
|
|
|
if (checked_add(path_sum_dual.back(),
|
|
|
|
path_sum_dual.back(),
|
|
|
|
blossom->dual_var)) {
|
|
|
|
return false;
|
|
|
|
}
|
2023-05-10 20:56:30 +02:00
|
|
|
|
|
|
|
// Initialize the number of matched edges at the new depth.
|
|
|
|
path_num_matched.push_back(0);
|
|
|
|
|
|
|
|
if (blossom->subblossoms.size() < 3) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (subblossom_it != blossom->subblossoms.end()) {
|
|
|
|
|
|
|
|
// Update the sub-blossom pointer at the current depth.
|
|
|
|
++stack_elem.second;
|
|
|
|
|
|
|
|
// Examine the current sub-blossom.
|
|
|
|
BlossomT* sub = subblossom_it->blossom;
|
2023-05-13 15:22:21 +02:00
|
|
|
NonTrivialBlossomT* ntb = sub->nontrivial();
|
2023-05-10 20:56:30 +02:00
|
|
|
if (ntb) {
|
|
|
|
// Prepare to descend into this sub-blossom.
|
|
|
|
stack.emplace(std::make_pair(ntb, ntb->subblossoms.begin()));
|
|
|
|
} else {
|
|
|
|
// Handle single vertex.
|
|
|
|
// For each incident edge, find the smallest blossom
|
|
|
|
// that contains it.
|
|
|
|
VertexId x = sub->base_vertex;
|
2023-05-27 23:29:05 +02:00
|
|
|
for (const EdgeT* edge : graph.adjacent_edges[x]) {
|
2023-05-10 20:56:30 +02:00
|
|
|
// Only consider edges pointing out from "x".
|
|
|
|
if (edge->vt.first == x) {
|
|
|
|
VertexId y = edge->vt.second;
|
|
|
|
VertexId edge_depth = vertex_depth[y];
|
|
|
|
if (edge_depth > 0) {
|
2023-05-16 22:19:55 +02:00
|
|
|
// Found the smallest blossom that contains this edge.
|
|
|
|
// Add the duals of the containing blossoms.
|
|
|
|
if (checked_add(edge_duals[edge_index(edge)],
|
|
|
|
edge_duals[edge_index(edge)],
|
|
|
|
path_sum_dual[edge_depth])) {
|
|
|
|
return false;
|
|
|
|
}
|
2023-05-10 20:56:30 +02:00
|
|
|
|
|
|
|
// Update the number of matched edges in the blossom.
|
2023-05-16 22:19:55 +02:00
|
|
|
if (ctx.vertex_mate[x] == y) {
|
2023-05-10 20:56:30 +02:00
|
|
|
path_num_matched[edge_depth] += 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
// We are leaving the current sub-blossom.
|
|
|
|
|
|
|
|
// Count the number of vertices inside this blossom.
|
|
|
|
VertexId blossom_num_vertex = 0;
|
|
|
|
for_vertices_in_blossom(blossom,
|
2023-05-12 18:09:32 +02:00
|
|
|
[&blossom_num_vertex](VertexId) {
|
2023-05-10 20:56:30 +02:00
|
|
|
++blossom_num_vertex;
|
|
|
|
});
|
|
|
|
|
|
|
|
// Check that the blossom is "full".
|
|
|
|
// A blossom is full if all except one of its vertices
|
|
|
|
// are matched to another vertex within the blossom.
|
|
|
|
VertexId blossom_num_matched = path_num_matched[depth];
|
|
|
|
if (blossom_num_vertex != 2 * blossom_num_matched + 1) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update the number of matched edges in the parent blossom.
|
|
|
|
path_num_matched[depth - 1] += path_num_matched[depth];
|
|
|
|
|
|
|
|
// Push vertices in this sub-blossom back to the parent.
|
|
|
|
for_vertices_in_blossom(blossom,
|
|
|
|
[&vertex_depth,depth](VertexId x) {
|
|
|
|
vertex_depth[x] = depth - 1;
|
|
|
|
});
|
|
|
|
|
|
|
|
// Trim the descending path.
|
|
|
|
path_sum_dual.pop_back();
|
|
|
|
path_num_matched.pop_back();
|
|
|
|
|
|
|
|
// Remove the current blossom from the stack.
|
|
|
|
stack.pop();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2023-05-16 22:19:55 +02:00
|
|
|
* Check that all blossoms are full.
|
|
|
|
* Also calculate the sum of dual variables for every edge.
|
2023-05-10 20:56:30 +02:00
|
|
|
*/
|
2023-05-16 22:19:55 +02:00
|
|
|
bool verify_blossoms_and_calc_edge_duals()
|
2023-05-10 20:56:30 +02:00
|
|
|
{
|
2023-05-16 22:19:55 +02:00
|
|
|
// For each edge, calculate the sum of its vertex duals.
|
2023-05-27 23:29:05 +02:00
|
|
|
for (const EdgeT& edge : graph.edges) {
|
2023-05-16 22:19:55 +02:00
|
|
|
if (checked_add(edge_duals[edge_index(&edge)],
|
|
|
|
ctx.vertex_dual[edge.vt.first],
|
|
|
|
ctx.vertex_dual[edge.vt.second])) {
|
2023-05-10 20:56:30 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Descend down each top-level blossom.
|
2023-05-16 22:19:55 +02:00
|
|
|
// Check that blossoms are full.
|
|
|
|
// Add blossom duals to the edges contained inside the blossoms.
|
2023-05-10 20:56:30 +02:00
|
|
|
// This takes total time O(n**2).
|
2023-05-16 22:19:55 +02:00
|
|
|
for (const NonTrivialBlossomT& blossom : ctx.nontrivial_blossom) {
|
2023-05-10 20:56:30 +02:00
|
|
|
if (blossom.parent == nullptr) {
|
2023-05-16 22:19:55 +02:00
|
|
|
if (!check_blossom(&blossom)) {
|
|
|
|
return false;
|
|
|
|
}
|
2023-05-10 20:56:30 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-16 22:19:55 +02:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check that all edges have non-negative slack,
|
|
|
|
* and check that all matched edges have zero slack.
|
|
|
|
*
|
|
|
|
* @pre Edge duals must be calculated before calling this function.
|
|
|
|
*/
|
|
|
|
bool verify_edge_slack()
|
|
|
|
{
|
2023-05-27 23:29:05 +02:00
|
|
|
for (const EdgeT& edge : graph.edges) {
|
2023-05-16 22:19:55 +02:00
|
|
|
WeightType duals = edge_duals[edge_index(&edge)];
|
|
|
|
WeightType weight = ctx.weight_factor * edge.weight;
|
|
|
|
|
|
|
|
if (weight > duals) {
|
2023-05-10 20:56:30 +02:00
|
|
|
return false;
|
|
|
|
}
|
2023-05-16 22:19:55 +02:00
|
|
|
WeightType slack = duals - weight;
|
|
|
|
|
|
|
|
if (ctx.vertex_mate[edge.vt.first] == edge.vt.second) {
|
2023-05-10 20:56:30 +02:00
|
|
|
if (slack != 0) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
2023-05-16 22:19:55 +02:00
|
|
|
|
2023-05-27 23:29:05 +02:00
|
|
|
private:
|
|
|
|
/** Reference to the MatchingContext instance. */
|
|
|
|
const MatchingContext<WeightType>& ctx;
|
|
|
|
|
|
|
|
/** Reference to the input graph. */
|
|
|
|
const Graph<WeightType>& graph;
|
|
|
|
|
2023-05-16 22:19:55 +02:00
|
|
|
/**
|
2023-05-27 23:29:05 +02:00
|
|
|
* For each edge, the sum of duals of its incident vertices
|
|
|
|
* and duals of all blossoms that contain the edge.
|
2023-05-16 22:19:55 +02:00
|
|
|
*/
|
2023-05-27 23:29:05 +02:00
|
|
|
std::vector<WeightType> edge_duals;
|
2023-05-10 20:56:30 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
} // namespace impl
|
|
|
|
|
|
|
|
|
2023-05-16 22:19:55 +02:00
|
|
|
/* **************************************************
|
|
|
|
* ** public functions **
|
|
|
|
* ************************************************** */
|
|
|
|
|
2023-05-10 20:56:30 +02:00
|
|
|
/**
|
|
|
|
* Compute a maximum-weighted matching in the general undirected weighted
|
|
|
|
* graph given by "edges".
|
|
|
|
*
|
|
|
|
* The graph is specified as a list of edges, each edge specified as a tuple
|
|
|
|
* of its two vertices and the edge weight.
|
|
|
|
* There may be at most one edge between any pair of vertices.
|
|
|
|
* No vertex may have an edge to itself.
|
|
|
|
* The graph may be non-connected (i.e. contain multiple components).
|
|
|
|
*
|
|
|
|
* Vertices are indexed by consecutive, non-negative integers, such that
|
|
|
|
* the first vertex has index 0 and the last vertex has index (n-1).
|
|
|
|
* Edge weights may be integers or floating point numbers.
|
|
|
|
*
|
|
|
|
* Isolated vertices (not incident to any edge) are allowed, but not
|
|
|
|
* recommended since such vertices consume time and memory but have
|
|
|
|
* no effect on the maximum-weight matching.
|
|
|
|
* Edges with negative weight are ignored.
|
|
|
|
*
|
|
|
|
* This function takes time O(n**3), where "n" is the number of vertices.
|
|
|
|
* This function uses O(n + m) memory, where "m" is the number of edges.
|
|
|
|
*
|
|
|
|
* @tparam WeightType Type used to represent edge weights.
|
|
|
|
* For example "long" or "double".
|
2023-05-16 22:19:55 +02:00
|
|
|
* @param edges Graph defined as a vector of weighted edges.
|
2023-05-10 20:56:30 +02:00
|
|
|
*
|
|
|
|
* @return Vector of pairs of matched vertex indices.
|
|
|
|
* This is a subset of the edges in the graph.
|
|
|
|
*
|
|
|
|
* @throw std::invalid_argument If the input graph is not valid.
|
|
|
|
*/
|
|
|
|
template <typename WeightType>
|
|
|
|
std::vector<VertexPair> maximum_weight_matching(
|
|
|
|
const std::vector<Edge<WeightType>>& edges)
|
|
|
|
{
|
|
|
|
// Check that the input meets all constraints.
|
|
|
|
impl::check_input_graph(edges);
|
|
|
|
|
|
|
|
// Run matching algorithm.
|
|
|
|
impl::MatchingContext<WeightType> matching(edges);
|
|
|
|
matching.run();
|
|
|
|
|
|
|
|
// Verify that the solution is optimal (works only for integer weights).
|
|
|
|
if (std::numeric_limits<WeightType>::is_integer) {
|
2023-05-16 22:19:55 +02:00
|
|
|
assert(impl::MatchingVerifier<WeightType>(matching).verify());
|
2023-05-10 20:56:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Extract the matched edges.
|
|
|
|
std::vector<VertexPair> solution;
|
2023-05-27 23:29:05 +02:00
|
|
|
for (const Edge<WeightType>& edge : matching.graph.edges) {
|
2023-05-10 20:56:30 +02:00
|
|
|
if (matching.vertex_mate[edge.vt.first] == edge.vt.second) {
|
|
|
|
solution.push_back(edge.vt);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return solution;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Adjust edge weights to compute a maximum cardinality matching.
|
|
|
|
*
|
|
|
|
* This function adjusts edge weights in the graph such that the
|
|
|
|
* maximum-weight matching of the adjusted graph is a maximum-cardinality
|
|
|
|
* matching, equal to a matching in the original graph that has maximum weight
|
|
|
|
* out of all matchings with maximum cardinality.
|
|
|
|
*
|
|
|
|
* The graph is specified as a vector of edges.
|
|
|
|
* Negative edge weights are allowed.
|
|
|
|
*
|
|
|
|
* This function increases all edge weights by an equal amount such that
|
|
|
|
* the adjusted weights satisfy the following conditions:
|
|
|
|
* - All edge weights are positive;
|
|
|
|
* - The minimum edge weight is at least "n" times the difference between
|
|
|
|
* maximum and minimum edge weight.
|
|
|
|
*
|
|
|
|
* This function increases edge weights by an amount that is proportional
|
|
|
|
* to the product of the unadjusted weight range and the number of vertices
|
|
|
|
* in the graph. This may fail if it would cause adjusted edge weights
|
|
|
|
* to exceed the supported numeric range. In case of floating point weights,
|
|
|
|
* the weight adjustment may also cause increased rounding errors.
|
|
|
|
*
|
|
|
|
* This function takes time O(m), where "m" is the number of edges.
|
|
|
|
*
|
|
|
|
* @tparam WeightType Type used to represent edge weights.
|
2023-05-16 22:19:55 +02:00
|
|
|
* @param edges Graph defined as a vector of weighted edges.
|
2023-05-10 20:56:30 +02:00
|
|
|
*
|
|
|
|
* @return Vector of edges with adjusted weights.
|
|
|
|
* If no adjustments are necessary, this will be a copy of the
|
|
|
|
* input vector.
|
|
|
|
*
|
|
|
|
* @throw std::invalid_argument If the graph is invalid or edge weights
|
|
|
|
* exceed the supported range.
|
|
|
|
*/
|
|
|
|
template <typename WeightType>
|
|
|
|
std::vector<Edge<WeightType>> adjust_weights_for_maximum_cardinality_matching(
|
|
|
|
const std::vector<Edge<WeightType>>& edges_in)
|
|
|
|
{
|
2023-05-12 18:09:32 +02:00
|
|
|
const WeightType min_safe_weight = std::numeric_limits<WeightType>::min() / 4;
|
|
|
|
const WeightType max_safe_weight = std::numeric_limits<WeightType>::max() / 4;
|
2023-05-10 20:56:30 +02:00
|
|
|
|
|
|
|
// Copy edges.
|
|
|
|
std::vector<Edge<WeightType>> edges(edges_in);
|
|
|
|
|
|
|
|
// Don't worry about empty graphs.
|
|
|
|
if (edges.empty()) {
|
|
|
|
return edges;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Count number of vertices.
|
|
|
|
// Determine minimum and maximum edge weight.
|
|
|
|
VertexId num_vertex = 0;
|
2023-05-12 18:09:32 +02:00
|
|
|
WeightType min_weight = edges.front().weight;
|
2023-05-10 20:56:30 +02:00
|
|
|
WeightType max_weight = min_weight;
|
|
|
|
|
|
|
|
const VertexId max_num_vertex = std::numeric_limits<VertexId>::max();
|
|
|
|
for (const Edge<WeightType>& edge : edges) {
|
|
|
|
VertexId m = std::max(edge.vt.first, edge.vt.second);
|
|
|
|
if (m >= max_num_vertex) {
|
|
|
|
throw std::invalid_argument("Vertex ID out of range");
|
|
|
|
}
|
|
|
|
num_vertex = std::max(num_vertex, m + 1);
|
|
|
|
|
|
|
|
if (! std::numeric_limits<WeightType>::is_integer) {
|
|
|
|
if (! std::isfinite(edge.weight)) {
|
|
|
|
throw std::invalid_argument(
|
|
|
|
"Edge weights must be finite numbers");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
min_weight = std::min(min_weight, edge.weight);
|
|
|
|
max_weight = std::max(max_weight, edge.weight);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Calculate weight range and required weight adjustment.
|
|
|
|
if ((min_weight < min_safe_weight) || (max_weight > max_safe_weight)) {
|
|
|
|
throw std::invalid_argument(
|
|
|
|
"Edge weight exceeds maximum supported value");
|
|
|
|
}
|
|
|
|
|
|
|
|
WeightType weight_range = max_weight - min_weight;
|
|
|
|
|
|
|
|
if (weight_range > max_safe_weight / num_vertex) {
|
|
|
|
throw std::invalid_argument(
|
|
|
|
"Adjusted edge weight exceeds maximum supported value");
|
|
|
|
}
|
|
|
|
|
|
|
|
// Do nothing if the weights already ensure maximum-cardinality.
|
|
|
|
if ((min_weight > 0) && (min_weight >= num_vertex * weight_range)) {
|
|
|
|
return edges;
|
|
|
|
}
|
|
|
|
|
|
|
|
WeightType delta;
|
|
|
|
if (weight_range > 0) {
|
|
|
|
// Increase weights to make minimum edge weight large enough
|
|
|
|
// to improve any non-maximum-cardinality matching.
|
|
|
|
delta = num_vertex * weight_range - min_weight;
|
|
|
|
} else {
|
|
|
|
// All weights are the same. Increase weights to make them positive.
|
|
|
|
delta = 1 - min_weight;
|
|
|
|
}
|
|
|
|
|
|
|
|
assert(delta >= 0);
|
|
|
|
|
|
|
|
if (delta > max_safe_weight - max_weight) {
|
|
|
|
throw std::invalid_argument(
|
|
|
|
"Adjusted edge weight exceeds maximum supported value");
|
|
|
|
}
|
|
|
|
|
|
|
|
// Increase all edge weights by "delta".
|
|
|
|
for (Edge<WeightType>& edge: edges) {
|
|
|
|
edge.weight += delta;
|
|
|
|
}
|
|
|
|
|
|
|
|
return edges;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
} // namespace mwmatching
|
|
|
|
|
|
|
|
#endif // MWMATCHING_H_
|