laba 2
.docxМИНИСТЕРСТВО ЦИФРОВОГО РАЗВИТИЯ, СВЯЗИ И МАССОВЫХ КОММУНИКАЦИЙ РОССИЙСКОЙ ФЕДЕРАЦИИ
ФЕДЕРАЛЬНОЕ ГОСУДАРСТВЕННОЕ БЮДЖЕТНОЕ ОБРАЗОВАТЕЛЬНОЕ УЧРЕЖДЕНИЕ ВЫСШЕГО ОБРАЗОВАНИЯ
«САНКТ-ПЕТЕРБУРГСКИЙ ГОСУДАРСТВЕННЫЙ УНИВЕРСИТЕТ ТЕЛЕКОММУНИКАЦИЙ ИМ.ПРОФ.М.А.БОНЧ-БРУЕВИЧА»
(СПбГУТ)
Факультет: «Институт магистратуры»
Кафедра: «Систем автоматизации и робототехники»
Направление подготовки: |
Автоматизация технологических процессов и производств |
Направленность (профиль): |
Интеллектуальные технологии в автоматизации |
ЛАБОРАТОРНАЯ РАБОТА № 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
