Files
AIM-PIbd-31-Yaruskin-S-A/lab_10/laba10.ipynb
2025-05-24 01:47:07 +04:00

476 KiB
Raw Blame History

Лаб 10. Генетический алгоритм. Погнали

In [1]:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import random

# Загружаем данные
df = pd.read_csv('..//static//csv//city.csv')
print(df.columns)
Index(['address', 'postal_code', 'country', 'federal_district', 'region_type',
       'region', 'area_type', 'area', 'city_type', 'city', 'settlement_type',
       'settlement', 'kladr_id', 'fias_id', 'fias_level', 'capital_marker',
       'okato', 'oktmo', 'tax_office', 'timezone', 'geo_lat', 'geo_lon',
       'population', 'foundation_year'],
      dtype='object')
In [2]:
print("Список городов:")
df.head()
Список городов:
Out[2]:
<style scoped=""> .dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; } </style>
address postal_code country federal_district region_type region area_type area city_type city ... fias_level capital_marker okato oktmo tax_office timezone geo_lat geo_lon population foundation_year
0 Респ Адыгея, г Адыгейск 385200.0 Россия Южный Респ Адыгея NaN NaN г Адыгейск ... 4 0 79403000000 79703000001 107 UTC+3 44.878414 39.190289 12689 1969
1 г Майкоп 385000.0 Россия Южный Респ Адыгея NaN NaN г Майкоп ... 4 2 79401000000 79701000001 105 UTC+3 44.609827 40.100661 144055 1857
2 г Горно-Алтайск 649000.0 Россия Сибирский Респ Алтай NaN NaN г Горно-Алтайск ... 4 2 84401000000 84701000 400 UTC+7 51.958103 85.960324 62861 1830
3 Алтайский край, г Алейск 658125.0 Россия Сибирский край Алтайский NaN NaN г Алейск ... 4 0 1403000000 1703000 2201 UTC+7 52.492251 82.779361 28528 1913
4 г Барнаул 656000.0 Россия Сибирский край Алтайский NaN NaN г Барнаул ... 4 2 1401000000 1701000 2200 UTC+7 53.347997 83.779806 635585 1730

5 rows × 24 columns

Генетический алгоритм

Я поставил цель реализовать генетический алгоритм для решения задачи коммивояжёра (TSP) для построения маршрутов между городами по России.

Важные характиристи:

  • Координаты городов (Широта и долгота) для расчёта расстояния
  • Название самих городов через которые будет идти путь
  • Население и год основания, чтобы посещать исторически значимые и крупные города.

Найдем лучшие города, которые стоит посетить

In [3]:
# Преобразуем годы основания и население в числовой тип
df["foundation_year"] = pd.to_numeric(df["foundation_year"], errors="coerce")
df["population"] = pd.to_numeric(df["population"], errors="coerce")

# Задаём нужные округа
target_districts = ["Северо-Западный", "Центральный", "Приволжский", "Южный"]

# Фильтрация по году основания, населению и федеральному округу
historic_large_cities = df[
    (df["foundation_year"] < 1800) &
    (df["population"] > 500_000) &
    (df["federal_district"].isin(target_districts))
].copy()

# Удалим дубликаты по названию города
historic_large_cities = historic_large_cities.drop_duplicates(subset=["region"])

# Сортировка по населению
historic_large_cities = historic_large_cities.sort_values("population", ascending=False)

# Сброс индекса
historic_large_cities = historic_large_cities.reset_index(drop=True)

# Вывод нужных столбцов
selected_columns = ["address", "city", "region", "country", "federal_district", "population", "foundation_year", "geo_lat", "geo_lon"]
print(f"Исторически значимые города из {target_districts} с населением более 500 000:")
display(historic_large_cities[selected_columns])
Исторически значимые города из ['Северо-Западный', 'Центральный', 'Приволжский', 'Южный'] с населением более 500 000:
<style scoped=""> .dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; } </style>
address city region country federal_district population foundation_year geo_lat geo_lon
0 г Москва NaN Москва Россия Центральный 11514330.0 1147.0 55.754047 37.620405
1 г Санкт-Петербург NaN Санкт-Петербург Россия Северо-Западный 4848742.0 1703.0 59.939131 30.315900
2 г Нижний Новгород Нижний Новгород Нижегородская Россия Приволжский 1250615.0 1221.0 56.324063 44.005391
3 г Казань Казань Татарстан Россия Приволжский 1216965.0 1005.0 55.794358 49.111497
4 г Самара Самара Самарская Россия Приволжский 1164900.0 1586.0 53.195031 50.106952
5 г Ростов-на-Дону Ростов-на-Дону Ростовская Россия Южный 1091544.0 1749.0 47.222457 39.718803
6 г Уфа Уфа Башкортостан Россия Приволжский 1062300.0 1574.0 54.734944 55.957847
7 г Волгоград Волгоград Волгоградская Россия Южный 1021244.0 1589.0 48.707004 44.517034
8 г Пермь Пермь Пермский Россия Приволжский 1000679.0 1723.0 58.010258 56.234203
9 г Воронеж Воронеж Воронежская Россия Центральный 889680.0 1586.0 51.659333 39.196923
10 г Саратов Саратов Саратовская Россия Приволжский 836900.0 1590.0 51.530305 45.952935
11 г Краснодар Краснодар Краснодарский Россия Южный 744933.0 1793.0 45.040160 38.975965
12 г Ижевск Ижевск Удмуртская Россия Приволжский 628117.0 1760.0 56.852738 53.211490
13 г Ульяновск Ульяновск Ульяновская Россия Приволжский 613793.0 1648.0 54.307941 48.374849
14 г Ярославль Ярославль Ярославская Россия Центральный 591486.0 1010.0 57.621548 39.897741
15 г Оренбург Оренбург Оренбургская Россия Приволжский 570329.0 1743.0 51.787509 55.101883
16 г Рязань Рязань Рязанская Россия Центральный 525062.0 1095.0 54.625445 39.735861
17 г Астрахань Астрахань Астраханская Россия Южный 520662.0 1558.0 46.365565 48.055924
18 г Пенза Пенза Пензенская Россия Приволжский 519592.0 1663.0 53.175331 45.034863
19 г Липецк Липецк Липецкая Россия Центральный 508124.0 1703.0 52.610249 39.594788
20 г Тула Тула Тульская Россия Центральный 501129.0 1146.0 54.192056 37.615384

Отобразим на графике выборку городов

In [4]:
import matplotlib.pyplot as plt

plt.figure(figsize=(12, 10))

# Построение точек: население делим для масштабирования
plt.scatter(
    historic_large_cities["geo_lon"],
    historic_large_cities["geo_lat"],
    s=historic_large_cities["population"] / 100000,
    alpha=0.6,
    color='royalblue',
    edgecolors='k'
)

# Подписи городов
for _, row in historic_large_cities.iterrows():
    plt.text(
        row["geo_lon"] + 0.3,
        row["geo_lat"] + 0.3,
        f"{row['address']} ({row['region']})",
        fontsize=8,
        ha="left",
        va="center"
    )

# Подписи и оформление
plt.title("Исторически крупные города с населением > 500 000", fontsize=14)
plt.xlabel("Долгота", fontsize=12)
plt.ylabel("Широта", fontsize=12)
plt.grid(True)
plt.tight_layout()
plt.show()
No description has been provided for this image

Создаю граф

In [5]:
import networkx as nx
from geopy.distance import geodesic

# Создаём пустой граф
G = nx.Graph()

# Добавляем узлы
for _, row in historic_large_cities.iterrows():
    G.add_node(
        row["address"],
        region=row["region"],
        country=row["country"],
        population=row["population"],
        year=row["foundation_year"],
        pos=(row["geo_lon"], row["geo_lat"])  # Для визуализации
    )

# Добавляем рёбра между всеми парами городов
cities = historic_large_cities[["address", "geo_lat", "geo_lon"]].values

for i in range(len(cities)):
    for j in range(i + 1, len(cities)):
        city1, lat1, lon1 = cities[i]
        city2, lat2, lon2 = cities[j]
        distance = geodesic((lat1, lon1), (lat2, lon2)).meters
        G.add_edge(city1, city2, weight=distance)

print(f"Граф построен: {G.number_of_nodes()} узлов, {G.number_of_edges()} рёбер")
Граф построен: 21 узлов, 210 рёбер

Теперь необходимо спроектировать хромосомы

Сделаю это используя кросинговер. А также нужно задать фитнес функцию. Которая будет оценивать насколько проложенный путь лучше, чем другой

In [6]:
cities_list = list(G.nodes)
n = len(cities_list)
distance_matrix = np.zeros((n, n))

for i in range(n):
    for j in range(n):
        if i != j:
            try:
                distance_matrix[i, j] = nx.shortest_path_length(
                    G, source=cities_list[i], target=cities_list[j], weight='weight'
                )
            except nx.NetworkXNoPath:
                distance_matrix[i, j] = 1e6  # Штраф
In [7]:
import random
import networkx as nx
from typing import List, Optional


# Хромосома
class Chromosome:
    # Инициализация решения
    def __init__(self, chromosome: List[int], distance_matrix: np.ndarray):
        if len(set(chromosome)) != len(chromosome):
            raise ValueError("Хромосома содержит дубликаты")
        self.chromosome = chromosome
        self.distance_matrix = distance_matrix
        self._total_distance: Optional[float] = None
        self._fitness: Optional[float] = None


    # Проверка хромосомы на соответствие
    def valid_chromosome(self, chromosome: List[int], count_cities: int) -> bool:
        """ Проверка типа данных """
        if not isinstance(chromosome, list) or not all(isinstance(gene, int) for gene in chromosome):
            return False
            
        """ Проверка длины хромосомы """
        if len(chromosome) != count_cities:
            return False
        
        """ Проверка уникальности генов """
        if len(set(chromosome)) != count_cities:
            return False
        
        """ Проверка диапазона значений """
        if any(gene < 0 or gene >= count_cities for gene in chromosome):
            return False
            
        return True
    
    
    # Вычисляем общую длину маршрута
    @property
    def _fitness_total_distance(self) -> float:
        if self._total_distance is None:
            self._total_distance = 0.0
            self._total_distance = sum(
                self.distance_matrix[self.chromosome[i], self.chromosome[(i + 1) % len(self.chromosome)]]
                for i in range(len(self.chromosome))
            )
        return self._total_distance
    
    
    # Решаем и получаем значение fitness-функции
    @property
    def fitness(self) -> float:
        """Вычисляем как обратную величину к общей длине маршрута"""
        if self._fitness is None:
            self._fitness = 1 / self._fitness_total_distance
        return self._fitness
    

    # Эволюция популяции
    @property
    def evaluate(self) -> float:
        return self.fitness
    
    
    # Мутация
    def mutate(self):
        i, j = random.sample(range(len(self.chromosome)), 2)
        self.chromosome[i], self.chromosome[j] = self.chromosome[j], self.chromosome[i]
        self._fitness = None
        self._total_distance = None
    

    # Представление решения в виде строки
    def __self__(self) -> str:
        return f"Маршрут: {self.chromosome} | Длина: {self._total_distance:.2f} км"

Сгенерируем начальную популяцию

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

In [8]:
# Генерация начальной популяции
def generate_initial_population(population_size: int, count_cities: int) -> List[List[int]]:
    """Создает начальную популяцию случайных маршрутов."""
    population = []
    for _ in range(population_size):
        chromosome = list(range(count_cities))
        random.shuffle(chromosome)
        population.append(chromosome)
    return population

Задам кроссинговера

В данной задаче используется упорядоченный кроссовер (OX1), который сохраняет порядок городов из родительских маршрутов, избегая дублирований.
Алгоритм:

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

In [9]:
# Кроссинговер
def crossover(parent1: Chromosome, parent2: Chromosome) -> Chromosome:
    """Реализует упорядоченный одноточечный кроссинговер (OX1)."""
    size = len(parent1.chromosome)
    cut = random.randint(1, size - 2)
    head = parent1.chromosome[:cut]
    tail = [gene for gene in parent2.chromosome if gene not in head]
    return Chromosome(head + tail, parent1.distance_matrix)


# Селекция (турнирный отбор)
def tournament_selection(population: List[Chromosome], tournament_size: int = 3) -> Chromosome:
    """Выбирает лучшую хромосому из случайной подгруппы."""
    contenders = random.sample(population, tournament_size)
    return max(contenders, key=lambda chrom: chrom.fitness)

Задам оператор мутации

Буду использовать два вида мутаций:

  • Обменная мутация (swap mutation) случайно выбираются два гена (города) в хромосоме и меняются местами.

  • Мутация перемешиванием (scramble mutation) случайный подотрезок хромосомы перемешивается, изменяя порядок городов внутри него.

In [10]:
# Обменная мутация
def swap_mutate(chromosome: List[int], mutation_rate: float) -> Chromosome:
    """Случайно меняет местами 2 города"""
    if np.random.random() < mutation_rate:
        genes = chromosome.chromosome
        idx1, idx2 = np.random.choice(len(genes), 2, replace=False)
        genes[idx1], genes[idx2] = genes[idx2], genes[idx1]
    return chromosome


# Мутация перемешиванием
def scramble_mutation(chromosome: List[int], mutation_rate: float) -> Chromosome:
    """Мутация "перемешиванием" случайного отрезка"""
    if np.random.random() < mutation_rate:
        size: int = len(chromosome)
        start, end = sorted(np.random.randint(0, size, 2))
        segment: List[int] = chromosome[start:end]
        np.random.shuffle(segment)
        chromosome[start:end] = segment
    return chromosome

Наконец сам генетический алгоритм

Основной эволюционный цикл Для каждого поколения выполняются следующие шаги:

  1. Оценка приспособленности Для каждой хромосомы вычисляется фитнес-функция как обратная величина к длине маршрута. Это делает более короткие маршруты более предпочтительными.
  2. Отбор элиты Лучшие elite_size решений переходят в следующее поколение без изменений, сохраняя уже найденные хорошие варианты.
  3. Формирование нового поколения Популяция дополняется потомками, созданными с помощью: Турнирного отбора (случайно выбираются несколько особей), Упорядоченного кроссовера (комбинация родительских маршрутов) и Мутаций - случайные изменения (обмен городов или перемешивание).
In [11]:
from typing import Callable, List

# Основной цикл Генетического Алгоритма 
def genetic_algorithm(cities: pd.DataFrame, generations: int = 400, mutation_rate: float = 0.01, population_size: int = 100,
    elite_size: int = 20, crossover_function: Callable = crossover, mutation_function: Callable = swap_mutate) -> Chromosome:
    """Запускает генетический алгоритм."""
    
    # Создание матрицы расстояний
    fitness_history = []
    coords = cities[["geo_lat", "geo_lon"]].values
    distance_matrix = np.zeros((len(coords), len(coords)))
    for i in range(len(coords)):
        for j in range(len(coords)):
            if i != j:
                distance_matrix[i][j] = haversine_distance(coords[i], coords[j])
    
    # Генерация начальной популяции как объектов Chromosome
    population: List[Chromosome] = [
        Chromosome(chromo, distance_matrix)
        for chromo in generate_initial_population(population_size, len(cities))
    ]

    # Основной цикл эволюции
    for generation in range(generations):
        # Сортировка по приспособленности
        population.sort(key=lambda x: x.fitness, reverse=True)

        # Элита
        new_population: List[Chromosome] = population[:elite_size]

        # Генерация потомков
        while len(new_population) < population_size:
            parent1 = tournament_selection(population, tournament_size=3)
            parent2 = tournament_selection(population, tournament_size=3)
            
            child = crossover_function(parent1, parent2)

            if random.random() < mutation_rate:
                mutation_function(child, mutation_rate)

            new_population.append(child)

        population = new_population
        best = population[0]
        fitness_history.append(1 / best.fitness)  # Длина маршрута в км или м
        print(f"Поколение {generation + 1} | Лучшая длина маршрута: {1 / best.fitness:.2f} км")

    return population[0], fitness_history



def haversine_distance(coordinate1: np.ndarray, coordinate2: np.ndarray) -> float:
    """Вычисляет расстояние между двумя точками на сфере (в км) по формуле гаверсинусов."""
    lat1, lon1 = coordinate1
    lat2, lon2 = coordinate2
    
    # Конвертация градусов в радианы
    lat1, lon1, lat2, lon2 = map(np.radians, [lat1, lon1, lat2, lon2])
    
    # Разницы координат
    dlat = lat2 - lat1
    dlon = lon2 - lon1
    
    # Формула гаверсинусов
    a = np.sin(dlat/2)**2 + np.cos(lat1) * np.cos(lat2) * np.sin(dlon/2)**2
    c = 2 * np.arcsin(np.sqrt(a))
    
    # Радиус Земли в километрах
    r = 6371
    return c * r
In [12]:
best, fitness_history = genetic_algorithm(
    historic_large_cities,
    population_size=100,
    elite_size=20,
    mutation_rate=0.05,
    generations=500,
    crossover_function=crossover,
    mutation_function=swap_mutate
)

