Проблема коммивояжера в Java

1. Вступление

В этом руководстве мы узнаем об алгоритме имитации отжига и покажем пример реализации, основанной на задаче коммивояжера (TSP).

2. Имитация отжига

Алгоритм имитации отжига является эвристическим для решения задач с большим пространством поиска.

Вдохновение и название пришли из отжига в металлургии; это метод, который включает нагревание и контролируемое охлаждение материала.

В целом, моделируемый отжиг снижает вероятность принятия худших решений, поскольку он исследует пространство решений и понижает температуру системы. Следующий https://commons.wikimedia.org/wiki/File:Hill Climbing with Simulated Annealing.gif[animation]демонстрирует механизм поиска наилучшего решения с помощью алгоритма имитации отжига:

ссылка:/uploads/Hill Climbing with Simulated Annealing.gif[]

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

Алгоритм имеет несколько параметров для работы:

  • количество итераций - условие остановки для симуляции

  • начальная температура - начальная энергия системы

  • параметр скорости охлаждения - процент, на который мы уменьшаем

температура системы ** минимальная температура - дополнительное условие остановки

  • время симуляции - необязательное условие остановки

Значения этих параметров должны быть тщательно выбраны, поскольку они могут оказать существенное влияние на производительность процесса.

3. Задача коммивояжера

Проблема коммивояжера (TSP) - самая известная проблема оптимизации компьютерных технологий в современном мире.

Проще говоря, это проблема поиска оптимального маршрута между узлами в графе. Общее расстояние перемещения может быть одним из критериев оптимизации. Для получения более подробной информации о TSP посмотрите https://simple.wikipedia.org/wiki/Travelling salesman problem[here].

4. Модель Java

Чтобы решить проблему TSP, нам понадобятся два класса моделей, а именно City и Travel . В первом мы будем хранить координаты узлов на графике:

@Data
public class City {

    private int x;
    private int y;

    public City() {
        this.x = (int) (Math.random() **  500);
        this.y = (int) (Math.random() **  500);
    }

    public double distanceToCity(City city) {
        int x = Math.abs(getX() - city.getX());
        int y = Math.abs(getY() - city.getY());
        return Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
    }

}

Конструктор класса City позволяет нам создавать случайные местоположения городов. Логика distanceToCity (..) отвечает за расчеты расстояния между городами.

Следующий код отвечает за моделирование тура коммивояжера. Начнем с генерации начального порядка городов в путешествиях:

public void generateInitialTravel() {
    if (travel.isEmpty())
        new Travel(10);
    Collections.shuffle(travel);
}

В дополнение к генерации начального заказа нам нужны методы для замены двух случайных городов в порядке перемещения. Мы будем использовать его для поиска лучших решений в алгоритме имитации отжига:

public void swapCities() {
    int a = generateRandomIndex();
    int b = generateRandomIndex();
    previousTravel = travel;
    City x = travel.get(a);
    City y = travel.get(b);
    travel.set(a, y);
    travel.set(b, x);
}

Кроме того, нам нужен метод, чтобы отменить создание свопа на предыдущем шаге, если новое решение не будет принято нашим алгоритмом:

public void revertSwap() {
    travel = previousTravel;
}

Последний метод, который мы хотим охватить, - это расчет общей длины пути, который будет использоваться в качестве критерия оптимизации:

public int getDistance() {
    int distance = 0;
    for (int index = 0; index < travel.size(); index++) {
        City starting = getCity(index);
        City destination;
        if (index + 1 < travel.size()) {
            destination = getCity(index + 1);
        } else {
            destination = getCity(0);
        }
            distance += starting.distanceToCity(destination);
    }
    return distance;
}

Теперь давайте сосредоточимся на основной части, реализации алгоритма имитации отжига.

5. Реализация имитации отжига

В следующей реализации имитации отжига мы собираемся решить проблему TSP. Просто быстрое напоминание, цель состоит в том, чтобы найти кратчайшее расстояние, чтобы путешествовать по всем городам.

Чтобы начать процесс, нам нужно предоставить три основных параметра, а именно startingTempera , numberOfIterations и coolingRate :

public double simulateAnnealing(double startingTemperature,
  int numberOfIterations, double coolingRate) {
    double t = startingTemperature;
    travel.generateInitialTravel();
    double bestDistance = travel.getDistance();

    Travel currentSolution = travel;
   //...
}

Перед началом симуляции мы генерируем начальный (случайный) порядок городов и рассчитываем общее расстояние для путешествия. Поскольку это первое вычисленное расстояние, мы сохраняем его в переменной bestDistance вместе с currentSolution.

На следующем шаге мы запускаем основной цикл моделирования:

for (int i = 0; i < numberOfIterations; i++) {
    if (t > 0.1) {
       //...
    } else {
        continue;
    }
}

Цикл будет длиться указанное нами количество итераций. Кроме того, мы добавили условие, чтобы остановить моделирование, если температура будет ниже или равна 0,1. Это позволит нам сэкономить время моделирования, так как при низких температурах различия в оптимизации практически не видны.

Давайте посмотрим на основную логику алгоритма имитации отжига:

currentSolution.swapCities();
double currentDistance = currentSolution.getDistance();
if (currentDistance < bestDistance) {
    bestDistance = currentDistance;
} else if (Math.exp((bestDistance - currentDistance)/t) < Math.random()) {
    currentSolution.revertSwap();
}

На каждом этапе моделирования мы случайным образом меняем два города в порядке следования.

Кроме того, мы рассчитываем currentDistance . Если рассчитанный currentDistance ниже, чем bestDistance , мы сохраняем его как лучший.

В противном случае мы проверяем, является ли функция Больцмана распределения вероятностей ниже случайно выбранной величины в диапазоне от 0 до 1. Если да, мы отменяем обмен городов. Если нет, мы сохраняем новый порядок городов, так как это может помочь нам избежать локальных минимумов.

Наконец, на каждом этапе моделирования мы снижаем температуру, предоставляя coolingRate:

t ** = coolingRate;

После моделирования мы возвращаем лучшее решение, которое мы нашли с помощью имитации отжига.

  • Обратите внимание на несколько советов о том, как выбрать лучшие параметры симуляции: **

  • для небольших пространств решения лучше уменьшить стартовый

температура и увеличить скорость охлаждения, так как это уменьшит время симуляции, без потери качества ** для больших пространств решения выберите более высокий старт

температура и небольшая скорость охлаждения, так как будет больше локальных минимумов ** всегда предоставляйте достаточно времени для моделирования от высокого до низкого

температура системы

Не забудьте потратить некоторое время на настройку алгоритма с помощью небольшого экземпляра задачи, прежде чем начинать основные симуляции, так как это улучшит конечные результаты. Настройка алгоритма имитации отжига была показана, например, в https://www.researchgate.net/publication/269268529 Simulated Annealing algorithm for optimization of elastic optical networks with unicast and anycast traffic[настоящей статьи].

6. Заключение

В этом быстром уроке мы смогли изучить алгоритм имитации отжига и решили задачу коммивояжера .

Мы надеемся, что это покажет, насколько удобен этот простой алгоритм применительно к определенным типам задач оптимизации.

Полная реализация этой статьи может быть найдена over на GitHub .