blob: 43d67bcc8cd4e7809ef5a9357a0d578841009b22 [file] [log] [blame]
// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'graph.dart';
// See: Asymmetric Traveling Salesman Problem.
// Strategy for finding optimal overlapping of chunks of a larger table,
// to save space.
// Does so by solving traveling salesman/hamiltonian cycle in a graph
// where the distance between chunks is how little they overlap
// (chunk length minus overlap size).
/// Optimize a cycle of a graph to minimize the edge weight.
///
/// The [cycle] must have the same node as first and last element.
///
/// This is an implementation of one step of 3-opt, a simple algorithm to
/// approximate an asymmetric traveling salesman problem (ATSP).
/// It splits the cycle into three parts and then find the best recombination
/// of the parts, each potentially reversed.
bool opt3(Graph graph, List<int> cycle) {
// Perhaps optimize the weight computations by creating
// a single array of cumulative weights, so any range can be computed
// as a difference between two points in that array.
for (var i = 1; i < cycle.length; i++) {
// Find three cut points in the cycle, A|B, C|D, and E|F,
// then find the cumulative weights of each section
// B-C, C-D, and E-A, in both directions, as well as the
// weight between the end-points.
//
// with Z being used to represent the start/end of the list
// representation (so the A-F/F-A ranges cross over the cycle
// representation edges)
// Find the weights
var nodeA = cycle[i - 1];
var nodeB = cycle[i];
// Weight of one-step transition from A to B.
var wAB = graph.weight(nodeA, nodeB);
var wBA = graph.weight(nodeB, nodeA);
// Weight of entire path for start to A.
var pZA = graph.pathWeight(cycle, 0, i - 1);
var pAZ = graph.pathWeight(cycle, i - 1, 0);
for (var j = i + 1; j < cycle.length; j++) {
var nodeC = cycle[j - 1];
var nodeD = cycle[j];
var wAC = graph.weight(nodeA, nodeC);
var wCA = graph.weight(nodeC, nodeA);
var wAD = graph.weight(nodeA, nodeD);
var wDA = graph.weight(nodeD, nodeA);
var wBD = graph.weight(nodeB, nodeD);
var wDB = graph.weight(nodeD, nodeB);
var wCD = graph.weight(nodeC, nodeD);
var wDC = graph.weight(nodeD, nodeC);
var pBC = graph.pathWeight(cycle, i, j - 1);
var pCB = graph.pathWeight(cycle, j - 1, i);
for (var k = j + 1; k < cycle.length; k++) {
var nodeE = cycle[k - 1];
var nodeF = cycle[k];
var wAE = graph.weight(nodeA, nodeE);
var wEA = graph.weight(nodeE, nodeA);
var wBE = graph.weight(nodeB, nodeE);
var wEB = graph.weight(nodeE, nodeB);
var wCE = graph.weight(nodeC, nodeE);
var wEC = graph.weight(nodeE, nodeC);
var wBF = graph.weight(nodeB, nodeF);
var wFB = graph.weight(nodeF, nodeB);
var wCF = graph.weight(nodeC, nodeF);
var wFC = graph.weight(nodeF, nodeC);
var wEF = graph.weight(nodeE, nodeF);
var wFE = graph.weight(nodeF, nodeE);
var wDF = graph.weight(nodeD, nodeF);
var wFD = graph.weight(nodeF, nodeD);
var pDE = graph.pathWeight(cycle, j, k - 1);
var pED = graph.pathWeight(cycle, k - 1, j);
var pFA = graph.pathWeight(cycle, k, cycle.length - 1) + pZA;
var pAF = graph.pathWeight(cycle, cycle.length - 1, k) + pAZ;
// Find best recombination of the three sections B-C, D-E, F-A,
// with each possibly reversed.
// Since there are only two ways to order three-element cycles,
// and three parts that can be reversed, this gives 16 combinations.
var wABCDEF = pFA + wAB + pBC + wCD + pDE + wEF;
var wACBDEF = pFA + wAC + pCB + wBD + pDE + wEF;
var wABCEDF = pFA + wAB + pBC + wCE + pED + wDF;
var wACBEDF = pFA + wAC + pCB + wBE + pED + wDF;
var wFBCDEA = pAF + wFB + pBC + wCD + pDE + wEA;
var wFCBDEA = pAF + wFC + pCB + wBD + pDE + wEA;
var wFBCEDA = pAF + wFB + pBC + wCE + pED + wDA;
var wFCBEDA = pAF + wFC + pCB + wBE + pED + wDA;
var wADEBCF = pFA + wAD + pDE + wEB + pBC + wCF;
var wADECBF = pFA + wAD + pDE + wEC + pCB + wBF;
var wAEDBCF = pFA + wAE + pED + wDB + pBC + wCF;
var wAEDCBF = pFA + wAE + pED + wDC + pCB + wBF;
var wFDEBCA = pAF + wFD + pDE + wEB + pBC + wCA;
var wFDECBA = pAF + wFD + pDE + wEC + pCB + wBA;
var wFEDBCA = pAF + wFE + pED + wDB + pBC + wCA;
var wFEDCBA = pAF + wFE + pED + wDC + pCB + wBA;
var best = min([
wABCDEF,
wACBDEF,
wABCEDF,
wACBEDF,
wFBCDEA,
wFCBDEA,
wFBCEDA,
wFCBEDA,
wADEBCF,
wADECBF,
wAEDBCF,
wAEDCBF,
wFDEBCA,
wFDECBA,
wFEDBCA,
wFEDCBA,
]);
if (best < wABCDEF) {
// Reorder and reverse to match the (or a) best solution.
if (best == wACBDEF) {
_reverse(cycle, i, j - 1);
} else if (best == wABCEDF) {
_reverse(cycle, j, k - 1);
} else if (best == wACBEDF) {
_reverse(cycle, i, j - 1);
_reverse(cycle, j, k - 1);
} else if (best == wFBCDEA) {
_reverse(cycle, i, k - 1);
_reverse(cycle, 0, cycle.length - 1);
} else if (best == wFCBDEA) {
_reverse(cycle, i, j - 1);
_reverse(cycle, i, k - 1);
_reverse(cycle, 0, cycle.length - 1);
} else if (best == wFBCEDA) {
_reverse(cycle, j, k - 1);
_reverse(cycle, i, k - 1);
_reverse(cycle, 0, cycle.length - 1);
} else if (best == wFCBEDA) {
_reverse(cycle, i, j - 1);
_reverse(cycle, j, k - 1);
_reverse(cycle, i, k - 1);
_reverse(cycle, 0, cycle.length - 1);
} else if (best == wADEBCF) {
_reverse(cycle, i, j - 1);
_reverse(cycle, j, k - 1);
_reverse(cycle, i, k - 1);
} else if (best == wADECBF) {
_reverse(cycle, j, k - 1);
_reverse(cycle, i, k - 1);
} else if (best == wAEDBCF) {
_reverse(cycle, i, j - 1);
_reverse(cycle, i, k - 1);
} else if (best == wAEDCBF) {
_reverse(cycle, i, k - 1);
} else if (best == wFDEBCA) {
_reverse(cycle, i, j - 1);
_reverse(cycle, j, k - 1);
_reverse(cycle, 0, cycle.length - 1);
} else if (best == wFDECBA) {
_reverse(cycle, j, k - 1);
_reverse(cycle, 0, cycle.length - 1);
} else if (best == wFEDBCA) {
_reverse(cycle, i, j - 1);
_reverse(cycle, 0, cycle.length - 1);
} else if (best == wFEDCBA) {
_reverse(cycle, 0, cycle.length - 1);
} else {
throw AssertionError('Unreachable');
}
return true;
}
}
}
}
return false;
}
/// Reverses a slice of a list.
void _reverse(List<int> list, int from, int to) {
while (from < to) {
var tmp = list[from];
list[from] = list[to];
list[to] = tmp;
from++;
to--;
}
}
int min(List<int> values) {
var result = values[0];
for (var i = 1; i < values.length; i++) {
var value = values[i];
if (value < result) result = value;
}
return result;
}