Скачиваний:
0
Добавлен:
26.10.2025
Размер:
249.42 Кб
Скачать

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 ­— Вывод статистики и найденный наикратчайший путь

Заключение

Разработанный генетический алгоритм показал эффективное и надёжное решение задачи коммивояжёра как для случайного, так и для упрощённого (линейного) расположения городов. Экспериментальные результаты подтвердили его применимость к задачам разного масштаба (от десятков до тысячи точек) и дали понимание влияния структуры входных данных на скорость и качество поиска. В дальнейшем можно расширить алгоритм, подобрать лучшие настройки параметров популяции и операторов.