forked from trekhleb/javascript-algorithms
-
Notifications
You must be signed in to change notification settings - Fork 0
/
bfTravellingSalesman.js
104 lines (86 loc) · 3.26 KB
/
bfTravellingSalesman.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
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;
}