print(f"Лучший маршрут: {best.chromosome}")
print(f"Общая длина маршрута: {1 / best.fitness:.2f} км")
Поколение 1 | Лучшая длина маршрута: 14053.82 км
Поколение 2 | Лучшая длина маршрута: 13371.11 км
Поколение 3 | Лучшая длина маршрута: 12816.05 км
Поколение 4 | Лучшая длина маршрута: 12799.97 км
Поколение 5 | Лучшая длина маршрута: 12200.28 км
Поколение 6 | Лучшая длина маршрута: 10584.77 км
Поколение 7 | Лучшая длина маршрута: 10584.77 км
Поколение 8 | Лучшая длина маршрута: 10584.77 км
Поколение 9 | Лучшая длина маршрута: 10584.77 км
Поколение 10 | Лучшая длина маршрута: 10551.35 км
Поколение 11 | Лучшая длина маршрута: 10524.14 км
Поколение 12 | Лучшая длина маршрута: 10524.14 км
Поколение 13 | Лучшая длина маршрута: 10524.14 км
Поколение 14 | Лучшая длина маршрута: 10524.14 км
Поколение 15 | Лучшая длина маршрута: 10524.14 км
Поколение 16 | Лучшая длина маршрута: 10524.14 км
Поколение 17 | Лучшая длина маршрута: 10524.14 км
Поколение 18 | Лучшая длина маршрута: 10524.14 км
Поколение 19 | Лучшая длина маршрута: 10524.14 км
Поколение 20 | Лучшая длина маршрута: 10524.14 км
Поколение 21 | Лучшая длина маршрута: 10524.14 км
Поколение 22 | Лучшая длина маршрута: 10524.14 км
Поколение 23 | Лучшая длина маршрута: 10524.14 км
Поколение 24 | Лучшая длина маршрута: 10524.14 км
Поколение 25 | Лучшая длина маршрута: 10524.14 км
Поколение 26 | Лучшая длина маршрута: 10524.14 км
Поколение 27 | Лучшая длина маршрута: 10524.14 км
Поколение 28 | Лучшая длина маршрута: 10524.14 км
Поколение 29 | Лучшая длина маршрута: 10524.14 км
Поколение 30 | Лучшая длина маршрута: 10524.14 км
Поколение 31 | Лучшая длина маршрута: 10524.14 км
Поколение 32 | Лучшая длина маршрута: 10524.14 км
Поколение 33 | Лучшая длина маршрута: 10413.95 км
Поколение 34 | Лучшая длина маршрута: 10413.95 км
Поколение 35 | Лучшая длина маршрута: 10413.95 км
Поколение 36 | Лучшая длина маршрута: 10413.95 км
Поколение 37 | Лучшая длина маршрута: 10413.95 км
Поколение 38 | Лучшая длина маршрута: 10413.95 км
Поколение 39 | Лучшая длина маршрута: 10413.95 км
Поколение 40 | Лучшая длина маршрута: 10413.95 км
Поколение 41 | Лучшая длина маршрута: 10413.95 км
Поколение 42 | Лучшая длина маршрута: 10413.95 км
Поколение 43 | Лучшая длина маршрута: 10413.95 км
Поколение 44 | Лучшая длина маршрута: 10413.95 км
Поколение 45 | Лучшая длина маршрута: 10413.95 км
Поколение 46 | Лучшая длина маршрута: 10413.95 км
Поколение 47 | Лучшая длина маршрута: 10413.95 км
Поколение 48 | Лучшая длина маршрута: 10413.95 км
Поколение 49 | Лучшая длина маршрута: 10413.95 км
Поколение 50 | Лучшая длина маршрута: 10413.95 км
Поколение 51 | Лучшая длина маршрута: 10413.95 км
Поколение 52 | Лучшая длина маршрута: 10413.95 км
Поколение 53 | Лучшая длина маршрута: 10413.95 км
Поколение 54 | Лучшая длина маршрута: 10413.95 км
Поколение 55 | Лучшая длина маршрута: 10413.95 км
Поколение 56 | Лучшая длина маршрута: 10413.95 км
Поколение 57 | Лучшая длина маршрута: 10413.95 км
Поколение 58 | Лучшая длина маршрута: 10413.95 км
Поколение 59 | Лучшая длина маршрута: 10413.95 км
Поколение 60 | Лучшая длина маршрута: 10413.95 км
Поколение 61 | Лучшая длина маршрута: 10413.95 км
Поколение 62 | Лучшая длина маршрута: 10413.95 км
Поколение 63 | Лучшая длина маршрута: 10413.95 км
Поколение 64 | Лучшая длина маршрута: 10413.95 км
Поколение 65 | Лучшая длина маршрута: 10413.95 км
Поколение 66 | Лучшая длина маршрута: 10413.95 км
Поколение 67 | Лучшая длина маршрута: 10413.95 км
Поколение 68 | Лучшая длина маршрута: 10413.95 км
Поколение 69 | Лучшая длина маршрута: 10413.95 км
Поколение 70 | Лучшая длина маршрута: 10413.95 км
Поколение 71 | Лучшая длина маршрута: 10413.95 км
Поколение 72 | Лучшая длина маршрута: 10413.95 км
Поколение 73 | Лучшая длина маршрута: 10413.95 км
Поколение 74 | Лучшая длина маршрута: 10413.95 км
Поколение 75 | Лучшая длина маршрута: 10413.95 км
Поколение 76 | Лучшая длина маршрута: 10413.95 км
Поколение 77 | Лучшая длина маршрута: 10413.95 км
Поколение 78 | Лучшая длина маршрута: 10413.95 км
Поколение 79 | Лучшая длина маршрута: 10413.95 км
Поколение 80 | Лучшая длина маршрута: 10413.95 км
Поколение 81 | Лучшая длина маршрута: 10413.95 км
Поколение 82 | Лучшая длина маршрута: 10413.95 км
Поколение 83 | Лучшая длина маршрута: 10413.95 км
Поколение 84 | Лучшая длина маршрута: 10413.95 км
Поколение 85 | Лучшая длина маршрута: 10413.95 км
Поколение 86 | Лучшая длина маршрута: 10413.95 км
Поколение 87 | Лучшая длина маршрута: 10413.95 км
Поколение 88 | Лучшая длина маршрута: 10413.95 км
Поколение 89 | Лучшая длина маршрута: 10413.95 км
Поколение 90 | Лучшая длина маршрута: 10413.95 км
Поколение 91 | Лучшая длина маршрута: 10413.95 км
Поколение 92 | Лучшая длина маршрута: 10413.95 км
Поколение 93 | Лучшая длина маршрута: 10413.95 км
Поколение 94 | Лучшая длина маршрута: 10413.95 км
Поколение 95 | Лучшая длина маршрута: 10413.95 км
Поколение 96 | Лучшая длина маршрута: 10413.95 км
Поколение 97 | Лучшая длина маршрута: 10413.95 км
Поколение 98 | Лучшая длина маршрута: 10413.95 км
Поколение 99 | Лучшая длина маршрута: 10413.95 км
Поколение 100 | Лучшая длина маршрута: 10413.95 км
Поколение 101 | Лучшая длина маршрута: 10413.95 км
Поколение 102 | Лучшая длина маршрута: 10413.95 км
Поколение 103 | Лучшая длина маршрута: 10413.95 км
Поколение 104 | Лучшая длина маршрута: 10413.95 км
Поколение 105 | Лучшая длина маршрута: 10413.95 км
Поколение 106 | Лучшая длина маршрута: 10413.95 км
Поколение 107 | Лучшая длина маршрута: 10413.95 км
Поколение 108 | Лучшая длина маршрута: 10413.95 км
Поколение 109 | Лучшая длина маршрута: 10413.95 км
Поколение 110 | Лучшая длина маршрута: 10413.95 км
Поколение 111 | Лучшая длина маршрута: 10413.95 км
Поколение 112 | Лучшая длина маршрута: 10413.95 км
Поколение 113 | Лучшая длина маршрута: 10413.95 км
Поколение 114 | Лучшая длина маршрута: 10413.95 км
Поколение 115 | Лучшая длина маршрута: 10413.95 км
Поколение 116 | Лучшая длина маршрута: 10413.95 км
Поколение 117 | Лучшая длина маршрута: 10413.95 км
Поколение 118 | Лучшая длина маршрута: 10413.95 км
Поколение 119 | Лучшая длина маршрута: 10413.95 км
Поколение 120 | Лучшая длина маршрута: 10413.95 км
Поколение 121 | Лучшая длина маршрута: 10413.95 км
Поколение 122 | Лучшая длина маршрута: 10413.95 км
Поколение 123 | Лучшая длина маршрута: 10413.95 км
Поколение 124 | Лучшая длина маршрута: 10413.95 км
Поколение 125 | Лучшая длина маршрута: 10413.95 км
Поколение 126 | Лучшая длина маршрута: 10413.95 км
Поколение 127 | Лучшая длина маршрута: 10413.95 км
Поколение 128 | Лучшая длина маршрута: 10413.95 км
Поколение 129 | Лучшая длина маршрута: 10413.95 км
Поколение 130 | Лучшая длина маршрута: 10413.95 км
Поколение 131 | Лучшая длина маршрута: 10413.95 км
Поколение 132 | Лучшая длина маршрута: 10413.95 км
Поколение 133 | Лучшая длина маршрута: 10413.95 км
Поколение 134 | Лучшая длина маршрута: 10413.95 км
Поколение 135 | Лучшая длина маршрута: 10413.95 км
Поколение 136 | Лучшая длина маршрута: 10413.95 км
Поколение 137 | Лучшая длина маршрута: 10413.95 км
Поколение 138 | Лучшая длина маршрута: 10413.95 км
Поколение 139 | Лучшая длина маршрута: 10413.95 км
Поколение 140 | Лучшая длина маршрута: 10413.95 км
Поколение 141 | Лучшая длина маршрута: 10413.95 км
Поколение 142 | Лучшая длина маршрута: 10413.95 км
Поколение 143 | Лучшая длина маршрута: 10413.95 км
Поколение 144 | Лучшая длина маршрута: 10413.95 км
Поколение 145 | Лучшая длина маршрута: 10413.95 км
Поколение 146 | Лучшая длина маршрута: 10413.95 км
Поколение 147 | Лучшая длина маршрута: 10413.95 км
Поколение 148 | Лучшая длина маршрута: 10413.95 км
Поколение 149 | Лучшая длина маршрута: 10413.95 км
Поколение 150 | Лучшая длина маршрута: 10413.95 км
Поколение 151 | Лучшая длина маршрута: 10413.95 км
Поколение 152 | Лучшая длина маршрута: 10413.95 км
Поколение 153 | Лучшая длина маршрута: 10413.95 км
Поколение 154 | Лучшая длина маршрута: 10413.95 км
Поколение 155 | Лучшая длина маршрута: 10413.95 км
Поколение 156 | Лучшая длина маршрута: 10413.95 км
Поколение 157 | Лучшая длина маршрута: 10413.95 км
Поколение 158 | Лучшая длина маршрута: 10413.95 км
Поколение 159 | Лучшая длина маршрута: 10413.95 км
Поколение 160 | Лучшая длина маршрута: 10413.95 км
Поколение 161 | Лучшая длина маршрута: 10413.95 км
Поколение 162 | Лучшая длина маршрута: 10413.95 км
Поколение 163 | Лучшая длина маршрута: 10413.95 км
Поколение 164 | Лучшая длина маршрута: 10413.95 км
Поколение 165 | Лучшая длина маршрута: 10413.95 км
Поколение 166 | Лучшая длина маршрута: 10413.95 км
Поколение 167 | Лучшая длина маршрута: 10413.95 км
Поколение 168 | Лучшая длина маршрута: 10413.95 км
Поколение 169 | Лучшая длина маршрута: 10413.95 км
Поколение 170 | Лучшая длина маршрута: 10413.95 км
Поколение 171 | Лучшая длина маршрута: 10413.95 км
Поколение 172 | Лучшая длина маршрута: 10413.95 км
Поколение 173 | Лучшая длина маршрута: 10413.95 км
Поколение 174 | Лучшая длина маршрута: 10413.95 км
Поколение 175 | Лучшая длина маршрута: 10413.95 км
Поколение 176 | Лучшая длина маршрута: 10413.95 км
Поколение 177 | Лучшая длина маршрута: 10413.95 км
Поколение 178 | Лучшая длина маршрута: 10413.95 км
Поколение 179 | Лучшая длина маршрута: 10413.95 км
Поколение 180 | Лучшая длина маршрута: 10413.95 км
Поколение 181 | Лучшая длина маршрута: 10413.95 км
Поколение 182 | Лучшая длина маршрута: 10413.95 км
Поколение 183 | Лучшая длина маршрута: 10413.95 км
Поколение 184 | Лучшая длина маршрута: 10413.95 км
Поколение 185 | Лучшая длина маршрута: 10413.95 км
Поколение 186 | Лучшая длина маршрута: 10413.95 км
Поколение 187 | Лучшая длина маршрута: 10413.95 км
Поколение 188 | Лучшая длина маршрута: 10413.95 км
Поколение 189 | Лучшая длина маршрута: 10413.95 км
Поколение 190 | Лучшая длина маршрута: 10413.95 км
Поколение 191 | Лучшая длина маршрута: 10413.95 км
Поколение 192 | Лучшая длина маршрута: 10413.95 км
Поколение 193 | Лучшая длина маршрута: 10413.95 км
Поколение 194 | Лучшая длина маршрута: 10413.95 км
Поколение 195 | Лучшая длина маршрута: 10413.95 км
Поколение 196 | Лучшая длина маршрута: 10413.95 км
Поколение 197 | Лучшая длина маршрута: 10413.95 км
Поколение 198 | Лучшая длина маршрута: 10413.95 км
Поколение 199 | Лучшая длина маршрута: 10413.95 км
Поколение 200 | Лучшая длина маршрута: 10413.95 км
Поколение 201 | Лучшая длина маршрута: 10413.95 км
Поколение 202 | Лучшая длина маршрута: 10413.95 км
Поколение 203 | Лучшая длина маршрута: 10413.95 км
Поколение 204 | Лучшая длина маршрута: 10413.95 км
Поколение 205 | Лучшая длина маршрута: 10413.95 км
Поколение 206 | Лучшая длина маршрута: 10413.95 км
Поколение 207 | Лучшая длина маршрута: 10413.95 км
Поколение 208 | Лучшая длина маршрута: 10413.95 км
Поколение 209 | Лучшая длина маршрута: 10413.95 км
Поколение 210 | Лучшая длина маршрута: 10173.97 км
Поколение 211 | Лучшая длина маршрута: 10173.97 км
Поколение 212 | Лучшая длина маршрута: 10173.97 км
Поколение 213 | Лучшая длина маршрута: 10173.97 км
Поколение 214 | Лучшая длина маршрута: 10173.97 км
Поколение 215 | Лучшая длина маршрута: 10173.97 км
Поколение 216 | Лучшая длина маршрута: 10173.97 км
Поколение 217 | Лучшая длина маршрута: 10173.97 км
Поколение 218 | Лучшая длина маршрута: 10173.97 км
Поколение 219 | Лучшая длина маршрута: 10173.97 км
Поколение 220 | Лучшая длина маршрута: 10173.97 км
Поколение 221 | Лучшая длина маршрута: 10173.97 км
Поколение 222 | Лучшая длина маршрута: 10173.97 км
Поколение 223 | Лучшая длина маршрута: 10173.97 км
Поколение 224 | Лучшая длина маршрута: 10173.97 км
Поколение 225 | Лучшая длина маршрута: 10173.97 км
Поколение 226 | Лучшая длина маршрута: 10173.97 км
Поколение 227 | Лучшая длина маршрута: 10173.97 км
Поколение 228 | Лучшая длина маршрута: 10173.97 км
Поколение 229 | Лучшая длина маршрута: 10173.97 км
Поколение 230 | Лучшая длина маршрута: 10173.97 км
Поколение 231 | Лучшая длина маршрута: 10173.97 км
Поколение 232 | Лучшая длина маршрута: 10173.97 км
Поколение 233 | Лучшая длина маршрута: 10173.97 км
Поколение 234 | Лучшая длина маршрута: 10173.97 км
Поколение 235 | Лучшая длина маршрута: 10173.97 км
Поколение 236 | Лучшая длина маршрута: 10173.97 км
Поколение 237 | Лучшая длина маршрута: 10173.97 км
Поколение 238 | Лучшая длина маршрута: 10173.97 км
Поколение 239 | Лучшая длина маршрута: 10173.97 км
Поколение 240 | Лучшая длина маршрута: 10173.97 км
Поколение 241 | Лучшая длина маршрута: 10173.97 км
Поколение 242 | Лучшая длина маршрута: 10173.97 км
Поколение 243 | Лучшая длина маршрута: 10173.97 км
Поколение 244 | Лучшая длина маршрута: 10173.97 км
Поколение 245 | Лучшая длина маршрута: 10173.97 км
Поколение 246 | Лучшая длина маршрута: 10173.97 км
Поколение 247 | Лучшая длина маршрута: 10173.97 км
Поколение 248 | Лучшая длина маршрута: 10173.97 км
Поколение 249 | Лучшая длина маршрута: 10173.97 км
Поколение 250 | Лучшая длина маршрута: 10173.97 км
Поколение 251 | Лучшая длина маршрута: 10173.97 км
Поколение 252 | Лучшая длина маршрута: 10173.97 км
Поколение 253 | Лучшая длина маршрута: 10173.97 км
Поколение 254 | Лучшая длина маршрута: 10173.97 км
Поколение 255 | Лучшая длина маршрута: 10173.97 км
Поколение 256 | Лучшая длина маршрута: 10173.97 км
Поколение 257 | Лучшая длина маршрута: 10173.97 км
Поколение 258 | Лучшая длина маршрута: 10173.97 км
Поколение 259 | Лучшая длина маршрута: 10173.97 км
Поколение 260 | Лучшая длина маршрута: 10173.97 км
Поколение 261 | Лучшая длина маршрута: 10173.97 км
Поколение 262 | Лучшая длина маршрута: 10173.97 км
Поколение 263 | Лучшая длина маршрута: 10173.97 км
Поколение 264 | Лучшая длина маршрута: 10173.97 км
Поколение 265 | Лучшая длина маршрута: 10173.97 км
Поколение 266 | Лучшая длина маршрута: 10173.97 км
Поколение 267 | Лучшая длина маршрута: 10173.97 км
Поколение 268 | Лучшая длина маршрута: 10173.97 км
Поколение 269 | Лучшая длина маршрута: 10173.97 км
Поколение 270 | Лучшая длина маршрута: 10173.97 км
Поколение 271 | Лучшая длина маршрута: 10173.97 км
Поколение 272 | Лучшая длина маршрута: 10173.97 км
Поколение 273 | Лучшая длина маршрута: 10173.97 км
Поколение 274 | Лучшая длина маршрута: 10173.97 км
Поколение 275 | Лучшая длина маршрута: 10173.97 км
Поколение 276 | Лучшая длина маршрута: 10173.97 км
Поколение 277 | Лучшая длина маршрута: 10173.97 км
Поколение 278 | Лучшая длина маршрута: 10173.97 км
Поколение 279 | Лучшая длина маршрута: 10173.97 км
Поколение 280 | Лучшая длина маршрута: 10173.97 км
Поколение 281 | Лучшая длина маршрута: 10173.97 км
Поколение 282 | Лучшая длина маршрута: 10173.97 км
Поколение 283 | Лучшая длина маршрута: 10173.97 км
Поколение 284 | Лучшая длина маршрута: 10173.97 км
Поколение 285 | Лучшая длина маршрута: 10173.97 км
Поколение 286 | Лучшая длина маршрута: 10173.97 км
Поколение 287 | Лучшая длина маршрута: 10173.97 км
Поколение 288 | Лучшая длина маршрута: 10173.97 км
Поколение 289 | Лучшая длина маршрута: 10173.97 км
Поколение 290 | Лучшая длина маршрута: 10173.97 км
Поколение 291 | Лучшая длина маршрута: 10173.97 км
Поколение 292 | Лучшая длина маршрута: 10173.97 км
Поколение 293 | Лучшая длина маршрута: 10173.97 км
Поколение 294 | Лучшая длина маршрута: 10173.97 км
Поколение 295 | Лучшая длина маршрута: 10173.97 км
Поколение 296 | Лучшая длина маршрута: 10173.97 км
Поколение 297 | Лучшая длина маршрута: 10173.97 км
Поколение 298 | Лучшая длина маршрута: 10173.97 км
Поколение 299 | Лучшая длина маршрута: 10173.97 км
Поколение 300 | Лучшая длина маршрута: 10173.97 км
Поколение 301 | Лучшая длина маршрута: 10173.97 км
Поколение 302 | Лучшая длина маршрута: 10173.97 км
Поколение 303 | Лучшая длина маршрута: 10173.97 км
Поколение 304 | Лучшая длина маршрута: 10173.97 км
Поколение 305 | Лучшая длина маршрута: 10173.97 км
Поколение 306 | Лучшая длина маршрута: 10173.97 км
Поколение 307 | Лучшая длина маршрута: 10173.97 км
Поколение 308 | Лучшая длина маршрута: 10173.97 км
Поколение 309 | Лучшая длина маршрута: 10173.97 км
Поколение 310 | Лучшая длина маршрута: 10173.97 км
Поколение 311 | Лучшая длина маршрута: 10173.97 км
Поколение 312 | Лучшая длина маршрута: 10173.97 км
Поколение 313 | Лучшая длина маршрута: 10173.97 км
Поколение 314 | Лучшая длина маршрута: 10173.97 км
Поколение 315 | Лучшая длина маршрута: 10173.97 км
Поколение 316 | Лучшая длина маршрута: 10173.97 км
Поколение 317 | Лучшая длина маршрута: 10173.97 км
Поколение 318 | Лучшая длина маршрута: 10173.97 км
Поколение 319 | Лучшая длина маршрута: 10173.97 км
Поколение 320 | Лучшая длина маршрута: 10173.97 км
Поколение 321 | Лучшая длина маршрута: 10173.97 км
Поколение 322 | Лучшая длина маршрута: 10173.97 км
Поколение 323 | Лучшая длина маршрута: 10173.97 км
Поколение 324 | Лучшая длина маршрута: 10173.97 км
Поколение 325 | Лучшая длина маршрута: 10173.97 км
Поколение 326 | Лучшая длина маршрута: 10173.97 км
Поколение 327 | Лучшая длина маршрута: 10173.97 км
Поколение 328 | Лучшая длина маршрута: 10173.97 км
Поколение 329 | Лучшая длина маршрута: 10173.97 км
Поколение 330 | Лучшая длина маршрута: 10173.97 км
Поколение 331 | Лучшая длина маршрута: 10173.97 км
Поколение 332 | Лучшая длина маршрута: 10173.97 км
Поколение 333 | Лучшая длина маршрута: 10173.97 км
Поколение 334 | Лучшая длина маршрута: 10173.97 км
Поколение 335 | Лучшая длина маршрута: 10173.97 км
Поколение 336 | Лучшая длина маршрута: 10173.97 км
Поколение 337 | Лучшая длина маршрута: 10173.97 км
Поколение 338 | Лучшая длина маршрута: 10173.97 км
Поколение 339 | Лучшая длина маршрута: 10173.97 км
Поколение 340 | Лучшая длина маршрута: 10173.97 км
Поколение 341 | Лучшая длина маршрута: 10173.97 км
Поколение 342 | Лучшая длина маршрута: 10173.97 км
Поколение 343 | Лучшая длина маршрута: 10173.97 км
Поколение 344 | Лучшая длина маршрута: 10173.97 км
Поколение 345 | Лучшая длина маршрута: 10173.97 км
Поколение 346 | Лучшая длина маршрута: 10173.97 км
Поколение 347 | Лучшая длина маршрута: 10173.97 км
Поколение 348 | Лучшая длина маршрута: 10169.38 км
Поколение 349 | Лучшая длина маршрута: 10169.38 км
Поколение 350 | Лучшая длина маршрута: 10169.38 км
Поколение 351 | Лучшая длина маршрута: 10169.38 км
Поколение 352 | Лучшая длина маршрута: 10169.38 км
Поколение 353 | Лучшая длина маршрута: 10169.38 км
Поколение 354 | Лучшая длина маршрута: 10169.38 км
Поколение 355 | Лучшая длина маршрута: 10169.38 км
Поколение 356 | Лучшая длина маршрута: 10169.38 км
Поколение 357 | Лучшая длина маршрута: 10169.38 км
Поколение 358 | Лучшая длина маршрута: 10169.38 км
Поколение 359 | Лучшая длина маршрута: 10169.38 км
Поколение 360 | Лучшая длина маршрута: 10169.38 км
Поколение 361 | Лучшая длина маршрута: 10169.38 км
Поколение 362 | Лучшая длина маршрута: 10169.38 км
Поколение 363 | Лучшая длина маршрута: 10169.38 км
Поколение 364 | Лучшая длина маршрута: 10169.38 км
Поколение 365 | Лучшая длина маршрута: 10169.38 км
Поколение 366 | Лучшая длина маршрута: 10169.38 км
Поколение 367 | Лучшая длина маршрута: 10169.38 км
Поколение 368 | Лучшая длина маршрута: 10169.38 км
Поколение 369 | Лучшая длина маршрута: 10169.38 км
Поколение 370 | Лучшая длина маршрута: 10169.38 км
Поколение 371 | Лучшая длина маршрута: 10169.38 км
Поколение 372 | Лучшая длина маршрута: 10169.38 км
Поколение 373 | Лучшая длина маршрута: 10169.38 км
Поколение 374 | Лучшая длина маршрута: 10169.38 км
Поколение 375 | Лучшая длина маршрута: 10169.38 км
Поколение 376 | Лучшая длина маршрута: 10169.38 км
Поколение 377 | Лучшая длина маршрута: 10169.38 км
Поколение 378 | Лучшая длина маршрута: 10169.38 км
Поколение 379 | Лучшая длина маршрута: 10169.38 км
Поколение 380 | Лучшая длина маршрута: 10169.38 км
Поколение 381 | Лучшая длина маршрута: 10169.38 км
Поколение 382 | Лучшая длина маршрута: 10169.38 км
Поколение 383 | Лучшая длина маршрута: 10169.38 км
Поколение 384 | Лучшая длина маршрута: 10169.38 км
Поколение 385 | Лучшая длина маршрута: 10169.38 км
Поколение 386 | Лучшая длина маршрута: 10169.38 км
Поколение 387 | Лучшая длина маршрута: 10169.38 км
Поколение 388 | Лучшая длина маршрута: 10169.38 км
Поколение 389 | Лучшая длина маршрута: 10169.38 км
Поколение 390 | Лучшая длина маршрута: 10169.38 км
Поколение 391 | Лучшая длина маршрута: 10169.38 км
Поколение 392 | Лучшая длина маршрута: 10169.38 км
Поколение 393 | Лучшая длина маршрута: 10169.38 км
Поколение 394 | Лучшая длина маршрута: 10169.38 км
Поколение 395 | Лучшая длина маршрута: 10169.38 км
Поколение 396 | Лучшая длина маршрута: 10169.38 км
Поколение 397 | Лучшая длина маршрута: 10169.38 км
Поколение 398 | Лучшая длина маршрута: 10169.38 км
Поколение 399 | Лучшая длина маршрута: 10169.38 км
Поколение 400 | Лучшая длина маршрута: 10169.38 км
Поколение 401 | Лучшая длина маршрута: 10169.38 км
Поколение 402 | Лучшая длина маршрута: 10169.38 км
Поколение 403 | Лучшая длина маршрута: 10169.38 км
Поколение 404 | Лучшая длина маршрута: 10169.38 км
Поколение 405 | Лучшая длина маршрута: 10169.38 км
Поколение 406 | Лучшая длина маршрута: 10169.38 км
Поколение 407 | Лучшая длина маршрута: 10169.38 км
Поколение 408 | Лучшая длина маршрута: 10169.38 км
Поколение 409 | Лучшая длина маршрута: 10169.38 км
Поколение 410 | Лучшая длина маршрута: 10169.38 км
Поколение 411 | Лучшая длина маршрута: 10169.38 км
Поколение 412 | Лучшая длина маршрута: 10169.38 км
Поколение 413 | Лучшая длина маршрута: 10169.38 км
Поколение 414 | Лучшая длина маршрута: 10169.38 км
Поколение 415 | Лучшая длина маршрута: 10169.38 км
Поколение 416 | Лучшая длина маршрута: 10169.38 км
Поколение 417 | Лучшая длина маршрута: 10169.38 км
Поколение 418 | Лучшая длина маршрута: 10169.38 км
Поколение 419 | Лучшая длина маршрута: 10169.38 км
Поколение 420 | Лучшая длина маршрута: 10169.38 км
Поколение 421 | Лучшая длина маршрута: 10169.38 км
Поколение 422 | Лучшая длина маршрута: 10169.38 км
Поколение 423 | Лучшая длина маршрута: 10169.38 км
Поколение 424 | Лучшая длина маршрута: 10169.38 км
Поколение 425 | Лучшая длина маршрута: 10169.38 км
Поколение 426 | Лучшая длина маршрута: 10169.38 км
Поколение 427 | Лучшая длина маршрута: 10169.38 км
Поколение 428 | Лучшая длина маршрута: 10169.38 км
Поколение 429 | Лучшая длина маршрута: 10169.38 км
Поколение 430 | Лучшая длина маршрута: 10169.38 км
Поколение 431 | Лучшая длина маршрута: 10169.38 км
Поколение 432 | Лучшая длина маршрута: 10169.38 км
Поколение 433 | Лучшая длина маршрута: 10169.38 км
Поколение 434 | Лучшая длина маршрута: 10169.38 км
Поколение 435 | Лучшая длина маршрута: 10169.38 км
Поколение 436 | Лучшая длина маршрута: 10169.38 км
Поколение 437 | Лучшая длина маршрута: 10169.38 км
Поколение 438 | Лучшая длина маршрута: 10169.38 км
Поколение 439 | Лучшая длина маршрута: 10169.38 км
Поколение 440 | Лучшая длина маршрута: 10169.38 км
Поколение 441 | Лучшая длина маршрута: 10169.38 км
Поколение 442 | Лучшая длина маршрута: 10169.38 км
Поколение 443 | Лучшая длина маршрута: 10169.38 км
Поколение 444 | Лучшая длина маршрута: 10169.38 км
Поколение 445 | Лучшая длина маршрута: 10169.38 км
Поколение 446 | Лучшая длина маршрута: 10169.38 км
Поколение 447 | Лучшая длина маршрута: 10169.38 км
Поколение 448 | Лучшая длина маршрута: 10169.38 км
Поколение 449 | Лучшая длина маршрута: 10169.38 км
Поколение 450 | Лучшая длина маршрута: 10169.38 км
Поколение 451 | Лучшая длина маршрута: 10169.38 км
Поколение 452 | Лучшая длина маршрута: 10169.38 км
Поколение 453 | Лучшая длина маршрута: 10169.38 км
Поколение 454 | Лучшая длина маршрута: 10169.38 км
Поколение 455 | Лучшая длина маршрута: 10169.38 км
Поколение 456 | Лучшая длина маршрута: 10169.38 км
Поколение 457 | Лучшая длина маршрута: 10169.38 км
Поколение 458 | Лучшая длина маршрута: 10169.38 км
Поколение 459 | Лучшая длина маршрута: 9924.99 км
Поколение 460 | Лучшая длина маршрута: 9924.99 км
Поколение 461 | Лучшая длина маршрута: 9924.99 км
Поколение 462 | Лучшая длина маршрута: 9924.99 км
Поколение 463 | Лучшая длина маршрута: 9704.80 км
Поколение 464 | Лучшая длина маршрута: 9704.80 км
Поколение 465 | Лучшая длина маршрута: 9704.80 км
Поколение 466 | Лучшая длина маршрута: 9704.80 км
Поколение 467 | Лучшая длина маршрута: 9704.80 км
Поколение 468 | Лучшая длина маршрута: 9704.80 км
Поколение 469 | Лучшая длина маршрута: 9704.80 км
Поколение 470 | Лучшая длина маршрута: 9704.80 км
Поколение 471 | Лучшая длина маршрута: 9704.80 км
Поколение 472 | Лучшая длина маршрута: 9704.80 км
Поколение 473 | Лучшая длина маршрута: 9704.80 км
Поколение 474 | Лучшая длина маршрута: 9704.80 км
Поколение 475 | Лучшая длина маршрута: 9704.80 км
Поколение 476 | Лучшая длина маршрута: 9704.80 км
Поколение 477 | Лучшая длина маршрута: 9704.80 км
Поколение 478 | Лучшая длина маршрута: 9704.80 км
Поколение 479 | Лучшая длина маршрута: 9704.80 км
Поколение 480 | Лучшая длина маршрута: 9704.80 км
Поколение 481 | Лучшая длина маршрута: 9704.80 км
Поколение 482 | Лучшая длина маршрута: 9704.80 км
Поколение 483 | Лучшая длина маршрута: 9704.80 км
Поколение 484 | Лучшая длина маршрута: 9704.80 км
Поколение 485 | Лучшая длина маршрута: 9704.80 км
Поколение 486 | Лучшая длина маршрута: 9704.80 км
Поколение 487 | Лучшая длина маршрута: 9704.80 км
Поколение 488 | Лучшая длина маршрута: 9704.80 км
Поколение 489 | Лучшая длина маршрута: 9704.80 км
Поколение 490 | Лучшая длина маршрута: 9704.80 км
Поколение 491 | Лучшая длина маршрута: 9704.80 км
Поколение 492 | Лучшая длина маршрута: 9704.80 км
Поколение 493 | Лучшая длина маршрута: 9704.80 км
Поколение 494 | Лучшая длина маршрута: 9704.80 км
Поколение 495 | Лучшая длина маршрута: 9704.80 км
Поколение 496 | Лучшая длина маршрута: 9704.80 км
Поколение 497 | Лучшая длина маршрута: 9704.80 км
Поколение 498 | Лучшая длина маршрута: 9704.80 км
Поколение 499 | Лучшая длина маршрута: 9704.80 км
Поколение 500 | Лучшая длина маршрута: 9704.80 км
Лучший маршрут: [20, 1, 14, 2, 8, 4, 6, 15, 7, 11, 5, 17, 12, 3, 13, 10, 18, 16, 0, 19, 9]
Общая длина маршрута: 9704.80 км

Нарисуем карсивый график

In [13]:
import matplotlib.cm as cm

plt.figure(figsize=(16, 12))

# Города с тенью для объёма
plt.scatter(
    historic_large_cities["geo_lon"], 
    historic_large_cities["geo_lat"], 
    c="red", s=120, edgecolors='black', linewidth=0.7, alpha=0.9, zorder=5
)

# Цветовой градиент для маршрута (от начала к концу)
route_order = best.chromosome + [best.chromosome[0]]
route_lng = [historic_large_cities.iloc[i]["geo_lon"] for i in route_order]
route_lat = [historic_large_cities.iloc[i]["geo_lat"] for i in route_order]

colors = cm.viridis(np.linspace(0, 1, len(route_order)-1))
for i in range(len(route_order)-1):
    plt.plot(
        route_lng[i:i+2], route_lat[i:i+2], 
        color=colors[i], linewidth=3, alpha=0.8, zorder=4
    )

# Номера и подписи городов с фоном
for i, city_idx in enumerate(best.chromosome):
    city = historic_large_cities.iloc[city_idx]

    plt.text(
        city["geo_lon"], city["geo_lat"], str(i+1),
        fontsize=12, ha="center", va="center",
        bbox=dict(facecolor="white", alpha=0.8, boxstyle="circle"), zorder=6
    )

    plt.text(
        city["geo_lon"] + 0.6, city["geo_lat"] + 0.3,
        f'{city["address"]}',
        fontsize=10, ha="left", va="center",
        bbox=dict(facecolor="white", alpha=0.6, boxstyle="round,pad=0.3"), zorder=6
    )

