All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jgrapht.alg.tour.TwoOptHeuristicTSP Maven / Gradle / Ivy

/*
 * (C) Copyright 2018-2021, by Dimitrios Michail and Contributors.
 *
 * JGraphT : a free Java graph-theory library
 *
 * See the CONTRIBUTORS.md file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0, or the
 * GNU Lesser General Public License v2.1 or later
 * which is available at
 * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html.
 *
 * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later
 */
package org.jgrapht.alg.tour;

import org.jgrapht.*;
import org.jgrapht.alg.interfaces.*;
import org.jgrapht.util.*;

import java.util.*;

import static org.jgrapht.util.ArrayUtil.*;

/**
 * The 2-opt heuristic algorithm for the TSP problem.
 *
 * 

* The travelling salesman problem (TSP) asks the following question: "Given a list of cities and * the distances between each pair of cities, what is the shortest possible route that visits each * city exactly once and returns to the origin city?". *

* *

* This is an implementation of the 2-opt improvement heuristic algorithm. The algorithm generates * passes initial tours and then iteratively improves the tours until a local minimum is * reached. In each iteration it applies the best possible 2-opt move which means to find the best * pair of edges $(i,i+1)$ and $(j,j+1)$ such that replacing them with $(i,j)$ and $(i+1,j+1)$ * minimizes the tour length. The default initial tours use RandomTour, however an alternative * algorithm can be provided to create the initial tour. Initial tours generated using * NearestNeighborHeuristicTSP give good results and performance. *

* *

* See wikipedia for more details. * *

* This implementation can also be used in order to try to improve an existing tour. See method * {@link #improveTour(GraphPath)}. * * @param the graph vertex type * @param the graph edge type * * @author Dimitrios Michail * @author Hannes Wellmann */ public class TwoOptHeuristicTSP extends HamiltonianCycleAlgorithmBase implements HamiltonianCycleImprovementAlgorithm { private final int passes; private final HamiltonianCycleAlgorithm initializer; private final double minCostImprovement; private Graph graph; private int n; private double[][] dist; private Map index; private List revIndex; /** * Constructor. By default one initial random tour is used. */ public TwoOptHeuristicTSP() { this(1, new Random()); } /** * Constructor * * @param passes how many initial random tours to check */ public TwoOptHeuristicTSP(int passes) { this(passes, new Random()); } /** * Constructor * * @param passes how many initial random tours to check * @param seed seed for the random number generator */ public TwoOptHeuristicTSP(int passes, long seed) { this(passes, new Random(seed)); } /** * Constructor * * @param passes how many initial random tours to check * @param rng random number generator */ public TwoOptHeuristicTSP(int passes, Random rng) { this(passes, new RandomTourTSP<>(rng)); } /** * Constructor * * @param passes how many initial random tours to check * @param rng random number generator * @param minCostImprovement Minimum cost improvement per iteration */ public TwoOptHeuristicTSP(int passes, Random rng, double minCostImprovement) { this(passes, new RandomTourTSP<>(rng), minCostImprovement); } /** * Constructor * * @param initializer Algorithm to generate initial tour */ public TwoOptHeuristicTSP(HamiltonianCycleAlgorithm initializer) { this(1, initializer); } /** * Constructor * * @param passes how many initial tours to check * @param initializer Algorithm to generate initial tour */ public TwoOptHeuristicTSP(int passes, HamiltonianCycleAlgorithm initializer) { this(passes, initializer, 1e-8); } /** * Constructor * * @param passes how many initial tours to check * @param initializer Algorithm to generate initial tours * @param minCostImprovement Minimum cost improvement per iteration */ public TwoOptHeuristicTSP( int passes, HamiltonianCycleAlgorithm initializer, double minCostImprovement) { if (passes < 1) { throw new IllegalArgumentException("passes must be at least one"); } this.passes = passes; this.initializer = Objects.requireNonNull(initializer, "Initial solver algorithm cannot be null"); this.minCostImprovement = Math.abs(minCostImprovement); } // algorithm /** * Computes a 2-approximate tour. * * @param graph the input graph * @return a tour * @throws IllegalArgumentException if the graph is not undirected * @throws IllegalArgumentException if the graph is not complete * @throws IllegalArgumentException if the graph contains no vertices */ @Override public GraphPath getTour(Graph graph) { checkGraph(graph); if (graph.vertexSet().size() == 1) { return getSingletonTour(graph); } // Initialize vertex index and distances init(graph); // Execute 2-opt for the specified number of passes and a new permutation in each pass GraphPath best = tourToPath(improve(createInitialTour())); for (int i = 1; i < passes; i++) { GraphPath other = tourToPath(improve(createInitialTour())); if (other.getWeight() < best.getWeight()) { best = other; } } return best; } /** * Try to improve a tour by running the 2-opt heuristic. * * @param tour a tour * @return a possibly improved tour */ @Override public GraphPath improveTour(GraphPath tour) { init(tour.getGraph()); return tourToPath(improve(pathToTour(tour))); } /** * Initialize graph and mapping to integer vertices. * * @param graph the input graph */ private void init(Graph graph) { this.graph = graph; this.n = graph.vertexSet().size(); this.dist = new double[n][n]; VertexToIntegerMapping vertex2index = new VertexToIntegerMapping<>(graph.vertexSet()); this.index = vertex2index.getVertexMap(); this.revIndex = vertex2index.getIndexList(); for (E e : graph.edgeSet()) { V s = graph.getEdgeSource(e); int si = index.get(s); V t = graph.getEdgeTarget(e); int ti = index.get(t); double weight = graph.getEdgeWeight(e); dist[si][ti] = weight; dist[ti][si] = weight; } } /** * Create an initial tour * * @return a complete tour */ private int[] createInitialTour() { return pathToTour(initializer.getTour(graph)); } /** * Improve the tour using the 2-opt heuristic. In each iteration it applies the best possible * 2-opt move which means to find the best pair of edges $(i,i+1)$ and $(j,j+1)$ such that * replacing them with $(i,j)$ and $(i+1,j+1)$ minimizes the tour length. * *

* The returned array instance might or might not be the input array. * * @param tour the input tour * @return a possibly improved tour */ private int[] improve(int[] tour) { double minChange; while (true) { minChange = -minCostImprovement; int mini = -1; int minj = -1; for (int i = 0; i < n - 2; i++) { for (int j = i + 2; j < n; j++) { int ci = tour[i]; int ci1 = tour[i + 1]; int cj = tour[j]; int cj1 = tour[j + 1]; double change = dist[ci][cj] + dist[ci1][cj1] - dist[ci][ci1] - dist[cj][cj1]; if (change < minChange) { minChange = change; mini = i; minj = j; } } } if (mini != -1 && minj != -1) { // apply move: reverse path from mini+1 to minj (both inclusive) reverse(tour, mini + 1, minj); } else { return tour; } } } /** * Transform from an array representation to a graph path. * * @param tour an array containing the index of the vertices of the tour * @return a graph path */ private GraphPath tourToPath(int[] tour) { List tourVertices = new ArrayList<>(n + 1); for (int vi : tour) { V v = revIndex.get(vi); tourVertices.add(v); } return closedVertexListToTour(tourVertices, graph); } /** * Transform from a path representation to an array representation. * * @param path graph path * @return an array containing the index of the vertices of the tour */ private int[] pathToTour(GraphPath path) { boolean[] visited = new boolean[n]; List vertexList = path.getVertexList(); // first and last element are the starting vertex if (vertexList.size() != n + 1) { throw new IllegalArgumentException("Not a valid tour"); } int[] tour = new int[n + 1]; for (int i = 0; i < n; i++) { int vi = index.get(vertexList.get(i)); if (visited[vi]) { throw new IllegalArgumentException("Not a valid tour"); } visited[vi] = true; tour[i] = vi; } tour[n] = tour[0]; return tour; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy