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

МИНИСТЕРСТВО ЦИФРОВОГО РАЗВИТИЯ, СВЯЗИ И МАССОВЫХ КОММУНИКАЦИЙ РОССИЙСКОЙ ФЕДЕРАЦИИ

ФЕДЕРАЛЬНОЕ ГОСУДАРСТВЕННОЕ БЮДЖЕТНОЕ ОБРАЗОВАТЕЛЬНОЕ УЧРЕЖДЕНИЕ ВЫСШЕГО ОБРАЗОВАНИЯ

«САНКТ-ПЕТЕРБУРГСКИЙ ГОСУДАРСТВЕННЫЙ УНИВЕРСИТЕТ ТЕЛЕКОММУНИКАЦИЙ ИМ.ПРОФ.М.А.БОНЧ-БРУЕВИЧА»

(СПбГУТ)

Факультет: «Институт магистратуры»

Кафедра: «Систем автоматизации и робототехники»

Направление подготовки:

Автоматизация технологических процессов и производств

Направленность (профиль):

Интеллектуальные технологии в автоматизации

ЛАБОРАТОРНАЯ РАБОТА № 2

по дисциплине:

Автоматизированное проектирование средств и систем управления

на тему:

Нахождение оптимального пути

Выполнили студенты группы:

дата, подпись

Фамилия И. О.

Принял к.т.н., доцент

Чебыкин В.А.

дата, подпись

Фамилия И. О.

Задание: Реализовать оптимальное нахождение пути

Ход работы

Программный код с выполненными заданиями:

using System;

using System.Collections.Generic;

using System.Drawing;

using System.Windows.Forms;

namespace Osipenko_front

