Лабы по ПиАА 2024 / Baraeva_Elizaveta_lb1
.docМИНОБРНАУКИ РОССИИ
Санкт-Петербургский государственный
электротехнический университет
«ЛЭТИ» им. В. И. Ульянова (Ленина)
Кафедра МО ЭВМ
отчет
по лабораторной работе №1
по дисциплине «Построение и анализ алгоритмов»
Тема: Поиск с возвратом
Студентка гр. 2383 |
|
Бараева Е. Н. |
Преподаватель |
|
Шевелева А. М. |
Санкт-Петербург
2024
Цель работы.
Изучить информацию об алгоритме поиска с возвратом. Решить поставленную задачу, используя алгоритм поиска с возвратом, на языке C++.
Задание.
У Вовы много квадратных обрезков доски. Их стороны (размер) изменяются от 1 до N−1, и у него есть неограниченное число обрезков любого размера. Но ему очень хочется получить большую столешницу - квадрат размера N. Он может получить ее, собрав из уже имеющихся обрезков(квадратов).
Например, столешница размера 7×7 может быть построена из 9 обрезков.
Рисунок 1 – Пример столешницы
Внутри столешницы не должно быть пустот, обрезки не должны выходить за пределы столешницы и не должны перекрываться. Кроме того, Вова хочет использовать минимально возможное число обрезков.
Входные данные
Размер столешницы - одно целое число N (2≤N≤20).
Выходные данные
Одно число K, задающее минимальное количество обрезков(квадратов), из которых можно построить столешницу(квадрат) заданного размера N. Далее должны идти K строк, каждая из которых должна содержать три целых числа, x,y и w, задающие координаты левого верхнего угла (1≤x,y≤N) и длину стороны соответствующего обрезка(квадрата).
Вариант 1.
Выполнить исследование задачи на изменение времени поиска решения в зависимости от размера квадрата.
Основные теоретические положения.
Данную задачу можно разделить на 2 случая: длина столешницы – составное или простое число. Для составных четных чисел количество досок будет 4, а их длина N / 2. Для составных нечетных числе задача сводится к нахождению минимального простого делителя и поиску для него решения, а затем домножения этого решения на N / min_div. А для простых чисел разумнее всего расположить 3 квадрата размеров (N + 1) / 2, (N - 1) / 2, (N - 1) / 2 в три угла так, чтобы два маленьких квадрата соприкасались сторонами с большим. Затем для оставшейся фигуры искать решение.
Выполнение работы.
void data_optimization() – функция, которая преобразовывает данные: ищет коэффициент домножения ответа, уменьшает размер поля, рисует первые 3 квадрата на карте и добавляет их координаты и размер в массив для ответа.
int min_div(int x) – функция поиска минимального простого делителя.
void set_square(int x, int y, int w, int setter) – функция рисовки или стирания квадрата на карте.
int check_square(int x, int y, int w) – функция проверки места для расположения квадрата.
void backtracking(int coord) – рекурсивная функция, которая ищет лучшее решение расположения квадратов в оставшемся месте. В начале двигается координата до ближайшего пустого места. Затем проверяется, не дошли ли до конца, если да, то осуществляется проверка пустоты и оптимальности ответа, перезапись его (если нужно) и возврат из функции. После функция высчитывает координаты, размер помещающегося квадрата, рисует его, записывая координаты в ответ, и вызывает себя. В конце квадрат стирается и координаты его тоже.
int main() – в функции считывается размер столешницы, карта заполняется нулями, вызывается data_optimization, backtracking и void output().
void output() – функция выводит ответ на экран.
Разработанный программный код см. в приложении А.
Тестирование.
Результаты тестирования представлены в табл. 1.
Таблица 1 – Результаты тестирования
№ п/п |
Входные данные |
Выходные данные |
Комментарии |
1. |
7 |
9 1 1 4 1 5 3 5 1 3 4 5 2 4 7 1 5 4 1 5 7 1 6 4 2 6 6 2 |
Простое число. Верно. |
2. |
9 |
6 1 1 6 1 7 3 7 1 3 4 7 3 7 4 3 7 7 3 |
Составное нечетное число. Верно. |
3. |
16 |
4 1 1 8 1 9 8 9 1 8 9 9 8 |
Составное четное число. Верно. |
4. |
23 |
13 1 1 12 1 13 11 13 1 11 12 13 2 12 15 5 12 20 4 13 12 1 14 12 3 16 20 1 16 21 3 17 12 7 17 19 2 19 19 5 |
Простое число. Верно. |
Исследование.
Результаты исследования работы алгоритма представлены в табл. 2.
Таблица 2 – Результаты исследования
N |
sec |
2 |
0,0000084 |
3 |
0,0000101 |
4 |
0,0000099 |
5 |
0,0000134 |
6 |
0,0000112 |
7 |
0,0000184 |
8 |
0,0000118 |
9 |
0,0000137 |
10 |
0,0000130 |
11 |
0,0000726 |
12 |
0,0000130 |
13 |
0,0001574 |
14 |
0,0000141 |
15 |
0,0000174 |
16 |
0,0000168 |
17 |
0,0009365 |
18 |
0,0000224 |
19 |
0,0025387 |
20 |
0,0000235 |
21 |
0,0000246 |
22 |
0,0000232 |
23 |
0,0095506 |
24 |
0,0000236 |
25 |
0,0000272 |
26 |
0,0000243 |
27 |
0,0000265 |
28 |
0,0000256 |
29 |
0,0670108 |
30 |
0,0000256 |
31 |
0,1571048 |
32 |
0,0000267 |
33 |
0,0000296 |
34 |
0,0000292 |
35 |
0,0000319 |
36 |
0,0000310 |
37 |
0,8320149 |
38 |
0,0000321 |
39 |
0,0000383 |
40 |
0,0000377 |
График отношения размера столешницы ко времени работы алгоритма см. рис. 2.
Рисунок 2 – График исследования
По графику можно сделать вывод о том, что значение в составных числах максимально приближены к нулю, а также о том, что наибольшее значение времени достигается на больших простых числах (23, 29, 31, 37). Это связно с тем, что не получается оптимизировать данные с заменой числа на его наименьший простой делитель.
Вывод.
Был изучен алгоритм поиска с возвратом, а также написан код для решения практической задачи.
Для оптимизации алгоритма было вычислено разбиения только для простых чисел, а для составных чисел ответ домножался на коэффициент. Также перед алгоритмом всегда расстанавливались три квадрат размерами: (N + 1) / 2, (N - 1) / 2, (N - 1).
Еще было проведено исследование роста времени выполнения алгоритма при увеличении размера квадрата. Результаты показали, что худший случай алгоритма – большое простое число. Лучший случай – составное число, которое можно сократить до наименьшего простого, что в разы уменьшает время работы программы. Сложность алгоритма по времени является экспоненциальной.
ПРИЛОЖЕНИЕ А ИСХОДНЫЙ КОД ПРОГРАММЫ
Название файла: main.cpp
#include <iostream>
#include <vector>
#include <array>
#include <cmath>
int N, n, k = 0, cell_count = 0;
std::vector<std::array<int,3>> answer, curr_answer;
std::vector<std::vector<int>> map;
int min_div(int x) {
if (x % 2 == 0)
return 2;
for (int i = 3; i < int(sqrt(x)) + 1; i += 2)
if (x % i == 0)
return i;
return x;
}
void set_square(int x, int y, int w, int setter) {
for (int i = x; i < x + w; i++)
for (int j = y; j < y + w; j++)
map[i][j] = setter;
}
int check_square(int x, int y, int w) {
if (y + w > n or x + w > n)
return 0;
for (int i = x; i < x + w; i++)
for (int j = y; j < y + w; j++)
if (map[i][j])
return 0;
return 1;
}
void backtracking(int coord) {
if (coord < cell_count)
while (map[coord / n][coord % n]) {
coord++;
if (coord == cell_count)
break;
}
if (coord == cell_count) {
if (answer.empty() or curr_answer.size() < answer.size())
answer = curr_answer;
return;
}
if (answer.empty() or curr_answer.size() + 1 < answer.size()) {
int x = coord / n;
int y = coord % n;
int max_square = std::min(n - x, n - y);
while (not check_square(x, y, max_square))
max_square--;
for (int size_square = max_square; size_square > 0; size_square--) {
set_square(x, y, size_square, 1);
curr_answer.push_back({x, y, size_square});
backtracking(coord + size_square);
set_square(x, y, size_square, 0);
curr_answer.pop_back();
}
}
}
void data_optimization() {
k = n / min_div(n);
n /= k;
cell_count = n * n;
set_square(0, 0, (n + 1) / 2, 1);
set_square(0, (n + 1) / 2, n / 2, 1);
set_square((n + 1) / 2, 0, n / 2, 1);
curr_answer.push_back({0, 0, (n + 1) / 2});
curr_answer.push_back({0, (n + 1) / 2, n / 2});
curr_answer.push_back({(n + 1) / 2, 0, n / 2});
}
void output() {
std::cout << answer.size() << std::endl;
for (int i = 0; i < answer.size(); i++)
std::cout << answer[i][0] * k + 1 << " " << answer[i][1] * k + 1 << " " << answer[i][2] * k << std::endl;
}
int main() {
std::cin >> N;
n = N;
for (int i = 0; i < N; i++)
map.emplace_back(N, 0);
data_optimization();
backtracking(0);
output();
return 0;
}
