|
@@ -0,0 +1,213 @@
|
|
|
+using Microsoft.VisualBasic;
|
|
|
+
|
|
|
+namespace AntColony.Algorithm;
|
|
|
+
|
|
|
+/// <summary>
|
|
|
+/// Алгоритм построения кратчайшего пути с помощью симуляции муравьиной колонии
|
|
|
+/// </summary>
|
|
|
+public class AntColonyOptimizer
|
|
|
+{
|
|
|
+ private readonly int numberOfCities;
|
|
|
+ private readonly double[][] distances;
|
|
|
+ private readonly double[][] pheromones;
|
|
|
+ private readonly double alpha; // Важность феромонов
|
|
|
+ private readonly double beta; // Важность расстояния
|
|
|
+ private readonly double evaporationRate;
|
|
|
+ private readonly double Q; // Множитель депозита феромонов
|
|
|
+ private readonly int numberOfAnts;
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Инициализировать алгоритм
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="distances">Матрица расстояний</param>
|
|
|
+ /// <param name="numberOfAnts">Количество муравьев</param>
|
|
|
+ /// <param name="alpha">Значение α (определяет вес феромонов)</param>
|
|
|
+ /// <param name="beta">Значение β (определяет вес расстояния)</param>
|
|
|
+ /// <param name="evaporationRate">Скорость испарения</param>
|
|
|
+ /// <param name="Q">Значение Q</param>
|
|
|
+ public AntColonyOptimizer(double[][] distances, int numberOfAnts = 20, double alpha = 1, double beta = 2,
|
|
|
+ double evaporationRate = 0.1, double Q = 100)
|
|
|
+ {
|
|
|
+ numberOfCities = distances.GetLength(0);
|
|
|
+ this.distances = distances;
|
|
|
+ this.numberOfAnts = numberOfAnts;
|
|
|
+ this.alpha = alpha;
|
|
|
+ this.beta = beta;
|
|
|
+ this.evaporationRate = evaporationRate;
|
|
|
+ this.Q = Q;
|
|
|
+
|
|
|
+ // Строим стартовую матрицу феромонов
|
|
|
+ pheromones = new double[numberOfCities][];
|
|
|
+ for (int i = 0; i < numberOfCities; i++)
|
|
|
+ {
|
|
|
+ var r = new double[numberOfCities];
|
|
|
+ for (int j = 0; j < numberOfCities; j++)
|
|
|
+ {
|
|
|
+ if (i != j)
|
|
|
+ r[j] = 1.0; // Начальное значение
|
|
|
+ }
|
|
|
+ pheromones[i] = r;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Решить задачу комивояжера (построить Гамильтонов граф)
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="maxIterations">Максимальное число итераций</param>
|
|
|
+ /// <returns>Лучший путь и его расстояние</returns>
|
|
|
+ public (List<int> bestTour, double bestDistance) Solve(int maxIterations)
|
|
|
+ {
|
|
|
+ List<int> bestTourEver = [];
|
|
|
+ double bestDistanceEver = double.MaxValue;
|
|
|
+ Lock distanceLock = new();
|
|
|
+ Lock pheromoneLock = new();
|
|
|
+
|
|
|
+ // for (int iteration = 0; iteration < maxIterations; iteration++)
|
|
|
+ Parallel.For(0, maxIterations, i =>
|
|
|
+ {
|
|
|
+ List<List<int>> antTours = [];
|
|
|
+ List<double> antDistances = [];
|
|
|
+
|
|
|
+ // Каждый муравей строит путь
|
|
|
+ for (int ant = 0; ant < numberOfAnts; ant++)
|
|
|
+ {
|
|
|
+ var (tour, distance) = ConstructTour();
|
|
|
+ antTours.Add(tour);
|
|
|
+ antDistances.Add(distance);
|
|
|
+
|
|
|
+ // Если текущий путь лучше - обновляем
|
|
|
+ lock (distanceLock)
|
|
|
+ {
|
|
|
+ if (distance < bestDistanceEver)
|
|
|
+ {
|
|
|
+ bestDistanceEver = distance;
|
|
|
+ bestTourEver = [.. tour];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Обновляем феромоны для всех путей
|
|
|
+ lock (pheromoneLock)
|
|
|
+ {
|
|
|
+ UpdatePheromones(antTours, antDistances);
|
|
|
+ }
|
|
|
+ // File.AppendAllText("./iteration-results", bestDistanceEver.ToString() + '\n');
|
|
|
+ // }
|
|
|
+ });
|
|
|
+
|
|
|
+ return (bestTourEver, bestDistanceEver);
|
|
|
+ }
|
|
|
+
|
|
|
+ private (List<int> tour, double distance) ConstructTour()
|
|
|
+ {
|
|
|
+ List<int> tour = [];
|
|
|
+ var visited = new bool[numberOfCities];
|
|
|
+ var currentCity = Random.Shared.Next(numberOfCities);
|
|
|
+
|
|
|
+ tour.Add(currentCity);
|
|
|
+ visited[currentCity] = true;
|
|
|
+
|
|
|
+ // Строим путь
|
|
|
+ while (tour.Count < numberOfCities)
|
|
|
+ {
|
|
|
+ int nextCity = SelectNextCity(currentCity, visited);
|
|
|
+ tour.Add(nextCity);
|
|
|
+ visited[nextCity] = true;
|
|
|
+ currentCity = nextCity;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Добавляем возврат на начальную точку
|
|
|
+ tour.Add(tour[0]);
|
|
|
+
|
|
|
+ return (tour, CalculateTourDistance(tour));
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Выбрать следующий город
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="currentCity">В каком городе муравей находится сейчас</param>
|
|
|
+ /// <param name="visited">Какие города муравей уже посетил</param>
|
|
|
+ /// <returns>Номер следующего города</returns>
|
|
|
+ private int SelectNextCity(int currentCity, bool[] visited)
|
|
|
+ {
|
|
|
+ var probabilities = new double[numberOfCities];
|
|
|
+ double probabilitiesSum = 0;
|
|
|
+
|
|
|
+ // Вероятности для каждого непосещенного города
|
|
|
+ for (int i = 0; i < numberOfCities; i++)
|
|
|
+ {
|
|
|
+ if (!visited[i])
|
|
|
+ {
|
|
|
+ if (distances[currentCity][i] == 0) continue;
|
|
|
+ probabilities[i] = Math.Pow(pheromones[currentCity][i], alpha) *
|
|
|
+ Math.Pow(1.0 / distances[currentCity][i], beta);
|
|
|
+ probabilitiesSum += probabilities[i];
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Случайно выбираем следующий город (по вероятностям)
|
|
|
+ double randomValue = Random.Shared.NextDouble() * probabilitiesSum;
|
|
|
+ double sum = 0;
|
|
|
+
|
|
|
+ for (int i = 0; i < numberOfCities; i++)
|
|
|
+ {
|
|
|
+ if (!visited[i])
|
|
|
+ {
|
|
|
+ sum += probabilities[i];
|
|
|
+ if (sum >= randomValue)
|
|
|
+ return i;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Иначе возвращаемся к первому непосещенному городу
|
|
|
+ return Array.FindIndex(visited, v => !v);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Обновить матрицу феромонов
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="antTours">Пути муравьев</param>
|
|
|
+ /// <param name="antDistances">Их расстояния</param>
|
|
|
+ private void UpdatePheromones(List<List<int>> antTours, List<double> antDistances)
|
|
|
+ {
|
|
|
+ // Испарение
|
|
|
+ for (int i = 0; i < numberOfCities; i++)
|
|
|
+ {
|
|
|
+ for (int j = 0; j < numberOfCities; j++)
|
|
|
+ {
|
|
|
+ if (i != j)
|
|
|
+ pheromones[i][j] *= 1.0 - evaporationRate;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Добавляем новые феромоны
|
|
|
+ for (int ant = 0; ant < antTours.Count; ant++)
|
|
|
+ {
|
|
|
+ double pheromoneDeposit = Q / antDistances[ant];
|
|
|
+ var tour = antTours[ant];
|
|
|
+
|
|
|
+ for (int i = 0; i < tour.Count - 1; i++)
|
|
|
+ {
|
|
|
+ int city1 = tour[i];
|
|
|
+ int city2 = tour[i + 1];
|
|
|
+ pheromones[city1][city2] += pheromoneDeposit;
|
|
|
+ pheromones[city2][city1] += pheromoneDeposit; // Граф ненаправленный
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Рассчитать длину пути
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="tour">Путь</param>
|
|
|
+ /// <returns>Длина пути</returns>
|
|
|
+ private double CalculateTourDistance(List<int> tour)
|
|
|
+ {
|
|
|
+ double distance = 0;
|
|
|
+ for (int i = 0; i < tour.Count - 1; i++)
|
|
|
+ {
|
|
|
+ distance += distances[tour[i]][tour[i + 1]];
|
|
|
+ }
|
|
|
+ return distance;
|
|
|
+ }
|
|
|
+}
|