From efad10c1fbcdab34e50f577ca0478de5632c8bbc Mon Sep 17 00:00:00 2001 From: Jaron Kent-Dobias Date: Thu, 29 Sep 2022 17:35:53 +0200 Subject: Working implementation of the hungarian algorithm. --- hungarian.cpp | 283 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 283 insertions(+) create mode 100644 hungarian.cpp diff --git a/hungarian.cpp b/hungarian.cpp new file mode 100644 index 0000000..00cc91d --- /dev/null +++ b/hungarian.cpp @@ -0,0 +1,283 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "randutils/randutils.hpp" +#include "pcg-cpp/include/pcg_random.hpp" + +using Rng = randutils::random_generator; + +class Edge; + +class Vertex { +public: + unsigned index; + double y; + bool inZ; + std::vector> neighbors; + std::array coordinate; + + Vertex() : y(0), inZ(false) {}; + + void addEdge(Edge& e) { + neighbors.push_back(e); + } + + bool inMatching() const; +}; + +class Edge { +public: + Vertex* s; + Vertex* t; + bool orientation; + double weight; + bool inPath; + + Edge() : orientation(false) {}; + void setVertices(Vertex& red, Vertex& blue) { + s = &red; + t = &blue; + red.addEdge(*this); + blue.addEdge(*this); + } + Vertex& tail() { + if (orientation) { + return *t; + } else { + return *s; + } + } + Vertex& head() { + if (orientation) { + return *s; + } else { + return *t; + } + } + const Vertex& tail() const { + if (orientation) { + return *t; + } else { + return *s; + } + } + const Vertex& head() const { + if (orientation) { + return *s; + } else { + return *t; + } + } + bool isTight() const { + return std::abs(s->y + t->y - weight) < 1e-10; + } +}; + +bool Vertex::inMatching() const { + for (const Edge& e : neighbors) { + if (e.orientation) { + return true; + } + } + return false; +} + +class Graph { +public: + std::vector S; + std::vector T; + std::vector E; + + Graph(unsigned n, Rng& r) : S(n * (n + 1)), T(n * (n + 1)), E(pow(2 * n, 2)) { + for (unsigned i = 0; i < S.size(); i++) { + S[i].index = i; + S[i].coordinate = {2 * (i % (n + 1)), 2 * (i / (n + 1)) + 1}; + T[i].index = S.size() + i; + T[i].coordinate = {2 * (i % n) + 1, 2 * (i / n)}; + } + for (unsigned i = 0; i < E.size(); i++) { + Vertex& redVertex = S[(1 + (i % (2 * n))) / 2 + (n + 1) * ((i / 4) / n)]; + Vertex& blueVertex = T[(i % (2 * n)) / 2 + n * (((i + 2 * n) / 4) / n)]; + E[i].setVertices(redVertex, blueVertex); + E[i].weight = r.variate(1); + } + } + + void hungarian() { + std::queue> q; + + for (Vertex &v : T) { + v.inZ = false; + } + + for (Vertex &v : S) { + v.inZ = false; + + if (!v.inMatching()) { + q.push(v); + } + } + + while (!q.empty()) { + Vertex& v = q.front(); + q.pop(); + + v.inZ = true; + + for (Edge& e : v.neighbors) { + if (e.isTight() && (e.tail().index == v.index && !e.head().inZ)) { + q.push(e.head()); + } + } + } + + bool reversedPath = false; + + for (Vertex& v : T) { + if (v.inZ && !v.inMatching()) { + std::stack>::iterator, std::vector>::iterator, unsigned>> path; + + for (Edge& e : E) { + e.inPath = false; + } + + path.push({v.neighbors.begin(), v.neighbors.end(), v.index}); + + while (!path.empty()) { + auto [it, itEnd, i] = path.top(); + if (it == itEnd) { + path.pop(); + if (path.empty()) { + exit(1); + } + std::tie(it, itEnd, i) = path.top(); + it->get().inPath = false; + it++; + path.top() = {it, itEnd, i}; + } else { + Edge& e = it->get(); + + if (e.isTight() && (e.head().index == i && !e.inPath)) { + if (!e.tail().inMatching()) { + break; + } + + e.inPath= true; + path.push({e.tail().neighbors.begin(), e.tail().neighbors.end(), e.tail().index}); + } else { + it++; + path.top() = {it, itEnd, i}; + } + } + } + + + if (path.empty()) { + exit(1); + } + + + while (!path.empty()) { + auto [it, itEnd, i] = path.top(); + Edge& e = it->get(); + path.pop(); + e.orientation = !e.orientation; + } + + reversedPath = true; + break; + } + } + + if (!reversedPath) { + double Δ = std::numeric_limits::infinity(); + + for (Edge& e : E) { + if (e.s->inZ && !e.t->inZ) { + Δ = std::min(Δ, e.weight - e.s->y - e.t->y); + } + } + + for (Vertex& v : S) { + if (v.inZ) { + v.y += Δ; + } + } + + for (Vertex& v : T) { + if (v.inZ) { + v.y -= Δ; + } + } + + } + } + + bool isPerfect() const { + for (const Vertex& v : S) { + if (!v.inMatching()) { + return false; + } + } + for (const Vertex& v : T) { + if (!v.inMatching()) { + return false; + } + } + + return true; + } + +}; + +int main(int argc, char* argv[]) { + unsigned n = 100; + unsigned maxSteps = 1e8; + double beliefThreshold = 1; + + int opt; + + while ((opt = getopt(argc, argv, "n:m:t:")) != -1) { + switch (opt) { + case 'n': + n = atoi(optarg); + break; + case 'm': + maxSteps = (unsigned)atof(optarg); + break; + case 't': + beliefThreshold = atof(optarg); + break; + default: + exit(1); + } + } + + Rng r; + Graph G(n, r); + + unsigned steps = 0; + double minBelief = 0; + + while (!G.isPerfect()) { + G.hungarian(); + } + + for (const Edge& e : G.E) { + if (e.orientation) { + std::cout + << e.tail().coordinate[0] << " " + << e.tail().coordinate[1] << " " + << e.head().coordinate[0] << " " + << e.head().coordinate[1] << std::endl; + } + } + + return 0; +} -- cgit v1.2.3-54-g00ecf