new path finder

main
Ogoun 4 months ago
parent 1f7d9440b2
commit cfe644f4f9

@ -1,979 +0,0 @@
class GraphEdge {
/**
* @param {GraphVertex} startVertex
* @param {GraphVertex} endVertex
* @param {number} [weight=1]
*/
constructor(startVertex, endVertex, weight = 0) {
this.startVertex = startVertex;
this.endVertex = endVertex;
this.weight = weight;
}
/**
* @return {string}
*/
getKey() {
const startVertexKey = this.startVertex.getKey();
const endVertexKey = this.endVertex.getKey();
return `${startVertexKey}_${endVertexKey}`;
}
/**
* @return {GraphEdge}
*/
reverse() {
const tmp = this.startVertex;
this.startVertex = this.endVertex;
this.endVertex = tmp;
return this;
}
/**
* @return {string}
*/
toString() {
return this.getKey();
}
}
class Comparator {
/**
* Constructor.
* @param {function(a: *, b: *)} [compareFunction] - It may be custom compare function that, let's
* say may compare custom objects together.
*/
constructor(compareFunction) {
this.compare = compareFunction || Comparator.defaultCompareFunction;
}
/**
* Default comparison function. It just assumes that "a" and "b" are strings or numbers.
* @param {(string|number)} a
* @param {(string|number)} b
* @returns {number}
*/
static defaultCompareFunction(a, b) {
if (a === b) {
return 0;
}
return a < b ? -1 : 1;
}
/**
* Checks if two variables are equal.
* @param {*} a
* @param {*} b
* @return {boolean}
*/
equal(a, b) {
return this.compare(a, b) === 0;
}
/**
* Checks if variable "a" is less than "b".
* @param {*} a
* @param {*} b
* @return {boolean}
*/
lessThan(a, b) {
return this.compare(a, b) < 0;
}
/**
* Checks if variable "a" is greater than "b".
* @param {*} a
* @param {*} b
* @return {boolean}
*/
greaterThan(a, b) {
return this.compare(a, b) > 0;
}
/**
* Checks if variable "a" is less than or equal to "b".
* @param {*} a
* @param {*} b
* @return {boolean}
*/
lessThanOrEqual(a, b) {
return this.lessThan(a, b) || this.equal(a, b);
}
/**
* Checks if variable "a" is greater than or equal to "b".
* @param {*} a
* @param {*} b
* @return {boolean}
*/
greaterThanOrEqual(a, b) {
return this.greaterThan(a, b) || this.equal(a, b);
}
/**
* Reverses the comparison order.
*/
reverse() {
const compareOriginal = this.compare;
this.compare = (a, b) => compareOriginal(b, a);
}
}
class LinkedListNode {
constructor(value, next = null) {
this.value = value;
this.next = next;
}
toString(callback) {
return callback ? callback(this.value) : `${this.value}`;
}
}
class LinkedList {
/**
* @param {Function} [comparatorFunction]
*/
constructor(comparatorFunction) {
/** @var LinkedListNode */
this.head = null;
/** @var LinkedListNode */
this.tail = null;
this.compare = new Comparator(comparatorFunction);
}
/**
* @param {*} value
* @return {LinkedList}
*/
prepend(value) {
// Make new node to be a head.
const newNode = new LinkedListNode(value, this.head);
this.head = newNode;
// If there is no tail yet let's make new node a tail.
if (!this.tail) {
this.tail = newNode;
}
return this;
}
/**
* @param {*} value
* @return {LinkedList}
*/
append(value) {
const newNode = new LinkedListNode(value);
// If there is no head yet let's make new node a head.
if (!this.head) {
this.head = newNode;
this.tail = newNode;
return this;
}
// Attach new node to the end of linked list.
this.tail.next = newNode;
this.tail = newNode;
return this;
}
/**
* @param {*} value
* @param {number} index
* @return {LinkedList}
*/
insert(value, rawIndex) {
const index = rawIndex < 0 ? 0 : rawIndex;
if (index === 0) {
this.prepend(value);
} else {
let count = 1;
let currentNode = this.head;
const newNode = new LinkedListNode(value);
while (currentNode) {
if (count === index) break;
currentNode = currentNode.next;
count += 1;
}
if (currentNode) {
newNode.next = currentNode.next;
currentNode.next = newNode;
} else {
if (this.tail) {
this.tail.next = newNode;
this.tail = newNode;
} else {
this.head = newNode;
this.tail = newNode;
}
}
}
return this;
}
/**
* @param {*} value
* @return {LinkedListNode}
*/
delete(value) {
if (!this.head) {
return null;
}
let deletedNode = null;
// If the head must be deleted then make next node that is different
// from the head to be a new head.
while (this.head && this.compare.equal(this.head.value, value)) {
deletedNode = this.head;
this.head = this.head.next;
}
let currentNode = this.head;
if (currentNode !== null) {
// If next node must be deleted then make next node to be a next next one.
while (currentNode.next) {
if (this.compare.equal(currentNode.next.value, value)) {
deletedNode = currentNode.next;
currentNode.next = currentNode.next.next;
} else {
currentNode = currentNode.next;
}
}
}
// Check if tail must be deleted.
if (this.compare.equal(this.tail.value, value)) {
this.tail = currentNode;
}
return deletedNode;
}
/**
* @param {Object} findParams
* @param {*} findParams.value
* @param {function} [findParams.callback]
* @return {LinkedListNode}
*/
find({
value = undefined,
callback = undefined
}) {
if (!this.head) {
return null;
}
let currentNode = this.head;
while (currentNode) {
// If callback is specified then try to find node by callback.
if (callback && callback(currentNode.value)) {
return currentNode;
}
// If value is specified then try to compare by value..
if (value !== undefined && this.compare.equal(currentNode.value, value)) {
return currentNode;
}
currentNode = currentNode.next;
}
return null;
}
/**
* @return {LinkedListNode}
*/
deleteTail() {
const deletedTail = this.tail;
if (this.head === this.tail) {
// There is only one node in linked list.
this.head = null;
this.tail = null;
return deletedTail;
}
// If there are many nodes in linked list...
// Rewind to the last node and delete "next" link for the node before the last one.
let currentNode = this.head;
while (currentNode.next) {
if (!currentNode.next.next) {
currentNode.next = null;
} else {
currentNode = currentNode.next;
}
}
this.tail = currentNode;
return deletedTail;
}
/**
* @return {LinkedListNode}
*/
deleteHead() {
if (!this.head) {
return null;
}
const deletedHead = this.head;
if (this.head.next) {
this.head = this.head.next;
} else {
this.head = null;
this.tail = null;
}
return deletedHead;
}
/**
* @param {*[]} values - Array of values that need to be converted to linked list.
* @return {LinkedList}
*/
fromArray(values) {
values.forEach((value) => this.append(value));
return this;
}
/**
* @return {LinkedListNode[]}
*/
toArray() {
const nodes = [];
let currentNode = this.head;
while (currentNode) {
nodes.push(currentNode);
currentNode = currentNode.next;
}
return nodes;
}
/**
* @param {function} [callback]
* @return {string}
*/
toString(callback) {
return this.toArray().map((node) => node.toString(callback)).toString();
}
/**
* Reverse a linked list.
* @returns {LinkedList}
*/
reverse() {
let currNode = this.head;
let prevNode = null;
let nextNode = null;
while (currNode) {
// Store next node.
nextNode = currNode.next;
// Change next node of the current node so it would link to previous node.
currNode.next = prevNode;
// Move prevNode and currNode nodes one step forward.
prevNode = currNode;
currNode = nextNode;
}
// Reset head and tail.
this.tail = this.head;
this.head = prevNode;
return this;
}
}
class GraphVertex {
/**
* @param {*} value
*/
constructor(value) {
if (value === undefined) {
throw new Error('Graph vertex must have a value');
}
/**
* @param {GraphEdge} edgeA
* @param {GraphEdge} edgeB
*/
const edgeComparator = (edgeA, edgeB) => {
if (edgeA.getKey() === edgeB.getKey()) {
return 0;
}
return edgeA.getKey() < edgeB.getKey() ? -1 : 1;
};
// Normally you would store string value like vertex name.
// But generally it may be any object as well
this.value = value;
this.edges = new LinkedList(edgeComparator);
}
/**
* @param {GraphEdge} edge
* @returns {GraphVertex}
*/
addEdge(edge) {
this.edges.append(edge);
return this;
}
/**
* @param {GraphEdge} edge
*/
deleteEdge(edge) {
this.edges.delete(edge);
}
/**
* @returns {GraphVertex[]}
*/
getNeighbors() {
const edges = this.edges.toArray();
/** @param {LinkedListNode} node */
const neighborsConverter = (node) => {
return node.value.startVertex === this ? node.value.endVertex : node.value.startVertex;
};
// Return either start or end vertex.
// For undirected graphs it is possible that current vertex will be the end one.
return edges.map(neighborsConverter);
}
/**
* @return {GraphEdge[]}
*/
getEdges() {
return this.edges.toArray().map((linkedListNode) => linkedListNode.value);
}
/**
* @return {number}
*/
getDegree() {
return this.edges.toArray().length;
}
/**
* @param {GraphEdge} requiredEdge
* @returns {boolean}
*/
hasEdge(requiredEdge) {
const edgeNode = this.edges.find({
callback: (edge) => edge === requiredEdge,
});
return !!edgeNode;
}
/**
* @param {GraphVertex} vertex
* @returns {boolean}
*/
hasNeighbor(vertex) {
const vertexNode = this.edges.find({
callback: (edge) => edge.startVertex === vertex || edge.endVertex === vertex,
});
return !!vertexNode;
}
/**
* @param {GraphVertex} vertex
* @returns {(GraphEdge|null)}
*/
findEdge(vertex) {
const edgeFinder = (edge) => {
return edge.startVertex === vertex || edge.endVertex === vertex;
};
const edge = this.edges.find({
callback: edgeFinder
});
return edge ? edge.value : null;
}
/**
* @returns {string}
*/
getKey() {
return this.value;
}
/**
* @return {GraphVertex}
*/
deleteAllEdges() {
this.getEdges().forEach((edge) => this.deleteEdge(edge));
return this;
}
/**
* @param {function} [callback]
* @returns {string}
*/
toString(callback) {
return callback ? callback(this.value) : `${this.value}`;
}
}
class Graph {
/**
* @param {boolean} isDirected
*/
constructor(isDirected = false) {
this.vertices = {};
this.edges = {};
this.isDirected = isDirected;
}
/**
* @param {GraphVertex} newVertex
* @returns {Graph}
*/
addVertex(newVertex) {
this.vertices[newVertex.getKey()] = newVertex;
return this;
}
/**
* @param {string} vertexKey
* @returns GraphVertex
*/
getVertexByKey(vertexKey) {
return this.vertices[vertexKey];
}
/**
* @param {GraphVertex} vertex
* @returns {GraphVertex[]}
*/
getNeighbors(vertex) {
return vertex.getNeighbors();
}
/**
* @return {GraphVertex[]}
*/
getAllVertices() {
return Object.values(this.vertices);
}
/**
* @return {GraphEdge[]}
*/
getAllEdges() {
return Object.values(this.edges);
}
/**
* @param {GraphEdge} edge
* @returns {Graph}
*/
addEdge(edge) {
// Try to find and end start vertices.
let startVertex = this.getVertexByKey(edge.startVertex.getKey());
let endVertex = this.getVertexByKey(edge.endVertex.getKey());
// Insert start vertex if it wasn't inserted.
if (!startVertex) {
this.addVertex(edge.startVertex);
startVertex = this.getVertexByKey(edge.startVertex.getKey());
}
// Insert end vertex if it wasn't inserted.
if (!endVertex) {
this.addVertex(edge.endVertex);
endVertex = this.getVertexByKey(edge.endVertex.getKey());
}
// Check if edge has been already added.
if (this.edges[edge.getKey()]) {
throw new Error('Edge has already been added before');
} else {
this.edges[edge.getKey()] = edge;
}
// Add edge to the vertices.
if (this.isDirected) {
// If graph IS directed then add the edge only to start vertex.
startVertex.addEdge(edge);
} else {
// If graph ISN'T directed then add the edge to both vertices.
startVertex.addEdge(edge);
endVertex.addEdge(edge);
}
return this;
}
/**
* @param {GraphEdge} edge
*/
deleteEdge(edge) {
// Delete edge from the list of edges.
if (this.edges[edge.getKey()]) {
delete this.edges[edge.getKey()];
} else {
throw new Error('Edge not found in graph');
}
// Try to find and end start vertices and delete edge from them.
const startVertex = this.getVertexByKey(edge.startVertex.getKey());
const endVertex = this.getVertexByKey(edge.endVertex.getKey());
startVertex.deleteEdge(edge);
endVertex.deleteEdge(edge);
}
/**
* @param {GraphVertex} startVertex
* @param {GraphVertex} endVertex
* @return {(GraphEdge|null)}
*/
findEdge(startVertex, endVertex) {
const vertex = this.getVertexByKey(startVertex.getKey());
if (!vertex) {
return null;
}
return vertex.findEdge(endVertex);
}
/**
* @return {number}
*/
getWeight() {
return this.getAllEdges().reduce((weight, graphEdge) => {
return weight + graphEdge.weight;
}, 0);
}
/**
* Reverse all the edges in directed graph.
* @return {Graph}
*/
reverse() {
/** @param {GraphEdge} edge */
this.getAllEdges().forEach((edge) => {
// Delete straight edge from graph and from vertices.
this.deleteEdge(edge);
// Reverse the edge.
edge.reverse();
// Add reversed edge back to the graph and its vertices.
this.addEdge(edge);
});
return this;
}
/**
* @return {object}
*/
getVerticesIndices() {
const verticesIndices = {};
this.getAllVertices().forEach((vertex, index) => {
verticesIndices[vertex.getKey()] = index;
});
return verticesIndices;
}
/**
* @return {*[][]}
*/
getAdjacencyMatrix() {
const vertices = this.getAllVertices();
const verticesIndices = this.getVerticesIndices();
// Init matrix with infinities meaning that there is no ways of
// getting from one vertex to another yet.
const adjacencyMatrix = Array(vertices.length).fill(null).map(() => {
return Array(vertices.length).fill(Infinity);
});
// Fill the columns.
vertices.forEach((vertex, vertexIndex) => {
vertex.getNeighbors().forEach((neighbor) => {
const neighborIndex = verticesIndices[neighbor.getKey()];
adjacencyMatrix[vertexIndex][neighborIndex] = this.findEdge(vertex, neighbor).weight;
});
});
return adjacencyMatrix;
}
/**
* @return {string}
*/
toString() {
return Object.keys(this.vertices).toString();
}
}
/**
* @param {number[][]} adjacencyMatrix
* @param {object} verticesIndices
* @param {GraphVertex[]} cycle
* @param {GraphVertex} vertexCandidate
* @return {boolean}
*/
function isSafe(adjacencyMatrix, verticesIndices, cycle, vertexCandidate) {
const endVertex = cycle[cycle.length - 1];
// Get end and candidate vertices indices in adjacency matrix.
const candidateVertexAdjacencyIndex = verticesIndices[vertexCandidate.getKey()];
const endVertexAdjacencyIndex = verticesIndices[endVertex.getKey()];
// Check if last vertex in the path and candidate vertex are adjacent.
if (adjacencyMatrix[endVertexAdjacencyIndex][candidateVertexAdjacencyIndex] === Infinity) {
return false;
}
// Check if vertexCandidate is being added to the path for the first time.
const candidateDuplicate = cycle.find((vertex) => vertex.getKey() === vertexCandidate.getKey());
return !candidateDuplicate;
}
/**
* @param {number[][]} adjacencyMatrix
* @param {object} verticesIndices
* @param {GraphVertex[]} cycle
* @return {boolean}
*/
function isCycle(adjacencyMatrix, verticesIndices, cycle) {
// Check if first and last vertices in hamiltonian path are adjacent.
// Get start and end vertices from the path.
const startVertex = cycle[0];
const endVertex = cycle[cycle.length - 1];
// Get start/end vertices indices in adjacency matrix.
const startVertexAdjacencyIndex = verticesIndices[startVertex.getKey()];
const endVertexAdjacencyIndex = verticesIndices[endVertex.getKey()];
// Check if we can go from end vertex to the start one.
return adjacencyMatrix[endVertexAdjacencyIndex][startVertexAdjacencyIndex] !== Infinity;
}
/**
* @param {number[][]} adjacencyMatrix
* @param {GraphVertex[]} vertices
* @param {object} verticesIndices
* @param {GraphVertex[][]} cycles
* @param {GraphVertex[]} cycle
*/
function hamiltonianCycleRecursive({
adjacencyMatrix,
vertices,
verticesIndices,
cycles,
cycle,
}) {
// Clone cycle in order to prevent it from modification by other DFS branches.
const currentCycle = [...cycle].map((vertex) => new GraphVertex(vertex.value));
if (vertices.length === currentCycle.length) {
// Hamiltonian path is found.
// Now we need to check if it is cycle or not.
if (isCycle(adjacencyMatrix, verticesIndices, currentCycle)) {
// Another solution has been found. Save it.
cycles.push(currentCycle);
}
return;
}
for (let vertexIndex = 0; vertexIndex < vertices.length; vertexIndex += 1) {
// Get vertex candidate that we will try to put into next path step and see if it fits.
const vertexCandidate = vertices[vertexIndex];
// Check if it is safe to put vertex candidate to cycle.
if (isSafe(adjacencyMatrix, verticesIndices, currentCycle, vertexCandidate)) {
// Add candidate vertex to cycle path.
currentCycle.push(vertexCandidate);
// Try to find other vertices in cycle.
hamiltonianCycleRecursive({
adjacencyMatrix,
vertices,
verticesIndices,
cycles,
cycle: currentCycle,
});
// BACKTRACKING.
// Remove candidate vertex from cycle path in order to try another one.
currentCycle.pop();
}
}
}
/**
* @param {Graph} graph
* @return {GraphVertex[][]}
*/
function hamiltonianCycle(graph) {
// Gather some information about the graph that we will need to during
// the problem solving.
const verticesIndices = graph.getVerticesIndices();
const adjacencyMatrix = graph.getAdjacencyMatrix();
const vertices = graph.getAllVertices();
// Define start vertex. We will always pick the first one
// this it doesn't matter which vertex to pick in a cycle.
// Every vertex is in a cycle so we can start from any of them.
const startVertex = vertices[0];
// Init cycles array that will hold all solutions.
const cycles = [];
// Init cycle array that will hold current cycle path.
const cycle = [startVertex];
// Try to find cycles recursively in Depth First Search order.
hamiltonianCycleRecursive({
adjacencyMatrix,
vertices,
verticesIndices,
cycles,
cycle,
});
// Return found cycles.
return cycles;
}
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[]}
*/
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;
}

