edmonds-blossom-fixed
Version:
Edmond's weighted maximum matching algorithm (Blossom algorithm) ported from http://jorisvr.nl/maximummatching.html
645 lines (623 loc) • 20.4 kB
JavaScript
/*Converted to JS from Python by Matt Krick. Original: http://jorisvr.nl/maximummatching.html*/
module.exports = function (edges, maxCardinality) {
if (edges.length === 0) {
return edges;
}
var edmonds = new Edmonds(edges, maxCardinality);
return edmonds.maxWeightMatching();
};
var Edmonds = function (edges, maxCardinality) {
this.edges = edges;
this.maxCardinality = maxCardinality;
this.nEdge = edges.length;
this.init();
};
Edmonds.prototype.maxWeightMatching = function () {
for (var t = 0; t < this.nVertex; t++) {
//console.log('DEBUG: STAGE ' + t);
this.label = filledArray(2 * this.nVertex, 0);
this.bestEdge = filledArray(2 * this.nVertex, -1);
this.blossomBestEdges = initArrArr(2 * this.nVertex);
this.allowEdge = filledArray(this.nEdge, false);
this.queue = [];
for (var v = 0; v < this.nVertex; v++) {
if (this.mate[v] === -1 && this.label[this.inBlossom[v]] === 0) {
this.assignLabel(v, 1, -1);
}
}
var augmented = false;
while (true) {
//console.log('DEBUG: SUBSTAGE');
while (this.queue.length > 0 && !augmented) {
v = this.queue.pop();
//console.log('DEBUG: POP ', 'v=' + v);
//console.assert(this.label[this.inBlossom[v]] == 1);
for (var ii = 0; ii < this.neighbend[v].length; ii++) {
var p = this.neighbend[v][ii];
var k = ~~(p / 2);
var w = this.endpoint[p];
if (this.inBlossom[v] === this.inBlossom[w]) continue;
if (!this.allowEdge[k]) {
var kSlack = this.slack(k);
if (kSlack <= 0) {
this.allowEdge[k] = true;
}
}
if (this.allowEdge[k]) {
if (this.label[this.inBlossom[w]] === 0) {
this.assignLabel(w, 2, p ^ 1);
} else if (this.label[this.inBlossom[w]] === 1) {
var base = this.scanBlossom(v, w);
if (base >= 0) {
this.addBlossom(base, k);
} else {
this.augmentMatching(k);
augmented = true;
break;
}
} else if (this.label[w] === 0) {
//console.assert(this.label[this.inBlossom[w]] === 2);
this.label[w] = 2;
this.labelEnd[w] = p ^ 1;
}
} else if (this.label[this.inBlossom[w]] === 1) {
var b = this.inBlossom[v];
if (this.bestEdge[b] === -1 || kSlack < this.slack(this.bestEdge[b])) {
this.bestEdge[b] = k;
}
} else if (this.label[w] === 0) {
if (this.bestEdge[w] === -1 || kSlack < this.slack(this.bestEdge[w])) {
this.bestEdge[w] = k;
}
}
}
}
if (augmented) break;
var deltaType = -1;
var delta = [];
var deltaEdge = [];
var deltaBlossom = [];
if (!this.maxCardinality) {
deltaType = 1;
delta = getMin(this.dualVar, 0, this.nVertex - 1);
}
for (v = 0; v < this.nVertex; v++) {
if (this.label[this.inBlossom[v]] === 0 && this.bestEdge[v] !== -1) {
var d = this.slack(this.bestEdge[v]);
if (deltaType === -1 || d < delta) {
delta = d;
deltaType = 2;
deltaEdge = this.bestEdge[v];
}
}
}
for (b = 0; b < 2 * this.nVertex; b++) {
if (this.blossomParent[b] === -1 && this.label[b] === 1 && this.bestEdge[b] !== -1) {
kSlack = this.slack(this.bestEdge[b]);
////console.assert((kSlack % 2) == 0);
d = kSlack / 2;
if (deltaType === -1 || d < delta) {
delta = d;
deltaType = 3;
deltaEdge = this.bestEdge[b];
}
}
}
for (b = this.nVertex; b < this.nVertex * 2; b++) {
if (this.blossomBase[b] >= 0 && this.blossomParent[b] === -1 && this.label[b] === 2 && (deltaType === -1 || this.dualVar[b] < delta)) {
delta = this.dualVar[b];
deltaType = 4;
deltaBlossom = b;
}
}
if (deltaType === -1) {
//console.assert(this.maxCardinality);
deltaType = 1;
delta = Math.max(0, getMin(this.dualVar, 0, this.nVertex - 1));
}
for (v = 0; v < this.nVertex; v++) {
var curLabel = this.label[this.inBlossom[v]];
if (curLabel === 1) {
this.dualVar[v] -= delta;
} else if (curLabel === 2) {
this.dualVar[v] += delta;
}
}
for (b = this.nVertex; b < this.nVertex * 2; b++) {
if (this.blossomBase[b] >= 0 && this.blossomParent[b] === -1) {
if (this.label[b] === 1) {
this.dualVar[b] += delta;
} else if (this.label[b] === 2) {
this.dualVar[b] -= delta;
}
}
}
//console.log('DEBUG: deltaType', deltaType, ' delta: ', delta);
if (deltaType === 1) {
break;
} else if (deltaType === 2) {
this.allowEdge[deltaEdge] = true;
var i = this.edges[deltaEdge][0];
var j = this.edges[deltaEdge][1];
var wt = this.edges[deltaEdge][2];
if (this.label[this.inBlossom[i]] === 0) {
i = i ^ j;
j = j ^ i;
i = i ^ j;
}
//console.assert(this.label[this.inBlossom[i]] == 1);
this.queue.push(i);
} else if (deltaType === 3) {
this.allowEdge[deltaEdge] = true;
i = this.edges[deltaEdge][0];
j = this.edges[deltaEdge][1];
wt = this.edges[deltaEdge][2];
//console.assert(this.label[this.inBlossom[i]] == 1);
this.queue.push(i);
} else if (deltaType === 4) {
this.expandBlossom(deltaBlossom, false);
}
}
if (!augmented) break;
for (b = this.nVertex; b < this.nVertex * 2; b++) {
if (this.blossomParent[b] === -1 && this.blossomBase[b] >= 0 && this.label[b] === 1 && this.dualVar[b] === 0) {
this.expandBlossom(b, true);
}
}
}
for (v = 0; v < this.nVertex; v++) {
if (this.mate[v] >= 0) {
this.mate[v] = this.endpoint[this.mate[v]];
}
}
for (v = 0; v < this.nVertex; v++) {
//console.assert(this.mate[v] == -1 || this.mate[this.mate[v]] == v);
}
return this.mate;
};
Edmonds.prototype.slack = function (k) {
var i = this.edges[k][0];
var j = this.edges[k][1];
var wt = this.edges[k][2];
return this.dualVar[i] + this.dualVar[j] - 2 * wt;
};
Edmonds.prototype.blossomLeaves = function (b) {
if (b < this.nVertex) {
return [b];
}
var leaves = [];
var childList = this.blossomChilds[b];
for (var t = 0; t < childList.length; t++) {
if (childList[t] <= this.nVertex) {
leaves.push(childList[t]);
} else {
var leafList = this.blossomLeaves(childList[t]);
for (var v = 0; v < leafList.length; v++) {
leaves.push(leafList[v]);
}
}
}
return leaves;
};
Edmonds.prototype.assignLabel = function (w, t, p) {
//console.log('DEBUG: assignLabel(' + w + ',' + t + ',' + p + '}');
var b = this.inBlossom[w];
//console.assert(this.label[w] === 0 && this.label[b] === 0);
this.label[w] = this.label[b] = t;
this.labelEnd[w] = this.labelEnd[b] = p;
this.bestEdge[w] = this.bestEdge[b] = -1;
if (t === 1) {
this.queue.push.apply(this.queue, this.blossomLeaves(b));
//console.log('DEBUG: PUSH ' + this.blossomLeaves(b).toString());
} else if (t === 2) {
var base = this.blossomBase[b];
//console.assert(this.mate[base] >= 0);
this.assignLabel(this.endpoint[this.mate[base]], 1, this.mate[base] ^ 1);
}
};
Edmonds.prototype.scanBlossom = function (v, w) {
//console.log('DEBUG: scanBlossom(' + v + ',' + w + ')');
var path = [];
var base = -1;
while (v !== -1 || w !== -1) {
var b = this.inBlossom[v];
if ((this.label[b] & 4)) {
base = this.blossomBase[b];
break;
}
//console.assert(this.label[b] === 1);
path.push(b);
this.label[b] = 5;
//console.assert(this.labelEnd[b] === this.mate[this.blossomBase[b]]);
if (this.labelEnd[b] === -1) {
v = -1;
} else {
v = this.endpoint[this.labelEnd[b]];
b = this.inBlossom[v];
//console.assert(this.label[b] === 2);
//console.assert(this.labelEnd[b] >= 0);
v = this.endpoint[this.labelEnd[b]];
}
if (w !== -1) {
v = v ^ w;
w = w ^ v;
v = v ^ w;
}
}
for (var ii = 0; ii < path.length; ii++) {
b = path[ii];
this.label[b] = 1;
}
return base;
};
Edmonds.prototype.addBlossom = function (base, k) {
var v = this.edges[k][0];
var w = this.edges[k][1];
var wt = this.edges[k][2];
var bb = this.inBlossom[base];
var bv = this.inBlossom[v];
var bw = this.inBlossom[w];
b = this.unusedBlossoms.pop();
//console.log('DEBUG: addBlossom(' + base + ',' + k + ')' + ' (v=' + v + ' w=' + w + ')' + ' -> ' + b);
this.blossomBase[b] = base;
this.blossomParent[b] = -1;
this.blossomParent[bb] = b;
path = this.blossomChilds[b] = [];
var endPs = this.blossomEndPs[b] = [];
while (bv !== bb) {
this.blossomParent[bv] = b;
path.push(bv);
endPs.push(this.labelEnd[bv]);
//console.assert(this.label[bv] === 2 || (this.label[bv] === 1 && this.labelEnd[bv] === this.mate[this.blossomBase[bv]]));
//console.assert(this.labelEnd[bv] >= 0);
v = this.endpoint[this.labelEnd[bv]];
bv = this.inBlossom[v];
}
path.push(bb);
path.reverse();
endPs.reverse();
endPs.push((2 * k));
while (bw !== bb) {
this.blossomParent[bw] = b;
path.push(bw);
endPs.push(this.labelEnd[bw] ^ 1);
//console.assert(this.label[bw] === 2 || (this.label[bw] === 1 && this.labelEnd[bw] === this.mate[this.blossomBase[bw]]));
//console.assert(this.labelEnd[bw] >= 0);
w = this.endpoint[this.labelEnd[bw]];
bw = this.inBlossom[w];
}
//console.assert(this.label[bb] === 1);
this.label[b] = 1;
this.labelEnd[b] = this.labelEnd[bb];
this.dualVar[b] = 0;
var leaves = this.blossomLeaves(b);
for (var ii = 0; ii < leaves.length; ii++) {
v = leaves[ii];
if (this.label[this.inBlossom[v]] === 2) {
this.queue.push(v);
}
this.inBlossom[v] = b;
}
var bestEdgeTo = filledArray(2 * this.nVertex, -1);
for (ii = 0; ii < path.length; ii++) {
bv = path[ii];
if (this.blossomBestEdges[bv].length === 0) {
var nbLists = [];
leaves = this.blossomLeaves(bv);
for (var x = 0; x < leaves.length; x++) {
v = leaves[x];
nbLists[x] = [];
for (var y = 0; y < this.neighbend[v].length; y++) {
var p = this.neighbend[v][y];
nbLists[x].push(~~(p / 2));
}
}
} else {
nbLists = [this.blossomBestEdges[bv]];
}
//console.log('DEBUG: nbLists ' + nbLists.toString());
for (x = 0; x < nbLists.length; x++) {
var nbList = nbLists[x];
for (y = 0; y < nbList.length; y++) {
k = nbList[y];
var i = this.edges[k][0];
var j = this.edges[k][1];
wt = this.edges[k][2];
if (this.inBlossom[j] === b) {
i = i ^ j;
j = j ^ i;
i = i ^ j;
}
var bj = this.inBlossom[j];
if (bj !== b && this.label[bj] === 1 && (bestEdgeTo[bj] === -1 || this.slack(k) < this.slack(bestEdgeTo[bj]))) {
bestEdgeTo[bj] = k;
}
}
}
this.blossomBestEdges[bv] = [];
this.bestEdge[bv] = -1;
}
var be = [];
for (ii = 0; ii < bestEdgeTo.length; ii++) {
k = bestEdgeTo[ii];
if (k !== -1) {
be.push(k);
}
}
this.blossomBestEdges[b] = be;
//console.log('DEBUG: blossomBestEdges[' + b + ']= ' + this.blossomBestEdges[b].toString());
this.bestEdge[b] = -1;
for (ii = 0; ii < this.blossomBestEdges[b].length; ii++) {
k = this.blossomBestEdges[b][ii];
if (this.bestEdge[b] === -1 || this.slack(k) < this.slack(this.bestEdge[b])) {
this.bestEdge[b] = k;
}
}
//console.log('DEBUG: blossomChilds[' + b + ']= ' + this.blossomChilds[b].toString());
};
Edmonds.prototype.expandBlossom = function (b, endStage) {
//console.log('DEBUG: expandBlossom(' + b + ',' + endStage + ') ' + this.blossomChilds[b].toString());
for (var ii = 0; ii < this.blossomChilds[b].length; ii++) {
var s = this.blossomChilds[b][ii];
this.blossomParent[s] = -1;
if (s < this.nVertex) {
this.inBlossom[s] = s;
} else if (endStage && this.dualVar[s] === 0) {
this.expandBlossom(s, endStage);
} else {
var leaves = this.blossomLeaves(s);
for (var jj = 0; jj < leaves.length; jj++) {
v = leaves[jj];
this.inBlossom[v] = s;
}
}
}
if (!endStage && this.label[b] === 2) {
//console.assert(this.labelEnd[b] >= 0);
var entryChild = this.inBlossom[this.endpoint[this.labelEnd[b] ^ 1]];
var j = this.blossomChilds[b].indexOf(entryChild);
if ((j & 1)) {
j -= this.blossomChilds[b].length;
var jStep = 1;
var endpTrick = 0;
} else {
jStep = -1;
endpTrick = 1;
}
var p = this.labelEnd[b];
while (j !== 0) {
this.label[this.endpoint[p ^ 1]] = 0;
this.label[this.endpoint[pIndex(this.blossomEndPs[b], j - endpTrick) ^ endpTrick ^ 1]] = 0;
this.assignLabel(this.endpoint[p ^ 1], 2, p);
this.allowEdge[~~(pIndex(this.blossomEndPs[b], j - endpTrick) / 2)] = true;
j += jStep;
p = pIndex(this.blossomEndPs[b], j - endpTrick) ^ endpTrick;
this.allowEdge[~~(p / 2)] = true;
j += jStep;
}
var bv = pIndex(this.blossomChilds[b], j);
this.label[this.endpoint[p ^ 1]] = this.label[bv] = 2;
this.labelEnd[this.endpoint[p ^ 1]] = this.labelEnd[bv] = p;
this.bestEdge[bv] = -1;
j += jStep;
while (pIndex(this.blossomChilds[b], j) !== entryChild) {
bv = pIndex(this.blossomChilds[b], j);
if (this.label[bv] === 1) {
j += jStep;
continue;
}
leaves = this.blossomLeaves(bv);
for (ii = 0; ii < leaves.length; ii++) {
v = leaves[ii];
if (this.label[v] !== 0) break;
}
if (this.label[v] !== 0) {
//console.assert(this.label[v] === 2);
//console.assert(this.inBlossom[v] === bv);
this.label[v] = 0;
this.label[this.endpoint[this.mate[this.blossomBase[bv]]]] = 0;
this.assignLabel(v, 2, this.labelEnd[v]);
}
j += jStep;
}
}
this.label[b] = this.labelEnd[b] = -1;
this.blossomEndPs[b] = this.blossomChilds[b] = [];
this.blossomBase[b] = -1;
this.blossomBestEdges[b] = [];
this.bestEdge[b] = -1;
this.unusedBlossoms.push(b);
};
Edmonds.prototype.augmentBlossom = function (b, v) {
//console.log('DEBUG: augmentBlossom(' + b + ',' + v + ')');
var i, j;
var t = v;
while (this.blossomParent[t] !== b) {
t = this.blossomParent[t];
}
if (t > this.nVertex) {
this.augmentBlossom(t, v);
}
i = j = this.blossomChilds[b].indexOf(t);
if ((i & 1)) {
j -= this.blossomChilds[b].length;
var jStep = 1;
var endpTrick = 0;
} else {
jStep = -1;
endpTrick = 1;
}
while (j !== 0) {
j += jStep;
t = pIndex(this.blossomChilds[b], j);
var p = pIndex(this.blossomEndPs[b], j - endpTrick) ^ endpTrick;
if (t >= this.nVertex) {
this.augmentBlossom(t, this.endpoint[p]);
}
j += jStep;
t = pIndex(this.blossomChilds[b], j);
if (t >= this.nVertex) {
this.augmentBlossom(t, this.endpoint[p ^ 1]);
}
this.mate[this.endpoint[p]] = p ^ 1;
this.mate[this.endpoint[p ^ 1]] = p;
}
//console.log('DEBUG: PAIR ' + this.endpoint[p] + ' ' + this.endpoint[p^1] + '(k=' + ~~(p/2) + ')');
this.blossomChilds[b] = this.blossomChilds[b].slice(i).concat(this.blossomChilds[b].slice(0, i));
this.blossomEndPs[b] = this.blossomEndPs[b].slice(i).concat(this.blossomEndPs[b].slice(0, i));
this.blossomBase[b] = this.blossomBase[this.blossomChilds[b][0]];
//console.assert(this.blossomBase[b] === v);
};
Edmonds.prototype.augmentMatching = function (k) {
var v = this.edges[k][0];
var w = this.edges[k][1];
//console.log('DEBUG: augmentMatching(' + k + ')' + ' (v=' + v + ' ' + 'w=' + w);
//console.log('DEBUG: PAIR ' + v + ' ' + w + '(k=' + k + ')');
for (var ii = 0; ii < 2; ii++) {
if (ii === 0) {
var s = v;
var p = 2 * k + 1;
} else {
s = w;
p = 2 * k;
}
while (true) {
var bs = this.inBlossom[s];
//console.assert(this.label[bs] === 1);
//console.assert(this.labelEnd[bs] === this.mate[this.blossomBase[bs]]);
if (bs >= this.nVertex) {
this.augmentBlossom(bs, s);
}
this.mate[s] = p;
if (this.labelEnd[bs] === -1) break;
var t = this.endpoint[this.labelEnd[bs]];
var bt = this.inBlossom[t];
//console.assert(this.label[bt] === 2);
//console.assert(this.labelEnd[bt] >= 0);
s = this.endpoint[this.labelEnd[bt]];
var j = this.endpoint[this.labelEnd[bt] ^ 1];
//console.assert(this.blossomBase[bt] === t);
if (bt >= this.nVertex) {
this.augmentBlossom(bt, j);
}
this.mate[j] = this.labelEnd[bt];
p = this.labelEnd[bt] ^ 1;
//console.log('DEBUG: PAIR ' + s + ' ' + t + '(k=' + ~~(p/2) + ')');
}
}
};
//INIT STUFF//
Edmonds.prototype.init = function () {
this.nVertexInit();
this.maxWeightInit();
this.endpointInit();
this.neighbendInit();
this.mate = filledArray(this.nVertex, -1);
this.label = filledArray(2 * this.nVertex, 0); //remove?
this.labelEnd = filledArray(2 * this.nVertex, -1);
this.inBlossomInit();
this.blossomParent = filledArray(2 * this.nVertex, -1);
this.blossomChilds = initArrArr(2 * this.nVertex);
this.blossomBaseInit();
this.blossomEndPs = initArrArr(2 * this.nVertex);
this.bestEdge = filledArray(2 * this.nVertex, -1); //remove?
this.blossomBestEdges = initArrArr(2 * this.nVertex); //remove?
this.unusedBlossomsInit();
this.dualVarInit();
this.allowEdge = filledArray(this.nEdge, false); //remove?
this.queue = []; //remove?
};
Edmonds.prototype.blossomBaseInit = function () {
var base = [];
for (var i = 0; i < this.nVertex; i++) {
base[i] = i;
}
var negs = filledArray(this.nVertex, -1);
this.blossomBase = base.concat(negs);
};
Edmonds.prototype.dualVarInit = function () {
var mw = filledArray(this.nVertex, this.maxWeight);
var zeros = filledArray(this.nVertex, 0);
this.dualVar = mw.concat(zeros);
};
Edmonds.prototype.unusedBlossomsInit = function () {
var i, unusedBlossoms = [];
for (i = this.nVertex; i < 2 * this.nVertex; i++) {
unusedBlossoms.push(i);
}
this.unusedBlossoms = unusedBlossoms;
};
Edmonds.prototype.inBlossomInit = function () {
var i, inBlossom = [];
for (i = 0; i < this.nVertex; i++) {
inBlossom[i] = i;
}
this.inBlossom = inBlossom;
};
Edmonds.prototype.neighbendInit = function () {
var k, i, j;
var neighbend = initArrArr(this.nVertex);
for (k = 0; k < this.nEdge; k++) {
i = this.edges[k][0];
j = this.edges[k][1];
neighbend[i].push(2 * k + 1);
neighbend[j].push(2 * k);
}
this.neighbend = neighbend;
};
Edmonds.prototype.endpointInit = function () {
var p;
var endpoint = [];
for (p = 0; p < 2 * this.nEdge; p++) {
endpoint[p] = this.edges[~~(p / 2)][p % 2];
}
this.endpoint = endpoint;
};
Edmonds.prototype.nVertexInit = function () {
var nVertex = 0;
for (var k = 0; k < this.nEdge; k++) {
var i = this.edges[k][0];
var j = this.edges[k][1];
if (i >= nVertex) nVertex = i + 1;
if (j >= nVertex) nVertex = j + 1;
}
this.nVertex = nVertex;
};
Edmonds.prototype.maxWeightInit = function () {
var maxWeight = 0;
for (var k = 0; k < this.nEdge; k++) {
var weight = this.edges[k][2];
if (weight > maxWeight) {
maxWeight = weight;
}
}
this.maxWeight = maxWeight;
};
//HELPERS//
function filledArray(len, fill) {
var i, newArray = [];
for (i = 0; i < len; i++) {
newArray[i] = fill;
}
return newArray;
}
function initArrArr(len) {
var arr = [];
for (var i = 0; i < len; i++) {
arr[i] = [];
}
return arr;
}
function getMin(arr, start, end) {
var min = Infinity;
for (var i = start; i <= end; i++) {
if (arr[i] < min) {
min = arr[i];
}
}
return min;
}
function pIndex(arr, idx) {
//if idx is negative, go from the back
return idx < 0 ? arr[arr.length + idx] : arr[idx];
}