{

public partial class Form1 : Form

{

// Класс для хранения глобальных переменных (начальные и конечные координаты, флаги и т.д.)

public static class S

{

public static int start1 = 0; // Начальный столбец

public static int start2 = 0; // Начальная строка

public static int finish1 = 0; // Финишный столбец

public static int finish2 = 0; // Финишная строка

public static int front = 1;

public static int off = 0;

public static int frontadd = 0;

public static FieldCell fs2; // Хранит ячейку, в которой обнаружен финиш

}

// Класс для описания ячейки поля с индексами строки (i) и столбца (k)

public class FieldCell

{

public int i, k;

public FieldCell(int i, int k)

{

this.i = i;

this.k = k;

}

}

// ------------------------------------------------------------------------

// Новое улучшение: поиск пути с минимальным количеством поворотов

// ------------------------------------------------------------------------

// Перечисление направлений для движения

public enum Direction

{

None, // Не задано (начальная точка)

Left,

Right,

Up,

Down

}

// Класс, описывающий узел пути с сохранением позиции, направления, числа поворотов и ссылки на родительский узел

public class PathNode

{

public int I { get; set; } // Индекс строки

public int J { get; set; } // Индекс столбца

public Direction Dir { get; set; } // Направление, из которого пришли в эту ячейку

public int Turns { get; set; } // Количество совершённых поворотов до этой ячейки

public PathNode Parent { get; set; } // Родительский узел для восстановления пути

public PathNode(int i, int j, Direction dir, int turns, PathNode parent = null)

{

I = i;

J = j;

Dir = dir;

Turns = turns;

Parent = parent;

}

}

// ------------------------------------------------------------------------

// Остальной код – для создания поля, отрисовки и базового поиска (обхода волнами)

// ------------------------------------------------------------------------

// Списки для хранения "фронтов" в алгоритме обхода (используются в базовой реализации)

public List<FieldCell> Front1 = new List<FieldCell>();

public List<FieldCell> Front2 = new List<FieldCell>();

// Списки для хранения точек маршрута, массива маршрутов и точек преград (для отрисовки)

List<Point> LineArr = new List<Point>(); // Точки маршрута

List<List<Point>> Lines = new List<List<Point>>(); // Массив маршрутов

List<Point> PrArr = new List<Point>(); // Точки для рамки преград

List<Point> PrArr2 = new List<Point>(); // Точки преград для отрисовки

List<List<Point>> Lines2 = new List<List<Point>>(); // Массив массивов точек преград

bool knopka = false; // Флаг для отрисовки преград

public Form1()

{

InitializeComponent();

// Создаём 22 столбца для DataGridView "Pole"

for (int col = 1; col <= 22; col++)

{

Pole.Columns.Add(col.ToString(), " ");

}

// Добавляем 17 строк (индексы 0..16)

Pole.Rows.Add(17);

// Инициализация списков

Front1.Clear();

Front2.Clear();

// Инициализация поля с препятствиями и свободными клетками

NewPole();

// Установка начальной и конечной точки

AddStart();

AddFinish();

// Добавляем стартовую ячейку в начальный фронт для дальнейшего поиска

Front1.Add(new FieldCell(S.start2, S.start1));

}

/*

Инициализация поля: заполнение массива, установка препятствий и границ

*/

public void NewPole()

{

// Создаем массив для хранения значений клеток

string[,] mainArray = new string[17, 22];

for (int i = 0; i < 17; i++)

{

for (int j = 0; j < 22; j++)

{

// Расставляем препятствия через каждые 2 строки и 3 столбца (исключая границы)

if ((i % 2 == 0) && (j % 3 == 0) && i != 0 && j != 0 && j != 21)

{

mainArray[i, j] = " o";

PrArr2.Add(new Point(i, j)); // Запоминаем точку для отрисовки препятствия

Pole.Rows[i].Cells[j].Style.BackColor = System.Drawing.Color.Gray;

}

else

{

mainArray[i, j] = "";

}

Pole.Rows[i].Cells[j].Value = mainArray[i, j];

}

}

// Устанавливаем границы слева и справа как препятствия

for (int i = 0; i < 17; i++)

{

Pole.Rows[i].Cells[0].Value = " o";

Pole.Rows[i].Cells[21].Value = " o";

Pole.Rows[i].Cells[0].Style.BackColor = System.Drawing.Color.Black;

Pole.Rows[i].Cells[21].Style.BackColor = System.Drawing.Color.Black;

}

// Устанавливаем верхнюю и нижнюю границы как препятствия

for (int j = 0; j < 22; j++)

{

Pole.Rows[0].Cells[j].Value = " o";

Pole.Rows[16].Cells[j].Value = " o";

Pole.Rows[0].Cells[j].Style.BackColor = System.Drawing.Color.Black;

Pole.Rows[16].Cells[j].Style.BackColor = System.Drawing.Color.Black;

}

// Добавляем угловые точки для отрисовки рамки препятствий

PrArr.Add(new Point(0, 0));

PrArr.Add(new Point(0, 21));

PrArr.Add(new Point(16, 21));

PrArr.Add(new Point(16, 0));

PrArr.Add(new Point(0, 0));

}

/*

Случайное размещение стартовой точки (S) и финишной точки (F) на поле.

*/

public void AddStart()

{

Random rand = new Random();

int i = rand.Next(1, 21); // Столбец от 1 до 20

int j = rand.Next(1, 15); // Строка от 1 до 14

// Находим свободную клетку для старта

while ((Pole.Rows[j].Cells[i].Value.ToString() == "F") || (Pole.Rows[j].Cells[i].Value.ToString() != " o"))

{

i = rand.Next(1, 21);

j = rand.Next(1, 15);

}

// Обозначаем старт и красим его

Pole.Rows[j].Cells[i].Value = "S";

Pole.Rows[j].Cells[i].Style.BackColor = System.Drawing.Color.LightCoral;

S.start1 = i;

S.start2 = j;

// Размещаем финишную точку (F)

int i2 = rand.Next(1, 21);

int j2 = rand.Next(1, 15);

// Ищем свободную клетку для финиша (не совпадающую со стартом)

while ((Pole.Rows[j2].Cells[i2].Value.ToString() == "S") || (Pole.Rows[j2].Cells[i2].Value.ToString() != " o"))

{

i2 = rand.Next(1, 21);

j2 = rand.Next(1, 15);

}

// Обозначаем финиш и окрашиваем его

Pole.Rows[j2].Cells[i2].Value = "F";

Pole.Rows[j2].Cells[i2].Style.BackColor = System.Drawing.Color.LightBlue;

S.finish1 = i2;

S.finish2 = j2;

}

/*

Базовый метод расширения волны (legacy-реализация) – используется для пошагового поиска пути.

*/

public void AddFront()

{

for (int l = 0; l < Front1.Count; l++)

{

FieldCell fs = Front1[l];

// Проверка левой ячейки

if (Pole.Rows[fs.i].Cells[fs.k - 1].Value.ToString() != "F")

{

if ((fs.k - 1 > 0) && (fs.k - 1 < 21) && (Pole.Rows[fs.i].Cells[fs.k - 1].Value.ToString() == ""))

{

Pole.Rows[fs.i].Cells[fs.k - 1].Value = S.front.ToString();

Front2.Add(new FieldCell(fs.i, fs.k - 1));

}

}

else

{

S.fs2 = new FieldCell(fs.i, fs.k - 1);

LineArr.Add(new Point(fs.i, fs.k - 1));

S.off = 1;

break;

}

// Проверка правой ячейки

if (Pole.Rows[fs.i].Cells[fs.k + 1].Value.ToString() != "F")

{

if ((fs.k + 1 > 0) && (fs.k + 1 < 21) && (Pole.Rows[fs.i].Cells[fs.k + 1].Value.ToString() == ""))

{

Pole.Rows[fs.i].Cells[fs.k + 1].Value = S.front.ToString();

Front2.Add(new FieldCell(fs.i, fs.k + 1));

}

}

else

{

S.fs2 = new FieldCell(fs.i, fs.k + 1);

LineArr.Add(new Point(fs.i, fs.k + 1));

S.off = 1;

break;

}

// Проверка верхней ячейки

if (Pole.Rows[fs.i - 1].Cells[fs.k].Value.ToString() != "F")

{

if ((fs.i - 1 > 0) && (fs.i - 1 < 15) && (Pole.Rows[fs.i - 1].Cells[fs.k].Value.ToString() == ""))

{

Pole.Rows[fs.i - 1].Cells[fs.k].Value = S.front.ToString();

Front2.Add(new FieldCell(fs.i - 1, fs.k));

}

}

else

{

S.fs2 = new FieldCell(fs.i - 1, fs.k);

LineArr.Add(new Point(fs.i - 1, fs.k));

S.off = 1;

break;

}

// Проверка нижней ячейки

if (Pole.Rows[fs.i + 1].Cells[fs.k].Value.ToString() != "F")

{

if ((fs.i + 1 > 0) && (fs.i + 1 < 15) && (Pole.Rows[fs.i + 1].Cells[fs.k].Value.ToString() == ""))

{

Pole.Rows[fs.i + 1].Cells[fs.k].Value = S.front.ToString();

Front2.Add(new FieldCell(fs.i + 1, fs.k));

}

}

else

{

S.fs2 = new FieldCell(fs.i + 1, fs.k);

LineArr.Add(new Point(fs.i + 1, fs.k));

S.off = 1;

break;

}

}

}

// Обработчик события нажатия на кнопку, реализующий пошаговое расширение волны

private void button1_Click(object sender, EventArgs e)

{

if (S.off == 0)

{

AddFront();

Front1.Clear();

Front1.AddRange(Front2);

Front2.Clear();

if (S.front == 3)

{

S.front = 1;

}

else

{

S.front++;

}

}

else if (S.off == 1)

{

if (S.frontadd == 0)

{

if (S.front == 3)

{

S.front = 1;

}

else

{

S.front++;

}

S.frontadd = 1;

}

if ((S.fs2.k - 1 > 0) && (S.fs2.k - 1 < 21) && (Pole.Rows[S.fs2.i].Cells[S.fs2.k - 1].Value.ToString() == S.front.ToString()))

{

Pole.Rows[S.fs2.i].Cells[S.fs2.k - 1].Value = "X";

LineArr.Add(new Point(S.fs2.i, S.fs2.k - 1));

S.fs2.k = S.fs2.k - 1;

if (S.front == 1)

S.front = 3;

else

S.front--;

}

else if ((S.fs2.k + 1 > 0) && (S.fs2.k + 1 < 21) && (Pole.Rows[S.fs2.i].Cells[S.fs2.k + 1].Value.ToString() == S.front.ToString()))

{

Pole.Rows[S.fs2.i].Cells[S.fs2.k + 1].Value = "X";

LineArr.Add(new Point(S.fs2.i, S.fs2.k + 1));

S.fs2.k = S.fs2.k + 1;

if (S.front == 1)

S.front = 3;

else

S.front--;

}

else if ((S.fs2.i - 1 > 0) && (S.fs2.i - 1 < 15) && (Pole.Rows[S.fs2.i - 1].Cells[S.fs2.k].Value.ToString() == S.front.ToString()))

{

Pole.Rows[S.fs2.i - 1].Cells[S.fs2.k].Value = "X";

LineArr.Add(new Point(S.fs2.i - 1, S.fs2.k));

S.fs2.i = S.fs2.i - 1;

if (S.front == 1)

S.front = 3;

else

S.front--;

}

else if ((S.fs2.i + 1 > 0) && (S.fs2.i + 1 < 15) && (Pole.Rows[S.fs2.i + 1].Cells[S.fs2.k].Value.ToString() == S.front.ToString()))

{

Pole.Rows[S.fs2.i + 1].Cells[S.fs2.k].Value = "X";

LineArr.Add(new Point(S.fs2.i + 1, S.fs2.k));

S.fs2.i = S.fs2.i + 1;

if (S.front == 1)

S.front = 3;

else

S.front--;

}

else

{

// Если подходящая ячейка не найдена, очищаем поле, восстанавливаем старт и сохраняем маршрут.

for (int i = 0; i < 17; i++)

{

for (int j = 0; j < 22; j++)

{

if (Pole.Rows[i].Cells[j].Value.ToString() == "S")

{

Pole.Rows[i].Cells[j].Value = " o";

Pole.Rows[i].Cells[j].Style.BackColor = System.Drawing.Color.Black;

Pole.Rows[i].Cells[j].Style.ForeColor = System.Drawing.Color.LightCoral;

LineArr.Add(new Point(i, j));

}

else if (Pole.Rows[i].Cells[j].Value.ToString() == "F")

{

Pole.Rows[i].Cells[j].Value = " o";

Pole.Rows[i].Cells[j].Style.BackColor = System.Drawing.Color.Black;

Pole.Rows[i].Cells[j].Style.ForeColor = System.Drawing.Color.LightBlue;

}

else if (Pole.Rows[i].Cells[j].Value.ToString() == "X")

{

Pole.Rows[i].Cells[j].Value = "-";

Pole.Rows[i].Cells[j].Style.BackColor = System.Drawing.Color.Black;

Pole.Rows[i].Cells[j].Style.ForeColor = System.Drawing.Color.Black;

}

else if (Pole.Rows[i].Cells[j].Value.ToString() == "1" ||

Pole.Rows[i].Cells[j].Value.ToString() == "2" ||

Pole.Rows[i].Cells[j].Value.ToString() == "3")

{

Pole.Rows[i].Cells[j].Value = "";

}

}

}

List<Point> line = new List<Point>();

foreach (var pt in LineArr)

line.Add(pt);

Lines.Add(line);

LineArr.Clear();

List<Point> pregrada = new List<Point>();

foreach (var pt in PrArr)

pregrada.Add(pt);

Lines2.Add(pregrada);

PrArr.Clear();

// Перезапускаем алгоритм: ставим новую стартовую точку и очищаем "фронт"

AddStart();

Front1.Clear();

Front1.Add(new FieldCell(S.start2, S.start1));

S.front = 1;

S.off = 0;

S.frontadd = 0;

Invalidate();

}

}

}

// Обработчик события отрисовки формы: вызываем методы отрисовки маршрута и препятствий.

private void Form1_Paint(object sender, PaintEventArgs e)

{

drawTrace(groupBox1.Width, groupBox1.Height, groupBox1.Left, groupBox1.Right, e.Graphics);

if (knopka == true)

{

drawTrace2(groupBox1.Width, groupBox1.Height, groupBox1.Left, groupBox1.Right, e.Graphics);

}

}

// Отрисовка маршрута по сохранённым точкам.

private void drawTrace(int width, int height, int left, int right, Graphics gr)

{

int deltaX = 42; // Расстояние между столбцами

int deltaY = 24; // Расстояние между строками

int x0 = groupBox1.Left;

int y0 = groupBox1.Top;

int X, X1, Y, Y1;

// Проходим по каждому участку маршрута

foreach (var line in Lines)

{

for (int i = 1; i < line.Count; i++)

{

X = (line[i - 1].Y) * deltaX + x0;

Y = (line[i - 1].X) * deltaY + y0;

X1 = (line[i].Y) * deltaX + x0;

Y1 = (line[i].X) * deltaY + y0;

gr.DrawLine(new Pen(Brushes.RosyBrown, 5), X, Y, X1, Y1);

}

}

}

// Отрисовка препятствий и рамки вокруг них.

public void drawTrace2(int width, int height, int left, int right, Graphics gr)

{

int deltaX = 42;

int deltaY = 24;

int x0 = groupBox1.Left;

int y0 = groupBox1.Top;

int X, X1, Y, Y1;

// Отрисовка линий рамки препятствий

foreach (var line in Lines2)

{

for (int i = 1; i < line.Count; i++)

{

X = (line[i - 1].Y) * deltaX + x0;

Y = (line[i - 1].X) * deltaY + y0;

X1 = (line[i].Y) * deltaX + x0;

Y1 = (line[i].X) * deltaY + y0;

gr.DrawLine(new Pen(Brushes.SlateGray, 7), X, Y, X1, Y1);

}

}

// Отрисовка отдельных точек препятствий

foreach (var pt in PrArr2)

{

X = (pt.Y) * deltaX + x0 - 6;

Y = (pt.X) * deltaY + y0 - 6;

gr.DrawEllipse(new Pen(Brushes.SlateGray, 2), X, Y, 12, 12);

}

}

/ Обработчик нажатия на кнопку для переключения отображения препятствий.

private void Button2_Click(object sender, EventArgs e)

{

knopka = !knopka;

Invalidate();

}

/*-------------------------------------------------------------------------------------

Новый метод для поиска пути с минимальным количеством поворотов.

Используется поиск в ширину (BFS) с учетом направления движения.

-------------------------------------------------------------------------------------

Поиск пути от стартовой до финишной точки, минимизирующего количество поворотов.

Каждый узел хранит информацию о позиции, направлении движения и количестве поворотов.

<param name="startI">Столбец стартовой точки</param>

<param name="startJ">Строка стартовой точки</param>

<param name="finishI">Столбец финишной точки</param>

<param name="finishJ">Строка финишной точки</param>

<returns>Список точек, представляющих оптимальный путь</returns>*/

public List<Point> FindPathMinTurns(int startI, int startJ, int finishI, int finishJ)

{

int rows = 17; // Общее число строк

int cols = 22; // Общее число столбцов

// Словарь для хранения минимального количества поворотов, необходимых для достижения клетки (i, j) с заданным направлением.

Dictionary<(int, int, Direction), int> bestTurns = new Dictionary<(int, int, Direction), int>();

Queue<PathNode> queue = new Queue<PathNode>();

// Начинаем с точки старта, направление не задано (None) и 0 поворотов

PathNode startNode = new PathNode(startJ, startI, Direction.None, 0);

queue.Enqueue(startNode);

bestTurns[(startJ, startI, Direction.None)] = 0;

// Массив возможных перемещений: смещение по строке, смещение по столбцу и соответствующее направление

(int di, int dj, Direction dir)[] moves = new (int, int, Direction)[]

{

(0, -1, Direction.Left),

(0, 1, Direction.Right),

(-1, 0, Direction.Up),

(1, 0, Direction.Down)

};

PathNode finishNode = null;

// Основной цикл поиска

while (queue.Count > 0)

{

PathNode current = queue.Dequeue();

// Если достигли финишной точки, сохраняем узел и завершаем поиск

if (current.I == finishJ && current.J == finishI)

{

finishNode = current;

break;

}

// Перебираем все возможные перемещения

foreach (var move in moves)

{

int newI = current.I + move.di;

int newJ = current.J + move.dj;

// Проверка, что клетка находится в пределах поля и проходима (свободна или является финишной)

if (!IsInside(newI, newJ, rows, cols) || !IsCellFree(newI, newJ))

continue;

// Если направление меняется (и текущее не None), увеличиваем счетчик поворотов

int newTurns = current.Turns;

if (current.Dir != Direction.None && current.Dir != move.dir)

newTurns++;

var key = (newI, newJ, move.dir);

// Если для данной клетки с этим направлением найден путь с большим количеством поворотов,

// или такой путь еще не найден, сохраняем новый вариант

if (!bestTurns.ContainsKey(key) || bestTurns[key] > newTurns)

{

bestTurns[key] = newTurns;

PathNode nextNode = new PathNode(newI, newJ, move.dir, newTurns, current);

queue.Enqueue(nextNode);

}

}

}

// Восстанавливаем путь, двигаясь от финиша к старту через родительские ссылки

List<Point> path = new List<Point>();

if (finishNode != null)

{

PathNode node = finishNode;

while (node != null)

{

// Преобразуем координаты: (столбец, строка)

path.Add(new Point(node.J, node.I));

node = node.Parent;

}

path.Reverse();

}

return path;

}

/ *

Проверка, находится ли клетка (i, j) внутри границ поля.

*/

private bool IsInside(int i, int j, int rows, int cols)

{

return (i >= 0 && i < rows && j >= 0 && j < cols);

}

/*

Проверка проходимости клетки (i, j).

Клетка считается проходимой, если она пустая (" o") или является финишной ("F").*/

private bool IsCellFree(int i, int j)

{

string cellVal = Pole.Rows[i].Cells[j].Value?.ToString();

return (cellVal == " o" || cellVal == "F");

}

}

}

Результат компиляции:

Вывод: Мы научились оптимизировать пути

Санкт Петербург

2025

Соседние файлы в предмете Автоматизация проектирования систем и средств управления