# Настройки графика
plt.title(
    "Оптимальный маршрут коммивояжера через города России\n"
    f"Общая длина маршрута: {best._total_distance:.2f} км",
    fontsize=16, pad=20
)
plt.xlabel("Долгота", fontsize=14)
plt.ylabel("Широта", fontsize=14)
plt.grid(alpha=0.25)
plt.tight_layout()

# Легенда с кастомными маркерами
from matplotlib.lines import Line2D
legend_elements = [
    Line2D([0], [0], marker='o', color='w', label='Города', markerfacecolor='red', markersize=12, markeredgecolor='black'),
    Line2D([0], [0], color=cm.viridis(0.6), lw=3, label='Маршрут')
]
plt.legend(handles=legend_elements, loc='upper right', fontsize=12)

plt.show()
No description has been provided for this image

Можно вывести пусть в строке GA

In [14]:
def print_city_route(best_chromosome, city_df):
    """Печатает маршрут обхода городов с направлением.
        best_chromosome: список индексов городов (например, best.chromosome)
        city_df: DataFrame с данными городов (например, historic_large_cities)
    """
    print("Маршрут коммивояжера:\n")
    
    for i in range(len(best_chromosome)):
        city_idx = best_chromosome[i]
        city_name = city_df.iloc[city_idx]["address"]
        print(f"{i + 1}. {city_name}")
        if i < len(best_chromosome) - 1:
            print("↓")
    
    # Возвращение в начальный город
    start_city = city_df.iloc[best_chromosome[0]]["address"]
    print("↓")
    print(f"{len(best_chromosome) + 1}. {start_city} (возвращение в начальный город)")

print_city_route(best.chromosome, historic_large_cities)
Маршрут коммивояжера:

1. г Тула
↓
2. г Санкт-Петербург
↓
3. г Ярославль
↓
4. г Нижний Новгород
↓
5. г Пермь
↓
6. г Самара
↓
7. г Уфа
↓
8. г Оренбург
↓
9. г Волгоград
↓
10. г Краснодар
↓
11. г Ростов-на-Дону
↓
12. г Астрахань
↓
13. г Ижевск
↓
14. г Казань
↓
15. г Ульяновск
↓
16. г Саратов
↓
17. г Пенза
↓
18. г Рязань
↓
19. г Москва
↓
20. г Липецк
↓
21. г Воронеж
↓
22. г Тула (возвращение в начальный город)