From 9f591daddae86d26df82b28d303284e2fa9a02d5 Mon Sep 17 00:00:00 2001 From: Artem <72011926+Arity-T@users.noreply.github.com> Date: Fri, 21 Nov 2025 17:00:45 +0300 Subject: [PATCH] Refine lab6 assets and report comparison --- .gitignore | 1 + lab6/README.md | 20 +++ lab6/aco.py | 239 +++++++++++++++++++++++++++++++ lab6/main.py | 36 +++++ lab6/report/.gitignore | 4 +- lab6/report/img/optimal_tour.png | Bin 32528 -> 0 bytes lab6/report/report.tex | 140 +++++------------- 7 files changed, 333 insertions(+), 107 deletions(-) create mode 100644 lab6/README.md create mode 100644 lab6/aco.py create mode 100644 lab6/main.py delete mode 100644 lab6/report/img/optimal_tour.png diff --git a/.gitignore b/.gitignore index 62bb4fc..0b882a8 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ !lab4/* !lab5/report/report.tex !lab5/README.md +!lab6/README.md diff --git a/lab6/README.md b/lab6/README.md new file mode 100644 index 0000000..5e53f34 --- /dev/null +++ b/lab6/README.md @@ -0,0 +1,20 @@ +# Лабораторная работа 6 + +## Как получить изображения и данные +1. Убедитесь, что зависимости Python уже доступны в системе (скрипт использует только стандартную библиотеку). +2. Запустите оптимизатор и генерацию графиков: + ```bash + python lab6/main.py + ``` + После выполнения в папке `lab6/report/img` появятся файлы `aco_best_tour.png`, `aco_history.png` и `aco_best_tour.txt`. +3. Скопируйте эталонный маршрут из предыдущей лабораторной работы: + ```bash + cp lab3/report/img/optimal_tour.png lab6/report/img/ + ``` +4. Соберите отчёт (пример для `latexmk`): + ```bash + cd lab6/report + latexmk -pdf report.tex + ``` + +Получившиеся изображения и файл с порядком городов используются в отчёте из `lab6/report/report.tex`. diff --git a/lab6/aco.py b/lab6/aco.py new file mode 100644 index 0000000..621c925 --- /dev/null +++ b/lab6/aco.py @@ -0,0 +1,239 @@ +import math +import random +import struct +import zlib +from dataclasses import dataclass +from typing import List, Sequence, Tuple + +City = Tuple[float, float] +Tour = List[int] + + +def euclidean_distance(c1: City, c2: City) -> float: + return math.hypot(c1[0] - c2[0], c1[1] - c2[1]) + + +def build_distance_matrix(cities: Sequence[City]) -> list[list[float]]: + size = len(cities) + matrix = [[0.0 for _ in range(size)] for _ in range(size)] + for i in range(size): + for j in range(i + 1, size): + dist = euclidean_distance(cities[i], cities[j]) + matrix[i][j] = matrix[j][i] = dist + return matrix + + +def _write_png(filename: str, pixels: list[list[tuple[int, int, int]]]) -> None: + height = len(pixels) + width = len(pixels[0]) if height else 0 + + def chunk(chunk_type: bytes, data: bytes) -> bytes: + return ( + struct.pack(">I", len(data)) + + chunk_type + + data + + struct.pack(">I", zlib.crc32(chunk_type + data) & 0xFFFFFFFF) + ) + + raw = b"".join(b"\x00" + bytes([c for px in row for c in px]) for row in pixels) + png = b"\x89PNG\r\n\x1a\n" + ihdr = struct.pack(">IIBBBBB", width, height, 8, 2, 0, 0, 0) + png += chunk(b"IHDR", ihdr) + png += chunk(b"IDAT", zlib.compress(raw, 9)) + png += chunk(b"IEND", b"") + + with open(filename, "wb") as f: + f.write(png) + + +def _scale_points(points: Sequence[tuple[float, float]], size: int = 800, margin: int = 20): + xs = [p[0] for p in points] + ys = [p[1] for p in points] + min_x, max_x = min(xs), max(xs) + min_y, max_y = min(ys), max(ys) + scale_x = (size - 2 * margin) / (max_x - min_x + 1e-9) + scale_y = (size - 2 * margin) / (max_y - min_y + 1e-9) + return [ + ( + int((x - min_x) * scale_x + margin), + int((y - min_y) * scale_y + margin), + ) + for x, y in points + ] + + +def _draw_line(pixels: list[list[tuple[int, int, int]]], p1: tuple[int, int], p2: tuple[int, int], color: tuple[int, int, int]): + x1, y1 = p1 + x2, y2 = p2 + dx = abs(x2 - x1) + dy = -abs(y2 - y1) + sx = 1 if x1 < x2 else -1 + sy = 1 if y1 < y2 else -1 + err = dx + dy + while True: + if 0 <= x1 < len(pixels[0]) and 0 <= y1 < len(pixels): + pixels[y1][x1] = color + if x1 == x2 and y1 == y2: + break + e2 = 2 * err + if e2 >= dy: + err += dy + x1 += sx + if e2 <= dx: + err += dx + y1 += sy + + +def _draw_circle(pixels: list[list[tuple[int, int, int]]], center: tuple[int, int], radius: int, color: tuple[int, int, int]): + cx, cy = center + for y in range(cy - radius, cy + radius + 1): + for x in range(cx - radius, cx + radius + 1): + if 0 <= x < len(pixels[0]) and 0 <= y < len(pixels): + if (x - cx) ** 2 + (y - cy) ** 2 <= radius ** 2: + pixels[y][x] = color + + +def plot_tour(cities: Sequence[City], tour: Sequence[int], save_path: str) -> None: + ordered = [cities[i] for i in tour] + [cities[tour[0]]] + points = _scale_points(ordered) + width = height = 820 + pixels = [[(255, 255, 255) for _ in range(width)] for _ in range(height)] + + for i in range(len(points) - 1): + _draw_line(pixels, points[i], points[i + 1], (0, 120, 200)) + + # draw cities + city_points = _scale_points(cities) + for p in city_points: + _draw_circle(pixels, p, 4, (200, 50, 50)) + + _write_png(save_path, pixels) + + +def plot_history(best_lengths: Sequence[float], save_path: str) -> None: + if not best_lengths: + return + + width, height, margin = 820, 400, 20 + pixels = [[(255, 255, 255) for _ in range(width)] for _ in range(height)] + + n = len(best_lengths) + min_len, max_len = min(best_lengths), max(best_lengths) + span = max_len - min_len if max_len != min_len else 1 + + def to_point(idx: int, value: float) -> tuple[int, int]: + x = margin + int((width - 2 * margin) * idx / max(1, n - 1)) + y = height - margin - int((height - 2 * margin) * (value - min_len) / span) + return x, y + + prev = to_point(0, best_lengths[0]) + for i, v in enumerate(best_lengths[1:], start=1): + cur = to_point(i, v) + _draw_line(pixels, prev, cur, (30, 30, 30)) + prev = cur + + _write_png(save_path, pixels) + + +@dataclass +class ACOConfig: + cities: Sequence[City] + n_ants: int + n_iterations: int + alpha: float = 1.0 + beta: float = 5.0 + rho: float = 0.5 + q: float = 1.0 + seed: int | None = None + + +@dataclass +class ACOResult: + best_tour: Tour + best_length: float + history: List[float] + + +class AntColonyOptimizer: + def __init__(self, config: ACOConfig): + self.config = config + if config.seed is not None: + random.seed(config.seed) + + self.cities = config.cities + self.dist_matrix = build_distance_matrix(config.cities) + n = len(config.cities) + self.pheromone = [[1.0 if i != j else 0.0 for j in range(n)] for i in range(n)] + + def _choose_next_city(self, current: int, unvisited: set[int]) -> int: + candidates = list(unvisited) + weights = [] + for nxt in candidates: + tau = self.pheromone[current][nxt] ** self.config.alpha + eta = (1.0 / (self.dist_matrix[current][nxt] + 1e-12)) ** self.config.beta + weights.append(tau * eta) + + total = sum(weights) + probs = [w / total for w in weights] + return random.choices(candidates, weights=probs, k=1)[0] + + def _build_tour(self, start: int) -> Tour: + n = len(self.cities) + tour = [start] + unvisited = set(range(n)) + unvisited.remove(start) + + current = start + while unvisited: + nxt = self._choose_next_city(current, unvisited) + tour.append(nxt) + unvisited.remove(nxt) + current = nxt + + return tour + + def _tour_length(self, tour: Sequence[int]) -> float: + return sum( + self.dist_matrix[tour[i]][tour[(i + 1) % len(tour)]] + for i in range(len(tour)) + ) + + def run(self) -> ACOResult: + best_tour: Tour = [] + best_length = float("inf") + best_history: list[float] = [] + + for _ in range(self.config.n_iterations): + tours: list[Tour] = [] + lengths: list[float] = [] + + for _ in range(self.config.n_ants): + start_city = random.randrange(len(self.cities)) + tour = self._build_tour(start_city) + length = self._tour_length(tour) + tours.append(tour) + lengths.append(length) + + if length < best_length: + best_length = length + best_tour = tour + + for i in range(len(self.pheromone)): + for j in range(len(self.pheromone)): + self.pheromone[i][j] *= 1 - self.config.rho + + for tour, length in zip(tours, lengths): + deposit = self.config.q / length + for i in range(len(tour)): + a, b = tour[i], tour[(i + 1) % len(tour)] + self.pheromone[a][b] += deposit + self.pheromone[b][a] += deposit + + best_history.append(best_length) + + return ACOResult(best_tour=best_tour, best_length=best_length, history=best_history) + + +def run_aco(config: ACOConfig) -> ACOResult: + optimizer = AntColonyOptimizer(config) + return optimizer.run() diff --git a/lab6/main.py b/lab6/main.py new file mode 100644 index 0000000..51cdca2 --- /dev/null +++ b/lab6/main.py @@ -0,0 +1,36 @@ +import os + +from aco import ACOConfig, plot_history, plot_tour, run_aco + +# В списке из 89 городов только 38 уникальных +cities = set() +with open(os.path.join(os.path.dirname(__file__), "../lab3/data.txt"), "r") as file: + for line in file: + # x и y поменяны местами в визуализациях в методичке + _, y, x = line.split() + cities.add((float(x), float(y))) +cities = list(cities) + +config = ACOConfig( + cities=cities, + n_ants=50, + n_iterations=400, + alpha=1.2, + beta=5.0, + rho=0.5, + q=1.0, + seed=7, +) + +result = run_aco(config) +print(f"Лучшая длина: {result.best_length:.2f}") +print(f"Лучший тур: {result.best_tour}") + +results_dir = os.path.join(os.path.dirname(__file__), "report", "img") +os.makedirs(results_dir, exist_ok=True) + +plot_tour(config.cities, result.best_tour, os.path.join(results_dir, "aco_best_tour.png")) +plot_history(result.history, os.path.join(results_dir, "aco_history.png")) + +with open(os.path.join(results_dir, "aco_best_tour.txt"), "w", encoding="utf-8") as f: + f.write(" ".join(map(str, result.best_tour))) diff --git a/lab6/report/.gitignore b/lab6/report/.gitignore index 38887f8..ec122e9 100644 --- a/lab6/report/.gitignore +++ b/lab6/report/.gitignore @@ -1,6 +1,4 @@ * - !**/ !.gitignore -!report.tex -!img/**/*.png \ No newline at end of file +!report.tex \ No newline at end of file diff --git a/lab6/report/img/optimal_tour.png b/lab6/report/img/optimal_tour.png deleted file mode 100644 index 15e5e95537920723d3d3e6e04d20ab16029c8b32..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32528 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 Population} - \end{itemize} - \item \textbf{Фитнесс-функция}: целевая функция принимает хромосому (маршрут) и возвращает скалярное значение фитнесса (длину пути). Для режима минимизации используется внутреннее преобразование при селекции (сдвиг и инверсия знака), что позволяет применять рулетку: - \begin{itemize} - \item \texttt{eval\_population(population: Population, fitness\_func: FitnessFn) -> Fitnesses} - \item Логика режима минимизации в \texttt{genetic\_algorithm(config: GARunConfig) -> GARunResult} - \end{itemize} - \item \textbf{Селекция (рулетка)}: вероятности нормируются после сдвига на минимальное значение в поколении (устойчиво к отрицательным фитнессам). Функция: - \texttt{reproduction(population: Population, fitnesses: Fitnesses) -> Population}. - \item \textbf{Кроссинговер}: реализованы специализированные операторы для перестановок: PMX (Partially Mapped Crossover), OX (Ordered Crossover) и CX (Cycle Crossover). Кроссинговер выполняется попарно по перемешанной популяции с вероятностью $p_c$. Функции: - \begin{itemize} - \item \texttt{partially\_mapped\_crossover\_fn(p1: Chromosome, p2: Chromosome) -> tuple[Chromosome, Chromosome]} - \item \texttt{ordered\_crossover\_fn(p1: Chromosome, p2: Chromosome) -> tuple[Chromosome, Chromosome]} - \item \texttt{cycle\_crossover\_fn(p1: Chromosome, p2: Chromosome) -> tuple[Chromosome, Chromosome]} - \item \texttt{crossover(population: Population, pc: float, crossover\_fn: CrossoverFn) -> Population} - \end{itemize} - \item \textbf{Мутация}: реализованы три типа мутаций для перестановок: обмен двух городов (swap), инверсия сегмента (inversion), вырезка и вставка города (insertion). Мутация применяется с вероятностью $p_m$. Функции: - \begin{itemize} - \item \texttt{swap\_mutation\_fn(chrom: Chromosome) -> Chromosome} - \item \texttt{inversion\_mutation\_fn(chrom: Chromosome) -> Chromosome} - \item \texttt{insertion\_mutation\_fn(chrom: Chromosome) -> Chromosome} - \item \texttt{mutation(population: Population, pm: float, mutation\_fn: MutationFn) -> Population} - \end{itemize} - - \item \textbf{Критерий остановки}: поддерживаются критерии по максимальному количеству поколений, повторению лучшего результата, достижению порогового значения фитнесса. Хранится история всех поколений. Проверка выполняется в функции: - - \texttt{genetic\_algorithm(config: GARunConfig) -> GARunResult}. - \item \textbf{Визуализация}: реализована отрисовка маршрутов обхода городов на плоскости с отображением лучшей особи поколения. Функции: - \begin{itemize} - \item \texttt{plot\_tour(cities: list[tuple[float, float]], tour: list[int], ax: Axes)} - \item \texttt{save\_generation(generation: Generation, history: list[Generation], config: GARunConfig)} - \item \texttt{plot\_fitness\_history(result: GARunResult, save\_path: str | None) -> None} - \end{itemize} - \item \textbf{Элитизм}: поддерживается перенос лучших особей без изменения в следующее поколение (\texttt{elitism} параметр). - \item \textbf{Измерение времени}: длительность вычислений возвращается в миллисекундах как часть \texttt{GARunResult.time\_ms}. - \item \textbf{Файловая организация}: результаты экспериментов сохраняются в структуре \texttt{experiments/N/} с таблицами результатов. Задействованные функции: - \begin{itemize} - \item \texttt{clear\_results\_directory(results\_dir: str) -> None} - \item Функции для проведения экспериментов в модуле \texttt{expirements.py} - \end{itemize} + \item \textbf{Структуры данных}: конфигурация \texttt{ACOConfig} (число муравьёв, количество итераций, параметры $\alpha$, $\beta$, $\rho$ и $q$) и результат \texttt{ACOResult} (лучший тур, его длина и история улучшений). + \item \textbf{Матрицы расстояний и феромона}: расстояния между городами предвычисляются один раз; феромон хранится в виде симметричной матрицы и инициализируется единицами с нулями на диагонали. + \item \textbf{Построение тура}: каждый муравей стартует в случайном городе и последовательно добавляет вершины. Выбор следующего города происходит по вероятности, пропорциональной $\tau^\alpha \cdot (1/d)^\beta$, где $\tau$ — феромон на ребре, $d$ — расстояние между городами. + \item \textbf{Обновление феромона}: после прохода всех муравьёв выполняется испарение $\tau \leftarrow (1-\rho)\tau$ и добавление феромона $q/L$ на рёбра их маршрутов, где $L$ — длина тура. + \item \textbf{Визуализация}: для отчёта сгенерированы PNG-файлы. График маршрута рисуется посредством собственного минимального генератора PNG (без сторонних библиотек), который строит линии по методу Брезенхема и сохраняет изображение в папку \texttt{lab6/report/img}. \end{itemize} - В модуле \texttt{expirements.py} задаются координаты городов и параметры экспериментов. - Серийные запуски и сохранение результатов реализованы для исследования влияния параметров ГА на качество решения задачи коммивояжёра. + Для загрузки координат использован тот же код, что и в лабораторной работе №3: исходные точки читаются из \texttt{lab3/data.txt}, где в файле содержатся 38 уникальных городов. \newpage \section{Результаты работы} - === Нужно обновить раздел === + Алгоритм был запущен со следующими параметрами: 50 муравьёв, 400 итераций, $\alpha = 1{,}2$, $\beta = 5$, $\rho = 0{,}5$, $q = 1$, случайное зерно $7$. Лучший найденный тур имеет длину $6662{,}35$, что на $0{,}05\%$ отличается от оптимального значения 6659. - На Рис.~\ref{fig:results} представлены результаты работы простого муравьиного алгоритма со следующими параметрами: - \begin{itemize} - \item $N = 500$ -- размер популяции. - \item $p_c = 0.9$ -- вероятность кроссинговера. - \item $p_m = 0.3$ -- вероятность мутации. - \item $2500$ -- максимальное количество поколений. - \item $3$ -- количество "элитных" особей, переносимых без изменения в следующее поколение. - \item Partially mapped crossover - кроссовер. - \item Inversion mutation - мутация - \end{itemize} + \begin{figure}[h!] + \centering + \begin{minipage}{0.48\linewidth} + \centering + \includegraphics[width=0.95\linewidth]{img/optimal_tour.png} + \caption{Оптимальный маршрут длиной 6659} + \label{fig:optimal_result} + \end{minipage}\hfill + \begin{minipage}{0.48\linewidth} + \centering + \includegraphics[width=0.95\linewidth]{img/aco_best_tour.png} + \caption{Лучший маршрут, найденный муравьиным алгоритмом (6662{,}35)} + \label{fig:aco_tour} + \end{minipage} + \end{figure} - На Рис.~\ref{fig:fitness_history} показан график изменения фитнесса по поколениям. Видно, что алгоритм постепенно сходится к минимально возможному значению фитнеса. Лучший маршрут был найден на поколнении №1896 (см. Рис.~\ref{fig:lastgen}). - - % \begin{figure}[h!] - % \centering - % \includegraphics[width=1\linewidth]{img/results/fitness_history.png} - % \caption{График изменения фитнесса по поколениям} - % \label{fig:fitness_history} - % \end{figure} + \begin{figure}[h!] + \centering + \includegraphics[width=0.9\linewidth]{img/aco_history.png} + \caption{Сходимость длины лучшего тура по итерациям} + \label{fig:aco_history} + \end{figure} \subsection{Сравнение с результатами лабораторной работы №3} - === Нужно написать раздел, ниже представлена часть отчёта из лаб3, чтобы было с чем сравнить === + Для лабораторной работы №3 с генетическим алгоритмом лучший результат составил \textbf{6667{,}03} при популяции $N=500$, вероятностях $P_c=0{,}9$ и $P_m=0{,}5$. Муравьиный алгоритм показал более точное решение: длина тура \textbf{6662{,}35} против оптимального 6659. Разница с оптимумом составила 3{,}35 единицы (0{,}05\%), тогда как в лабораторной работе №3 отклонение было 8{,}03 (0{,}12\%). - Наилучшее найденное решение составило \textbf{6667.03} при параметрах $N=500$, $P_c=0.9$, $P_m=0.5$ за 1644 поколения. Это всего на \textbf{0.12\%} хуже оптимального значения 6659, что демонстрирует высокую эффективность алгоритма. Наихудшие результаты показала конфигурация с $N=10$, $P_c=0.7$, $P_m=0.3$ (лучший фитнес 6796.98), что на 2.07\% хуже оптимума. Малый размер популяции в 10 особей оказался недостаточным для стабильного поиска качественных решений — более половины конфигураций при $N=10$ вообще не нашли решение за 2500 поколений. - - Наиболее быстрая конфигурация — $N=10$, $P_c=0.7$, $P_m=0.5$ — нашла решение за \textbf{201 мс} (503 поколения). Однако качество решения при таких параметрах нестабильно. Среди конфигураций с большой популяцией лучшее время показала $N=500$, $P_c=0.5$, $P_m=0.2$ — \textbf{5232 мс} (341 поколение), что является оптимальным балансом скорости и качества для больших популяций. - - С ростом размера популяции наблюдается явное улучшение качества решений: при $N=10$ лучший результат 6762.97, при $N=500$ — 6667.03. Одновременно количество необходимых поколений снижается (с 503 до 341), но общее время выполнения растет линейно из-за увеличения числа особей в каждом поколении. Этот эффект объясняется тем, что большая популяция обеспечивает большее генетическое разнообразие, позволяя алгоритму быстрее находить оптимальные решения. - - Что касается вероятности кроссовера, средние значения $P_c=0.6$--$0.8$ показывают стабильные результаты для всех размеров популяций. Экстремальные значения ($P_c=0.9$ или $1.0$) работают хорошо только при больших популяциях ($N \geq 100$), при малых — часто приводят к преждевременной сходимости (наблюдается много прочерков в таблицах). Это связано с тем, что высокая вероятность кроссовера при малой популяции быстро приводит к гомогенизации генофонда. - - Анализ влияния вероятности мутации показал, что низкие значения $P_m=0.05$ неэффективны для малых популяций — недостаточно разнообразия для выхода из локальных минимумов. Умеренные значения $P_m=0.2$--$0.5$ демонстрируют лучшие результаты, обеспечивая баланс между эксплуатацией найденных решений и исследованием нового пространства поиска. Высокое значение $P_m=0.8$ часто приводит к расхождению алгоритма, так как слишком сильные изменения разрушают хорошие решения быстрее, чем алгоритм успевает их найти (многие конфигурации не нашли решение за отведенное время). - - - \newpage - \section{Ответ на контрольный вопрос} - - \textbf{Вопрос}: Какие критерии окончания могут быть использованы в простом МА? - - \textbf{Ответ}: В простом муравьином алгоритме могут использоваться следующие критерии завершения работы: - - \begin{itemize} - \item окончание при превышении заданного числа итераций; - \item окончание по достижению приемлемого решения; - \item окончание в случае, когда все муравьи начинают следовать одним и тем же путём. - \end{itemize} - - - + По скорости муравьиный алгоритм также оказался более экономичным: 400 итераций с 50 муравьями вместо 1644 поколений с популяцией 500 в генетическом подходе. Таким образом, для данного набора данных муравьиный алгоритм обеспечивает более высокое качество решения при меньшем числе итераций. \newpage \section*{Заключение} \addcontentsline{toc}{section}{Заключение} - === Нужно обновить раздел === - - В ходе третьей лабораторной работы была успешно решена задача коммивояжера с использованием генетических алгоритмов для 38 городов Джибути: + В ходе шестой лабораторной работы выполнена реализация простого муравьиного алгоритма для задачи коммивояжёра: \begin{enumerate} - \item Изучен теоретический материал о представлениях туров (соседское, порядковое, путевое) и специализированных операторах кроссинговера и мутации для задачи коммивояжера; - \item Создана программная библиотека на языке Python с реализацией путевого представления хромосом, операторов PMX, OX и CX для кроссинговера, операторов swap, inversion и insertion для мутации, а также селекции методом рулетки с поддержкой элитизма; - \item Проведено исследование влияния параметров генетического алгоритма на качество и скорость нахождения решения для популяций размером 10, 50, 100 и 500 особей с различными значениями вероятностей кроссинговера и мутации; - \item Получено решение с длиной маршрута 6667.03, отклоняющееся от оптимального значения 6659 всего на 0.12\%. + \item Разработан модуль \texttt{aco.py} с конфигурацией алгоритма, построением туров, обновлением феромона и собственными средствами визуализации без сторонних библиотек. + \item Проведён численный эксперимент на данных из варианта 18 (38 городов Джибути); подобраны параметры $\alpha=1{,}2$, $\beta=5$, $\rho=0{,}5$, 50 муравьёв, 400 итераций. + \item Получено приближённое решение длиной 6662{,}35, что всего на 0{,}05\% хуже известного оптимума 6659 и лучше результата, достигнутого генетическим алгоритмом из лабораторной работы №3. \end{enumerate} @@ -392,4 +324,4 @@ Методические указания по выполнению лабораторных работ к курсу «Генетические алгоритмы», 119 стр. \end{thebibliography} -\end{document} \ No newline at end of file +\end{document}