ACO.cs 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. using Microsoft.VisualBasic;
  2. namespace AntColony.Algorithm;
  3. /// <summary>
  4. /// Алгоритм построения кратчайшего пути с помощью симуляции муравьиной колонии
  5. /// </summary>
  6. public class AntColonyOptimizer
  7. {
  8. private readonly int numberOfCities;
  9. private readonly double[][] distances;
  10. private readonly double[][] pheromones;
  11. private readonly double alpha; // Важность феромонов
  12. private readonly double beta; // Важность расстояния
  13. private readonly double evaporationRate;
  14. private readonly double Q; // Множитель депозита феромонов
  15. private readonly int numberOfAnts;
  16. /// <summary>
  17. /// Инициализировать алгоритм
  18. /// </summary>
  19. /// <param name="distances">Матрица расстояний</param>
  20. /// <param name="numberOfAnts">Количество муравьев</param>
  21. /// <param name="alpha">Значение α (определяет вес феромонов)</param>
  22. /// <param name="beta">Значение β (определяет вес расстояния)</param>
  23. /// <param name="evaporationRate">Скорость испарения</param>
  24. /// <param name="Q">Значение Q</param>
  25. public AntColonyOptimizer(double[][] distances, int numberOfAnts = 20, double alpha = 1, double beta = 2,
  26. double evaporationRate = 0.1, double Q = 100)
  27. {
  28. numberOfCities = distances.GetLength(0);
  29. this.distances = distances;
  30. this.numberOfAnts = numberOfAnts;
  31. this.alpha = alpha;
  32. this.beta = beta;
  33. this.evaporationRate = evaporationRate;
  34. this.Q = Q;
  35. // Строим стартовую матрицу феромонов
  36. pheromones = new double[numberOfCities][];
  37. for (int i = 0; i < numberOfCities; i++)
  38. {
  39. var r = new double[numberOfCities];
  40. for (int j = 0; j < numberOfCities; j++)
  41. {
  42. if (i != j)
  43. r[j] = 1.0; // Начальное значение
  44. }
  45. pheromones[i] = r;
  46. }
  47. }
  48. /// <summary>
  49. /// Решить задачу комивояжера (построить Гамильтонов граф)
  50. /// </summary>
  51. /// <param name="maxIterations">Максимальное число итераций</param>
  52. /// <returns>Лучший путь и его расстояние</returns>
  53. public (List<int> bestTour, double bestDistance) Solve(int maxIterations)
  54. {
  55. List<int> bestTourEver = [];
  56. double bestDistanceEver = double.MaxValue;
  57. Lock distanceLock = new();
  58. Lock pheromoneLock = new();
  59. // for (int iteration = 0; iteration < maxIterations; iteration++)
  60. Parallel.For(0, maxIterations, i =>
  61. {
  62. List<List<int>> antTours = [];
  63. List<double> antDistances = [];
  64. // Каждый муравей строит путь
  65. for (int ant = 0; ant < numberOfAnts; ant++)
  66. {
  67. var (tour, distance) = ConstructTour();
  68. antTours.Add(tour);
  69. antDistances.Add(distance);
  70. // Если текущий путь лучше - обновляем
  71. lock (distanceLock)
  72. {
  73. if (distance < bestDistanceEver)
  74. {
  75. bestDistanceEver = distance;
  76. bestTourEver = [.. tour];
  77. }
  78. }
  79. }
  80. // Обновляем феромоны для всех путей
  81. lock (pheromoneLock)
  82. {
  83. UpdatePheromones(antTours, antDistances);
  84. }
  85. // File.AppendAllText("./iteration-results", bestDistanceEver.ToString() + '\n');
  86. // }
  87. });
  88. return (bestTourEver, bestDistanceEver);
  89. }
  90. private (List<int> tour, double distance) ConstructTour()
  91. {
  92. List<int> tour = [];
  93. var visited = new bool[numberOfCities];
  94. var currentCity = Random.Shared.Next(numberOfCities);
  95. tour.Add(currentCity);
  96. visited[currentCity] = true;
  97. // Строим путь
  98. while (tour.Count < numberOfCities)
  99. {
  100. int nextCity = SelectNextCity(currentCity, visited);
  101. tour.Add(nextCity);
  102. visited[nextCity] = true;
  103. currentCity = nextCity;
  104. }
  105. // Добавляем возврат на начальную точку
  106. tour.Add(tour[0]);
  107. return (tour, CalculateTourDistance(tour));
  108. }
  109. /// <summary>
  110. /// Выбрать следующий город
  111. /// </summary>
  112. /// <param name="currentCity">В каком городе муравей находится сейчас</param>
  113. /// <param name="visited">Какие города муравей уже посетил</param>
  114. /// <returns>Номер следующего города</returns>
  115. private int SelectNextCity(int currentCity, bool[] visited)
  116. {
  117. var probabilities = new double[numberOfCities];
  118. double probabilitiesSum = 0;
  119. // Вероятности для каждого непосещенного города
  120. for (int i = 0; i < numberOfCities; i++)
  121. {
  122. if (!visited[i])
  123. {
  124. if (distances[currentCity][i] == 0) continue;
  125. probabilities[i] = Math.Pow(pheromones[currentCity][i], alpha) *
  126. Math.Pow(1.0 / distances[currentCity][i], beta);
  127. probabilitiesSum += probabilities[i];
  128. }
  129. }
  130. // Случайно выбираем следующий город (по вероятностям)
  131. double randomValue = Random.Shared.NextDouble() * probabilitiesSum;
  132. double sum = 0;
  133. for (int i = 0; i < numberOfCities; i++)
  134. {
  135. if (!visited[i])
  136. {
  137. sum += probabilities[i];
  138. if (sum >= randomValue)
  139. return i;
  140. }
  141. }
  142. // Иначе возвращаемся к первому непосещенному городу
  143. return Array.FindIndex(visited, v => !v);
  144. }
  145. /// <summary>
  146. /// Обновить матрицу феромонов
  147. /// </summary>
  148. /// <param name="antTours">Пути муравьев</param>
  149. /// <param name="antDistances">Их расстояния</param>
  150. private void UpdatePheromones(List<List<int>> antTours, List<double> antDistances)
  151. {
  152. // Испарение
  153. for (int i = 0; i < numberOfCities; i++)
  154. {
  155. for (int j = 0; j < numberOfCities; j++)
  156. {
  157. if (i != j)
  158. pheromones[i][j] *= 1.0 - evaporationRate;
  159. }
  160. }
  161. // Добавляем новые феромоны
  162. for (int ant = 0; ant < antTours.Count; ant++)
  163. {
  164. double pheromoneDeposit = Q / antDistances[ant];
  165. var tour = antTours[ant];
  166. for (int i = 0; i < tour.Count - 1; i++)
  167. {
  168. int city1 = tour[i];
  169. int city2 = tour[i + 1];
  170. pheromones[city1][city2] += pheromoneDeposit;
  171. pheromones[city2][city1] += pheromoneDeposit; // Граф ненаправленный
  172. }
  173. }
  174. }
  175. /// <summary>
  176. /// Рассчитать длину пути
  177. /// </summary>
  178. /// <param name="tour">Путь</param>
  179. /// <returns>Длина пути</returns>
  180. private double CalculateTourDistance(List<int> tour)
  181. {
  182. double distance = 0;
  183. for (int i = 0; i < tour.Count - 1; i++)
  184. {
  185. distance += distances[tour[i]][tour[i + 1]];
  186. }
  187. return distance;
  188. }
  189. }