3.7. Спецификация City.Cs
Назначение:
1. Хранение идентификатора и координат города
2. Вычисление расстояния между городами
Свойства:
1. int Id { get; } — идентификатор города
2. double X { get; } — координата X
3. double Y { get; } — координата Y
Конструкторы:
1. City(int id, double x, double y) — инициализация города с указанными координатами
Методы:
1. static double Distance(City a, City b) — вычисление евклидова расстояния между двумя городами
4. Исходный код программы
4.1. Код Program.cs
class Program
{
static void Main(string[] args)
{
int exp = int.Parse(Console.ReadLine());
int n = 1000;
List<City> cities;
if (exp == 1)
{
n = int.Parse(Console.ReadLine());
cities = GenerateRandomCities(n);
}
else if (exp == 2)
{
cities = Enumerable.Range(0, n)
.Select(i => new City(i, i * 10.0, 0.0))
.ToList();
}
else return;
int populationSize = 20;
double mutationProb = 0.0525, crossoverProb = 0.1;
int maxGenerations = 100, runs = 10;
var distances = new List<double>();
var times = new List<double>();
for (int i = 0; i < runs; i++)
{
var sw = Stopwatch.StartNew();
var mgr = new EvolutionManager(cities, populationSize, maxGenerations, crossoverProb, mutationProb);
mgr.Run();
sw.Stop();
distances.Add(mgr.BestIndividual.FitnessValue);
times.Add(sw.Elapsed.TotalSeconds);
}
Console.WriteLine($"{distances.Min():F2} | {times.Average():F2}с [{times.Min():F2}-{times.Max():F2}]");
}
static List<City> GenerateRandomCities(int n)
{
var rnd = new Random();
var list = new List<City>();
for (int i = 0; i < n; i++)
list.Add(new City(i, rnd.NextDouble() * 1000, rnd.NextDouble() * 1000));
return list;
}
}
4.2. Код Population.cs
public class Population
{
public List<Individual> Individuals { get; private set; }
private List<City> cities;
private Random rnd;
public Population(List<City> cities, int size, Random rnd)
{
this.cities = cities;
this.rnd = rnd;
Individuals = Enumerable.Range(0, size)
.Select(_ => new Individual(cities, rnd))
.ToList();
}
(Individual, Individual) SelectPair(double p)
{
Individual Tournament()
{
var top = Individuals.OrderBy(_ => rnd.Next()).Take(3)
.OrderBy(ind => ind.FitnessValue).ToList();
return rnd.NextDouble() < p ? top[0] : top[1];
}
var p1 = Tournament();
var p2 = Tournament();
while (p1 == p2 && Individuals.Count > 1) p2 = Tournament();
return (p1, p2);
}
public void CreateNewGeneration(double crossProb, double mutProb)
{
var next = new List<Individual>();
while (next.Count < Individuals.Count)
{
var (a, b) = SelectPair(0.9);
Individual c1, c2;
if (rnd.NextDouble() < crossProb)
(c1, c2) = Individual.Crossover(a, b, rnd);
else
{
c1 = new Individual(cities, a.Tour);
c2 = new Individual(cities, b.Tour);
}
c1.Mutation(mutProb, rnd);
c2.Mutation(mutProb, rnd);
next.Add(c1);
if (next.Count < Individuals.Count) next.Add(c2);
}
Individuals = next;
}
}
4.3. Код Individual.cs
public class Individual
{
public List<int> Tour { get; private set; }
public double FitnessValue { get; private set; }
private List<City> cities;
public Individual(List<City> cities, Random rnd)
{
this.cities = cities;
Tour = Enumerable.Range(0, cities.Count).OrderBy(_ => rnd.Next()).ToList();
Evaluate();
}
public Individual(List<City> cities, List<int> tour)
{
this.cities = cities;
Tour = new List<int>(tour);
Evaluate();
}
void Evaluate()
{
FitnessValue = Enumerable.Range(0, Tour.Count)
.Sum(i =>
{
var a = cities[Tour[i]];
var b = cities[Tour[(i + 1) % Tour.Count]];
return City.Distance(a, b);
});
}
public void Mutation(double p, Random rnd)
{
if (rnd.NextDouble() < p)
{
int i = rnd.Next(Tour.Count), j = rnd.Next(Tour.Count);
(Tour[i], Tour[j]) = (Tour[j], Tour[i]);
Evaluate();
}
}
public static (Individual, Individual) Crossover(Individual p1, Individual p2, Random rnd)
{
int size = p1.Tour.Count;
int s = rnd.Next(size), e = rnd.Next(s, size);
var c1 = new int[size];
var c2 = new int[size];
for (int i = s; i <= e; i++)
{
c1[i] = p1.Tour[i];
c2[i] = p2.Tour[i];
}
Fill(c1, p2.Tour, e);
Fill(c2, p1.Tour, e);
return (new Individual(p1.cities, c1.ToList()),
new Individual(p2.cities, c2.ToList()));
}
static void Fill(int[] child, List<int> donor, int end)
{
int idx = (end + 1) % donor.Count;
foreach (var g in donor)
if (!child.Contains(g))
child[idx = idx % donor.Count] = g;
idx++;
}
}
4.4. Код Gen.cs
public class Gen
{
public double Value { get; private set; }
public Gen(double value) => Value = value;
public void Mutation(double xmin, double xmax, double p, Random rnd)
{
if (rnd.NextDouble() < p)
{
double d = (xmax - xmin) / 5;
double delta = xmin / 5 + rnd.NextDouble() * d;
double v = Math.Clamp(Value + delta, xmin, xmax);
Value = v;
}
}
}
4.5. Код EvolutionManager.cs
public class EvolutionManager
{
private List<City> cities;
private int populationSize, maxGenerations;
private double crossoverProbability, mutationProbability;
private Random rnd;
public Individual BestIndividual { get; private set; }
public EvolutionManager(List<City> cities, int popSize, int maxGens, double crossProb, double mutProb)
{
this.cities = cities;
populationSize = popSize;
maxGenerations = maxGens;
crossoverProbability = crossProb;
mutationProbability = mutProb;
rnd = new Random();
}
public void Run()
{
var pop = new Population(cities, populationSize, rnd);
BestIndividual = pop.Individuals.MinBy(ind => ind.FitnessValue);
for (int gen = 0; gen < maxGenerations; gen++)
{
pop.CreateNewGeneration(crossoverProbability, mutationProbability);
var currentBest = pop.Individuals.MinBy(ind => ind.FitnessValue);
if (currentBest.FitnessValue < BestIndividual.FitnessValue)
BestIndividual = currentBest;
}
}
}
4.6. Код Chromosome.cs
public class Chromosome
{
public List<Gen> Gens { get; private set; }
public double XMin { get; private set; }
public double XMax { get; private set; }
public int Dimension => Gens.Count;
public Chromosome(List<double> values, double xmin, double xmax)
{
XMin = xmin;
XMax = xmax;
Gens = values.Select(v => new Gen(Math.Clamp(v, xmin, xmax))).ToList();
}
public Chromosome(List<Gen> gens, double xmin, double xmax)
{
XMin = xmin;
XMax = xmax;
Gens = new List<Gen>(gens);
}
public static (Chromosome, Chromosome) BLXabCrossover(
Chromosome p1, Chromosome p2, double a, double b, double xmin, double xmax, Random rnd)
{
int n = p1.Dimension;
var c1 = new List<double>(new double[n]);
var c2 = new List<double>(new double[n]);
for (int i = 0; i < n; i++)
{
double x = p1.Gens[i].Value, y = p2.Gens[i].Value;
double d = Math.Abs(x - y);
double low = Math.Min(x, y) - a * d, up = Math.Max(x, y) + b * d;
c1[i] = Math.Clamp(low + rnd.NextDouble() * (up - low), xmin, xmax);
c2[i] = Math.Clamp(low + rnd.NextDouble() * (up - low), xmin, xmax);
}
return (new Chromosome(c1, xmin, xmax), new Chromosome(c2, xmin, xmax));
}
public void Mutation(double p, Random rnd)
{
Gens[rnd.Next(Dimension)].Mutation(XMin, XMax, p, rnd);
}
}
4.7. Код City.cs
public class City
{
public int Id { get; }
public double X { get; }
public double Y { get; }
public City(int id, double x, double y)
{
Id = id; X = x; Y = y;
}
public static double Distance(City a, City b)
{
double dx = a.X - b.X, dy = a.Y - b.Y;
return Math.Sqrt(dx * dx + dy * dy);
}
}
5. Результаты выполнения программы
5.1. Выбор эксперимента
Рис.3 — Окно выбора эксперимента
5.2. Выбор количества городов
Рис.4 — Окно выбора количества городов
5.3. Вывод координат городов
Рис.5 — Вывод сгенерированных координат городов
5.4. Вывод расстояния между городами
Рис.6 — Вывод рассчитанных расстояний между городами
5.5. Вывод работы генетического алгоритма
Рис.7 — Вывод разных путей кратчайших расстояний между городами при помощи ГА
5.6. Вывод статистики
Рис.8 — Вывод статистики и найденный наикратчайший путь
Заключение
Разработанный генетический алгоритм показал эффективное и надёжное решение задачи коммивояжёра как для случайного, так и для упрощённого (линейного) расположения городов. Экспериментальные результаты подтвердили его применимость к задачам разного масштаба (от десятков до тысячи точек) и дали понимание влияния структуры входных данных на скорость и качество поиска. В дальнейшем можно расширить алгоритм, подобрать лучшие настройки параметров популяции и операторов.
