Задача о торговом представителе - решим?

Задача о торговом представителе: Решение и практическое применение

Задача о торговом представителе - решим?
Cross-USA route optimization / cornell.edu

The article was posted: | 4 min read


Вообще, в учебниках эта задача называется "задача о коммивояжере". Коммивояжер это не совсем торговый представитель, но для более приближенного к нашим реалиям понимания, скажем, что это торговый представитель на авто. Ему нужно посетить несколько городов и вернуться в исходную точку, но есть одна загвоздка: он хочет сделать это, пройдя минимальный путь.

Задача о коммивояжере не является нерешаемой, но она относится к классу сложных комбинаторных задач. Проблема заключается в том, что с увеличением числа городов количество возможных маршрутов стремительно растет, что делает поиск оптимального решения чрезвычайно трудоемким. Это явление называется "комбинаторным взрывом".

В чем сложность задачи?

Для понимания проблемы представьте, что у нас есть 3 города. Коммивояжер может начать путешествие из любого из них, что дает 3 возможных стартовых города. Для каждого стартового города есть 2 варианта для следующего города, и наконец, остается один город для последнего этапа. Это значит, что общее количество маршрутов (перестановок) равно 3! = 3 × 2 × 1 = 6. ! это факториал, тот самый, который учили в школе. Можно открыть приложение калькулятор на телефоне, только переключить его в инженерный режим, где есть больше функций. Клавиша с ! и есть факториал.

Для 4 городов количество возможных маршрутов уже составляет 4! = 24, а для 10 городов — 10! = 3628800. При таком росте числа возможных маршрутов проверка каждого из них становится практически невозможной для больших значений n (число городов).

Решение на языке JavaScript

Чтобы решить эту задачу, мы можем использовать простой и наглядный метод — перебор всех возможных маршрутов. Хотя это не самый быстрый способ, он понятен и помогает лучше разобраться в сути задачи.

Допустим, у нас есть четыре города, расположенных в точках A, B, C и D. Коммивояжер стартует из точки A и хочет посетить все остальные города и вернуться обратно.


const cities = [
  { name: "A", x: 0, y: 0 },
  { name: "B", x: 2, y: 2 },
  { name: "C", x: 4, y: 0 },
  { name: "D", x: 6, y: 3 }
];

// Функция для вычисления расстояния между двумя городами
function distance(city1, city2) {
  return Math.sqrt((city1.x - city2.x) ** 2 + (city1.y - city2.y) ** 2);
}

// Функция для нахождения кратчайшего маршрута
function findShortestPath(cities) {
  let shortestDistance = Infinity;
  let bestRoute = [];

  function permute(route, remainingCities) {
    if (remainingCities.length === 0) {
      // Добавляем путь обратно к началу
      route.push(route[0]);
      let totalDistance = 0;
      for (let i = 0; i < route.length - 1; i++) {
        totalDistance += distance(route[i], route[i + 1]);
      }
      if (totalDistance < shortestDistance) {
        shortestDistance = totalDistance;
        bestRoute = [...route];
      }
      route.pop(); // Убираем возвращение в начало для дальнейших вычислений
    } else {
      for (let i = 0; i < remainingCities.length; i++) {
        permute([...route, remainingCities[i]], remainingCities.filter((_, index) => index !== i));
      }
    }
  }

  permute([], cities);
  return { bestRoute, shortestDistance };
}

// Поиск кратчайшего маршрута
const result = findShortestPath(cities);
console.log("Кратчайший маршрут:", result.bestRoute.map(city => city.name));
console.log("Минимальное расстояние:", result.shortestDistance);

Вы, наверное, можете сказать, что это слишком сложно и что не может быть так много вариантов, если мы имеем так мало городов. Приведу пример. У нас всего два города, Николаев и Одесса, и торговому представителю нужно вернуться обратно в тот город, откуда он выехал. Так вот расстояние туда и обратно практически никогда не будут равны. Например, в одну сторону дорога перекрыта дорожной службой, они ложат асфальт, торговому представителю приходится объезжать. Или он едет по автобану, а съезды с автобанов не очень часты. Можно проехать 30 км и больше до ближайшего съезда. Или мы едем по улице с односторонним движением, но, когда мы будем возвращаться обратно, мы уже не сможем вернуться по этой же улице, а должны выбрать другой маршрут.

Где мне это может понадобиться?

Естественно, в логистике, планировании перевозок, в планировании маршрутов мусоровозок. Интернет, по сути, это тоже точки с маршрутами между ними. Даже токарь станка ЧПУ на заводе "Заря", сам того не зная, сталкивается с этим алгоритмом, потому что резец также двигается между точками на изделии и оптимизация его передвижения позволяет сэкономить на времени работы и износе железа.