mirror of
git://git.tartarus.org/simon/puzzles.git
synced 2025-04-21 08:01:30 -07:00
Files

Having stated the principle in the previous commit, I should apply it consistently. A source file linked into the Puzzles library of common support code should not also define a main() under ifdef. This commit only goes as far as the _library_ support modules. It would be a much bigger job to do the same for all the actual _puzzles_ that have test main()s or standalone-solver main()s. And it's not necessary, because modifying one of those source files only triggers a rebuild of _one_ puzzle, not absolutely everything. (Not to mention that it's quite likely the puzzle and the test main() will need to be modified in conjunction anyway.) As in the previous commit, this has required exposing a few internal API functions as global, and maybe editing them a bit. In particular, the one-shot internal function that divvy_rectangle() loops on until it succeeds is now exposed as divvy_rectangle_attempt(), which means the test program doesn't have to condition a failure counter into the real function. I've thrown away penrose-vector-test completely, because that didn't look like a test program with any ongoing use at all - it was surely vestigial, while James was getting the vector representation up and running in the first place.
332 lines
9.9 KiB
C
332 lines
9.9 KiB
C
/*
|
|
* Implementation of matching.h.
|
|
*/
|
|
|
|
#include <assert.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
|
|
#include "puzzles.h"
|
|
#include "matching.h"
|
|
|
|
struct scratch {
|
|
/*
|
|
* Current contents of the in-progress matching. LtoR is an array
|
|
* of nl integers, each of which holds a value in {0,1,...,nr-1},
|
|
* or -1 for no current assignment. RtoL is exactly the reverse.
|
|
*
|
|
* Invariant: LtoR[i] is non-empty and equal to j if and only if
|
|
* RtoL[j] is non-empty and equal to i.
|
|
*/
|
|
int *LtoR, *RtoL;
|
|
|
|
/*
|
|
* Arrays of nl and nr integer respectively, giving the layer
|
|
* assigned to each integer in the breadth-first search step of
|
|
* the algorithm.
|
|
*/
|
|
int *Llayer, *Rlayer;
|
|
|
|
/*
|
|
* Arrays of nl and nr integers respectively, used to hold the
|
|
* to-do queues in the breadth-first search.
|
|
*/
|
|
int *Lqueue, *Rqueue;
|
|
|
|
/*
|
|
* An augmenting path of vertices, alternating between L vertices
|
|
* (in the even-numbered positions, starting at 0) and R (in the
|
|
* odd positions). Must be long enough to hold any such path that
|
|
* never repeats a vertex, i.e. must be at least 2*min(nl,nr) in
|
|
* size.
|
|
*/
|
|
int *augpath;
|
|
|
|
/*
|
|
* Track the progress of the depth-first search at each
|
|
* even-numbered layer. Has one element for each even-numbered
|
|
* position in augpath.
|
|
*/
|
|
int *dfsstate;
|
|
|
|
/*
|
|
* Store a random permutation of the L vertex indices, if we're
|
|
* randomising the dfs phase.
|
|
*/
|
|
int *Lorder;
|
|
};
|
|
|
|
size_t matching_scratch_size(int nl, int nr)
|
|
{
|
|
size_t n;
|
|
int nmin = (nl < nr ? nl : nr);
|
|
|
|
n = (sizeof(struct scratch) + sizeof(int)-1)/sizeof(int);
|
|
n += nl; /* LtoR */
|
|
n += nr; /* RtoL */
|
|
n += nl; /* Llayer */
|
|
n += nr; /* Rlayer */
|
|
n += nl; /* Lqueue */
|
|
n += nr; /* Rqueue */
|
|
n += 2*nmin; /* augpath */
|
|
n += nmin; /* dfsstate */
|
|
n += nl; /* Lorder */
|
|
return n * sizeof(int);
|
|
}
|
|
|
|
int matching_with_scratch(void *scratchv,
|
|
int nl, int nr, int **adjlists, int *adjsizes,
|
|
random_state *rs, int *outl, int *outr)
|
|
{
|
|
struct scratch *s = (struct scratch *)scratchv;
|
|
int L, R, i, j;
|
|
|
|
/*
|
|
* Set up the various array pointers in the scratch space.
|
|
*/
|
|
{
|
|
int *p = scratchv;
|
|
int nmin = (nl < nr ? nl : nr);
|
|
|
|
p += (sizeof(struct scratch) + sizeof(int)-1)/sizeof(int);
|
|
s->LtoR = p; p += nl;
|
|
s->RtoL = p; p += nr;
|
|
s->Llayer = p; p += nl;
|
|
s->Rlayer = p; p += nr;
|
|
s->Lqueue = p; p += nl;
|
|
s->Rqueue = p; p += nr;
|
|
s->augpath = p; p += 2*nmin;
|
|
s->dfsstate = p; p += nmin;
|
|
s->Lorder = p; p += nl;
|
|
}
|
|
|
|
/*
|
|
* Set up the initial matching, which is empty.
|
|
*/
|
|
for (L = 0; L < nl; L++)
|
|
s->LtoR[L] = -1;
|
|
for (R = 0; R < nr; R++)
|
|
s->RtoL[R] = -1;
|
|
|
|
while (1) {
|
|
/*
|
|
* Breadth-first search starting from the unassigned left
|
|
* vertices, traversing edges from left to right only if they
|
|
* are _not_ part of the matching, and from right to left only
|
|
* if they _are_. We assign a 'layer number' to all vertices
|
|
* visited by this search, with the starting vertices being
|
|
* layer 0 and every successor of a layer-n node being layer
|
|
* n+1.
|
|
*/
|
|
int Lqs, Rqs, layer, target_layer;
|
|
|
|
for (L = 0; L < nl; L++)
|
|
s->Llayer[L] = -1;
|
|
for (R = 0; R < nr; R++)
|
|
s->Rlayer[R] = -1;
|
|
|
|
Lqs = 0;
|
|
for (L = 0; L < nl; L++) {
|
|
if (s->LtoR[L] == -1) {
|
|
s->Llayer[L] = 0;
|
|
s->Lqueue[Lqs++] = L;
|
|
}
|
|
}
|
|
|
|
layer = 0;
|
|
while (1) {
|
|
bool found_free_R_vertex = false;
|
|
|
|
Rqs = 0;
|
|
for (i = 0; i < Lqs; i++) {
|
|
L = s->Lqueue[i];
|
|
assert(s->Llayer[L] == layer);
|
|
|
|
for (j = 0; j < adjsizes[L]; j++) {
|
|
R = adjlists[L][j];
|
|
if (R != s->LtoR[L] && s->Rlayer[R] == -1) {
|
|
s->Rlayer[R] = layer+1;
|
|
s->Rqueue[Rqs++] = R;
|
|
if (s->RtoL[R] == -1)
|
|
found_free_R_vertex = true;
|
|
}
|
|
}
|
|
}
|
|
layer++;
|
|
|
|
if (found_free_R_vertex)
|
|
break;
|
|
|
|
if (Rqs == 0)
|
|
goto done;
|
|
|
|
Lqs = 0;
|
|
for (j = 0; j < Rqs; j++) {
|
|
R = s->Rqueue[j];
|
|
assert(s->Rlayer[R] == layer);
|
|
if ((L = s->RtoL[R]) != -1 && s->Llayer[L] == -1) {
|
|
s->Llayer[L] = layer+1;
|
|
s->Lqueue[Lqs++] = L;
|
|
}
|
|
}
|
|
layer++;
|
|
|
|
if (Lqs == 0)
|
|
goto done;
|
|
}
|
|
|
|
target_layer = layer;
|
|
|
|
/*
|
|
* Vertices in the target layer are only interesting if
|
|
* they're actually unassigned. Blanking out the others here
|
|
* will save us a special case in the dfs loop below.
|
|
*/
|
|
for (R = 0; R < nr; R++)
|
|
if (s->Rlayer[R] == target_layer && s->RtoL[R] != -1)
|
|
s->Rlayer[R] = -1;
|
|
|
|
/*
|
|
* Choose an ordering in which to try the L vertices at the
|
|
* start of the next pass.
|
|
*/
|
|
for (L = 0; L < nl; L++)
|
|
s->Lorder[L] = L;
|
|
if (rs)
|
|
shuffle(s->Lorder, nl, sizeof(*s->Lorder), rs);
|
|
|
|
/*
|
|
* Now depth-first search through that layered set of vertices
|
|
* to find as many (vertex-)disjoint augmenting paths as we
|
|
* can, and for each one we find, augment the matching.
|
|
*/
|
|
s->dfsstate[0] = 0;
|
|
i = 0;
|
|
while (1) {
|
|
/*
|
|
* Find the next vertex to go on the end of augpath.
|
|
*/
|
|
if (i == 0) {
|
|
/* In this special case, we're just looking for L
|
|
* vertices that are not yet assigned. */
|
|
if (s->dfsstate[i] == nl)
|
|
break; /* entire DFS has finished */
|
|
|
|
L = s->Lorder[s->dfsstate[i]++];
|
|
|
|
if (s->Llayer[L] != 2*i)
|
|
continue; /* skip this vertex */
|
|
} else {
|
|
/* In the more usual case, we're going through the
|
|
* adjacency list for the previous L vertex. */
|
|
L = s->augpath[2*i-2];
|
|
j = s->dfsstate[i]++;
|
|
if (j == adjsizes[L]) {
|
|
/* Run out of neighbours of the previous vertex. */
|
|
i--;
|
|
continue;
|
|
}
|
|
if (rs && adjsizes[L] - j > 1) {
|
|
int which = j + random_upto(rs, adjsizes[L] - j);
|
|
int tmp = adjlists[L][which];
|
|
adjlists[L][which] = adjlists[L][j];
|
|
adjlists[L][j] = tmp;
|
|
}
|
|
R = adjlists[L][j];
|
|
|
|
if (s->Rlayer[R] != 2*i-1)
|
|
continue; /* skip this vertex */
|
|
|
|
s->augpath[2*i-1] = R;
|
|
s->Rlayer[R] = -1; /* mark vertex as visited */
|
|
|
|
if (2*i-1 == target_layer) {
|
|
/*
|
|
* We've found an augmenting path, in the form of
|
|
* an even-sized list of vertices alternating
|
|
* L,R,...,L,R, with the initial L and final R
|
|
* vertex free and otherwise each R currently
|
|
* connected to the next L. Adjust so that each L
|
|
* connects to the next R, increasing the edge
|
|
* count in the matching by 1.
|
|
*/
|
|
for (j = 0; j < 2*i; j += 2) {
|
|
s->LtoR[s->augpath[j]] = s->augpath[j+1];
|
|
s->RtoL[s->augpath[j+1]] = s->augpath[j];
|
|
}
|
|
|
|
/*
|
|
* Having dealt with that path, and already marked
|
|
* all its vertices as visited, rewind right to
|
|
* the start and resume our DFS from a new
|
|
* starting L-vertex.
|
|
*/
|
|
i = 0;
|
|
continue;
|
|
}
|
|
|
|
L = s->RtoL[R];
|
|
if (s->Llayer[L] != 2*i)
|
|
continue; /* skip this vertex */
|
|
}
|
|
|
|
s->augpath[2*i] = L;
|
|
s->Llayer[L] = -1; /* mark vertex as visited */
|
|
i++;
|
|
s->dfsstate[i] = 0;
|
|
}
|
|
}
|
|
|
|
done:
|
|
/*
|
|
* Fill in the output arrays.
|
|
*/
|
|
if (outl) {
|
|
for (i = 0; i < nl; i++)
|
|
outl[i] = s->LtoR[i];
|
|
}
|
|
if (outr) {
|
|
for (j = 0; j < nr; j++)
|
|
outr[j] = s->RtoL[j];
|
|
}
|
|
|
|
/*
|
|
* Return the number of matching edges.
|
|
*/
|
|
for (i = j = 0; i < nl; i++)
|
|
if (s->LtoR[i] != -1)
|
|
j++;
|
|
return j;
|
|
}
|
|
|
|
int matching(int nl, int nr, int **adjlists, int *adjsizes,
|
|
random_state *rs, int *outl, int *outr)
|
|
{
|
|
void *scratch;
|
|
int size;
|
|
int ret;
|
|
|
|
size = matching_scratch_size(nl, nr);
|
|
scratch = malloc(size);
|
|
if (!scratch)
|
|
return -1;
|
|
|
|
ret = matching_with_scratch(scratch, nl, nr, adjlists, adjsizes,
|
|
rs, outl, outr);
|
|
|
|
free(scratch);
|
|
|
|
return ret;
|
|
}
|
|
|
|
void matching_witness(void *scratchv, int nl, int nr, int *witness)
|
|
{
|
|
struct scratch *s = (struct scratch *)scratchv;
|
|
int i, j;
|
|
|
|
for (i = 0; i < nl; i++)
|
|
witness[i] = s->Llayer[i] == -1;
|
|
for (j = 0; j < nr; j++)
|
|
witness[nl + j] = s->Rlayer[j] == -1;
|
|
}
|