Add travelling salesman problem.

This commit is contained in:
Oleksii Trekhleb 2018-05-21 08:58:22 +03:00
parent 296b20ed16
commit 35476a2f3f
4 changed files with 183 additions and 0 deletions

View File

@ -92,6 +92,7 @@ a set of rules that precisely defines a sequence of operations.
* [Eulerian Path and Eulerian Circuit](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/eulerian-path) - Fleury's algorithm - Visit every edge exactly once
* [Hamiltonian Cycle](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/hamiltonian-cycle) - Visit every vertex exactly once
* [Strongly Connected Components](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/strongly-connected-components) - Kosaraju's algorithm
* [Travelling Salesman Problem](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/travelling-salesman) - brute force algorithm
* **Uncategorized**
* [Tower of Hanoi](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/uncategorized/hanoi-tower)
* [N-Queens Problem](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/uncategorized/n-queens)
@ -105,6 +106,7 @@ algorithm is an abstraction higher than a computer program.
* **Brute Force** - look at all the possibilities and selects the best solution
* [Maximum Subarray](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/sets/maximum-subarray)
* [Travelling Salesman Problem](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/travelling-salesman)
* **Greedy** - choose the best option at the current time, without any consideration for the future
* [Unbound Knapsack Problem](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/sets/knapsack-problem)
* [Dijkstra Algorithm](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/dijkstra) - finding shortest path to all graph vertices

View File