@ -80,7 +80,8 @@ class WaypointsData {
// Точки для карты
const waypoints = new WaypointsData();
const markerUrl = './img/map-marker.png';
// Максимальное число путей для перебора
const max_ways_count = 200000;
let regionPointsWidget;
let selectedPointsWidget;
@ -259,11 +260,17 @@ function calculateDistance(lat1, lon1, lat2, lon2) {
return d;
}
function shuffle(array) {
function shuffle(array, fix_start_point = true, fix_end_point = true) {
let currentIndex = array.length;
if (fix_end_point) {
currentIndex = array.length - 1;
}
let start_index = 0;
if (fix_start_point) {
start_index = 1;
}
// While there remain elements to shuffle...
while (currentIndex != 0) {
while (currentIndex != start_index) {
// Pick a remaining element...
let randomIndex = Math.floor(Math.random() * currentIndex);
@ -286,8 +293,41 @@ function factorial(n) {
}
function stochasticPathFind(array) {
function stochasticPathFind(array, fix_start_point = true, fix_end_point = true) {
let variants_count = array.length;
if (fix_start_point) {
variants_count--;
}
if (fix_end_point) {
variants_count--;
}
let max_ways = factorial(array.length);
if (max_ways > max_ways_count) {
max_ways = max_ways_count;
}
var min_distance = Number.MAX_VALUE;
var min_distance_waypoint = [];
for (let i = 0; i < max_ways; i++) {
shuffle(array, fix_start_point, fix_end_point);
let prevPoint = null;
let distance = 0;
for (let ind in array) {
let point = array[ind];
if (prevPoint != null) {
const d = calculateDistance(point.location[0], point.location[1], prevPoint.location[0], prevPoint.location[1]) / 1000.0;
distance += d;
}
prevPoint = point;
}
if (distance < min_distance) {
min_distance = distance;
min_distance_waypoint = [];
for (let ind in array) {
min_distance_waypoint.push(array[ind].id);
}
}
}
return min_distance_waypoint;
}
// Вычисление оптимального маршрута по начальной и конечной точке
@ -310,36 +350,10 @@ function recalculateWaypoints(waypoints) {
// Три точки, старт, стоп и промежуточная выводим без расчета
showTravelPoints([waypoints[0].id, waypoints[1].id, waypoints[2].id]);
} else if (waypoints.length <= 9) {
// Если точек от 4 до 9, используем алгоритм коммивояжера
// Создаем словарь с вершинами
let vertexes = {};
for (const ind in waypoints) {
vertexes[ind] = new GraphVertex(ind);
}
// Формируем граф
const graph = new Graph();
const len = waypoints.length;
for (let i = 0; i < len - 1; i++) {
for (let j = i + 1; j < len; j++) {
const point1 = waypoints[i];
const point2 = waypoints[j];
const d = calculateDistance(point1.location[0], point1.location[1], point2.location[0], point2.location[1]);
graph.addEdge(new GraphEdge(vertexes[i], vertexes[j], d));
}
}
// Решение задачи коммивояжера
const salesmanPath = bfTravellingSalesman(graph);
let indexes = [];
for (var ind in salesmanPath) {
indexes.push(waypoints[salesmanPath[ind].value].id);
}
showTravelPoints(indexes);
} else {
// Если точек от 9, близкие к np алгоритмам расчеты слишком затратные
// применяем лучшие поиск из 200.000 лучших вариантов (9! = 362880, берем такого порядка число чтобы не грузить браузер расчетами)
let path_indexes = stochasticPathFind(waypoints, true, true);
showTravelPoints(path_indexes);
}
}
}

Loading…
Cancel
Save

Powered by TurnKey Linux.