org.jgrapht.alg.tour.NearestNeighborHeuristicTSP Maven / Gradle / Ivy
/*
* (C) Copyright 2019-2023, by Peter Harman 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 java.util.*;
import static org.jgrapht.util.ArrayUtil.*;
/**
* The nearest neighbour 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 perhaps the simplest and most straightforward TSP heuristic. The key to this algorithm is
* to always visit the nearest city.
*
*
*
* The tour computed with a {@code Nearest-Neighbor-Heuristic} can vary depending on the first
* vertex visited. The first vertex for the next or for multiple subsequent tour computations (calls
* of {@link #getTour(Graph)}) can be specified in the constructors
* {@link #NearestNeighborHeuristicTSP(Object)} or {@link #NearestNeighborHeuristicTSP(Iterable)}.
* This can be used for example to ensure that the first vertices visited are different for
* subsequent calls of {@code getTour(Graph)}. Once each specified first vertex is used, the first
* vertex in subsequent tour computations is selected randomly from the graph. Alternatively
* {@link #NearestNeighborHeuristicTSP(Random)} or {@link #NearestNeighborHeuristicTSP(long)} can be
* used to specify a {@code Random} used to randomly select the vertex visited first.
*
*
*
* The implementation of this class is based on:
* Nilsson, Christian. "Heuristics for the traveling salesman problem." Linkoping University 38
* (2003)
*
*
*
* The runtime complexity of this class is $O(V^2)$.
*
*
*
* This algorithm requires that the graph is complete.
*
*
* @param the graph vertex type
* @param the graph edge type
*
* @author Peter Harman
* @author Hannes Wellmann
*/
public class NearestNeighborHeuristicTSP
extends HamiltonianCycleAlgorithmBase
{
private Random rng;
/** Nulled, if it has no next */
private Iterator initiaVertex;
/**
* Constructor. By default a random vertex is chosen to start.
*/
public NearestNeighborHeuristicTSP()
{
this(null, new Random());
}
/**
* Constructor
*
* @param first First vertex to visit
* @throws NullPointerException if first is null
*/
public NearestNeighborHeuristicTSP(V first)
{
this(
Collections.singletonList(
Objects.requireNonNull(first, "Specified initial vertex cannot be null")),
new Random());
}
/**
* Constructor
*
* @param initialVertices The Iterable of vertices visited first in subsequent tour computations
* (per call of {@link #getTour(Graph)} another vertex of the Iterable is used as first)
* @throws NullPointerException if first is null
*/
public NearestNeighborHeuristicTSP(Iterable initialVertices)
{
this(
Objects.requireNonNull(initialVertices, "Specified initial vertices cannot be null"),
new Random());
}
/**
* Constructor
*
* @param seed seed for the random number generator
*/
public NearestNeighborHeuristicTSP(long seed)
{
this(null, new Random(seed));
}
/**
* Constructor
*
* @param rng Random number generator
* @throws NullPointerException if rng is null
*/
public NearestNeighborHeuristicTSP(Random rng)
{
this(null, Objects.requireNonNull(rng, "Random number generator cannot be null"));
}
/**
* Constructor
*
* @param initialVertices The Iterable of vertices visited first in subsequent tour
* computations, or null to choose at random
* @param rng Random number generator
*/
private NearestNeighborHeuristicTSP(Iterable initialVertices, Random rng)
{
if (initialVertices != null) {
Iterator iterator = initialVertices.iterator();
this.initiaVertex = iterator.hasNext() ? iterator : null;
}
this.rng = rng;
}
// algorithm
/**
* Computes a tour using the nearest neighbour heuristic.
*
* @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
* @throws IllegalArgumentException if the specified initial vertex is not in the graph
*/
@Override
public GraphPath getTour(Graph graph)
{
checkGraph(graph);
if (graph.vertexSet().size() == 1) {
return getSingletonTour(graph);
}
Set vertexSet = graph.vertexSet();
int n = vertexSet.size();
@SuppressWarnings("unchecked") V[] path = (V[]) vertexSet.toArray(new Object[n + 1]);
List pathList = Arrays.asList(path); // List backed by path-array
// move initial vertex to the beginning
int initalIndex = getFirstVertexIndex(pathList);
swap(path, 0, initalIndex);
// search nearest neighbors
int limit = n - 1; // last vertex won't be changed -> no need to check it
for (int i = 1; i < limit; i++) {
V v = path[i - 1];
// path before i is established. The element at i must be the closest to element at i-1.
// -> get nearest of remaining elements (index >= i) and set it as next in path
int nearestNeighbor = getNearestNeighbor(v, path, i, graph);
swap(path, i, nearestNeighbor);
}
path[n] = path[0]; // close tour manually. Arrays.asList does not support add
return closedVertexListToTour(pathList, graph);
}
/**
* Returns the start vertex of the tour about to compute.
*
* @param path the initial path, containing all vertices in unspecified order
* @return the vertex to start with
* @throws IllegalArgumentException if the specified initial vertex is not in the graph
*/
private int getFirstVertexIndex(List path)
{
if (initiaVertex != null) {
V first = initiaVertex.next();
if (!initiaVertex.hasNext()) {
initiaVertex = null; // release the resource backing the iterator immediately
}
int initialIndex = path.indexOf(first);
if (initialIndex < 0) {
throw new IllegalArgumentException("Specified initial vertex is not in graph");
}
return initialIndex;
} else { // first not specified
return rng.nextInt(path.size() - 1); // path has size n+1
}
}
/**
* Find the vertex in the range staring at {@code from} that is closest to the element at index
* from-1.
*
* @param current the vertex for which the nearest neighbor is searched
* @param vertices the vertices of the graph. The unvisited neighbors start at index
* {@code start}
* @param start the index of the first vertex to consider
* @param g the graph containing the vertices
*
* @return the index of the unvisited vertex closest to the vertex at firstNeighbor-1.
*/
private static int getNearestNeighbor(V current, V[] vertices, int start, Graph g)
{
int closest = -1;
double minDist = Double.MAX_VALUE;
int n = vertices.length - 1; // last element in vertices is null
for (int i = start; i < n; i++) {
V v = vertices[i];
double vDist = g.getEdgeWeight(g.getEdge(current, v));
if (vDist < minDist) {
closest = i;
minDist = vDist;
}
}
return closest;
}
}