@ -0,0 +1,26 @@
# Travelling Salesman 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
and returns to the origin city?"
![Travelling Salesman](https://upload.wikimedia.org/wikipedia/commons/1/11/GLPK_solution_of_a_travelling_salesman_problem.svg)
Solution of a travelling salesman problem: the black line shows
the shortest possible loop that connects every red dot.
![Travelling Salesman Graph](https://upload.wikimedia.org/wikipedia/commons/3/30/Weighted_K4.svg)
TSP can be modelled as an undirected weighted graph, such that
cities are the graph's vertices, paths are the graph's edges,
and a path's distance is the edge's weight. It is a minimization
problem starting and finishing at a specified vertex after having
visited each other vertex exactly once. Often, the model is a
complete graph (i.e. each pair of vertices is connected by an
edge). If no path exists between two cities, adding an arbitrarily
long edge will complete the graph without affecting the optimal tour.
## References
- [Wikipedia](https://en.wikipedia.org/wiki/Travelling_salesman_problem)

View File

@ -0,0 +1,51 @@
import GraphVertex from '../../../../data-structures/graph/GraphVertex';
import GraphEdge from '../../../../data-structures/graph/GraphEdge';
import Graph from '../../../../data-structures/graph/Graph';
import bfTravellingSalesman from '../bfTravellingSalesman';
describe('bfTravellingSalesman', () => {
it('should solve problem for simple graph', () => {
const vertexA = new GraphVertex('A');
const vertexB = new GraphVertex('B');
const vertexC = new GraphVertex('C');
const vertexD = new GraphVertex('D');
const edgeAB = new GraphEdge(vertexA, vertexB, 1);
const edgeBD = new GraphEdge(vertexB, vertexD, 1);
const edgeDC = new GraphEdge(vertexD, vertexC, 1);
const edgeCA = new GraphEdge(vertexC, vertexA, 1);
const edgeBA = new GraphEdge(vertexB, vertexA, 5);
const edgeDB = new GraphEdge(vertexD, vertexB, 8);
const edgeCD = new GraphEdge(vertexC, vertexD, 7);
const edgeAC = new GraphEdge(vertexA, vertexC, 4);
const edgeAD = new GraphEdge(vertexA, vertexD, 2);
const edgeDA = new GraphEdge(vertexD, vertexA, 3);
const edgeBC = new GraphEdge(vertexB, vertexC, 3);
const edgeCB = new GraphEdge(vertexC, vertexB, 9);
const graph = new Graph(true);
graph
.addEdge(edgeAB)
.addEdge(edgeBD)
.addEdge(edgeDC)
.addEdge(edgeCA)
.addEdge(edgeBA)
.addEdge(edgeDB)
.addEdge(edgeCD)
.addEdge(edgeAC)
.addEdge(edgeAD)
.addEdge(edgeDA)
.addEdge(edgeBC)
.addEdge(edgeCB);
const salesmanPath = bfTravellingSalesman(graph);
expect(salesmanPath.length).toBe(4);
expect(salesmanPath[0].getKey()).toEqual(vertexA.getKey());
expect(salesmanPath[1].getKey()).toEqual(vertexB.getKey());
expect(salesmanPath[2].getKey()).toEqual(vertexD.getKey());
expect(salesmanPath[3].getKey()).toEqual(vertexC.getKey());
});
});

View File

@ -0,0 +1,104 @@
/**
* Get all possible paths
* @param {GraphVertex} startVertex
* @param {GraphVertex[][]} [paths]
* @param {GraphVertex[]} [path]
*/
function findAllPaths(startVertex, paths = [], path = []) {
// Clone path.
const currentPath = [...path];
// Add startVertex to the path.
currentPath.push(startVertex);
// Generate visited set from path.
const visitedSet = currentPath.reduce((accumulator, vertex) => {
const updatedAccumulator = { ...accumulator };
updatedAccumulator[vertex.getKey()] = vertex;
return updatedAccumulator;
}, {});
// Get all unvisited neighbors of startVertex.
const unvisitedNeighbors = startVertex.getNeighbors().filter((neighbor) => {
return !visitedSet[neighbor.getKey()];
});
// If there no unvisited neighbors then treat current path as complete and save it.
if (!unvisitedNeighbors.length) {
paths.push(currentPath);
return paths;
}
// Go through all the neighbors.
for (let neighborIndex = 0; neighborIndex < unvisitedNeighbors.length; neighborIndex += 1) {
const currentUnvisitedNeighbor = unvisitedNeighbors[neighborIndex];
findAllPaths(currentUnvisitedNeighbor, paths, currentPath);
}
return paths;
}
/**
* @param {number[][]} adjacencyMatrix
* @param {object} verticesIndices
* @param {GraphVertex[]} cycle
* @return {number}
*/
function getCycleWeight(adjacencyMatrix, verticesIndices, cycle) {
let weight = 0;
for (let cycleIndex = 1; cycleIndex < cycle.length; cycleIndex += 1) {
const fromVertex = cycle[cycleIndex - 1];
const toVertex = cycle[cycleIndex];
const fromVertexIndex = verticesIndices[fromVertex.getKey()];
const toVertexIndex = verticesIndices[toVertex.getKey()];
weight += adjacencyMatrix[fromVertexIndex][toVertexIndex];
}
return weight;
}
/**
* BRUTE FORCE approach to solve Traveling Salesman Problem.
*
* @param {Graph} graph
* @return {GraphVertex[]}
*/
export default function bfTravellingSalesman(graph) {
// Pick starting point from where we will traverse the graph.
const startVertex = graph.getAllVertices()[0];
// BRUTE FORCE.
// Generate all possible paths from startVertex.
const allPossiblePaths = findAllPaths(startVertex);
// Filter out paths that are not cycles.
const allPossibleCycles = allPossiblePaths.filter((path) => {
/** @var {GraphVertex} */
const lastVertex = path[path.length - 1];
const lastVertexNeighbors = lastVertex.getNeighbors();
return lastVertexNeighbors.includes(startVertex);
});
// Go through all possible cycles and pick the one with minimum overall tour weight.
const adjacencyMatrix = graph.getAdjacencyMatrix();
const verticesIndices = graph.getVerticesIndices();
let salesmanPath = [];
let salesmanPathWeight = null;
for (let cycleIndex = 0; cycleIndex < allPossibleCycles.length; cycleIndex += 1) {
const currentCycle = allPossibleCycles[cycleIndex];
const currentCycleWeight = getCycleWeight(adjacencyMatrix, verticesIndices, currentCycle);
// If current cycle weight is smaller then previous ones treat current cycle as most optimal.
if (salesmanPathWeight === null || currentCycleWeight < salesmanPathWeight) {
salesmanPath = currentCycle;
salesmanPathWeight = currentCycleWeight;
}
}
// Return the solution.
return salesmanPath;
}