| // 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; |
| } |