Вы находитесь на странице: 1из 21

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

Федеральное государственное автономное


образовательное учреждение высшего образования
«Южно-Уральский государственный университет
(национальный исследовательский университет)»

Высшая школа электроники и компьютерных наук


Кафедра «Электронные вычислительные машины»

ОТЧЕТ ПО УЧЕБНОЙ ПРАКТИКЕ


Алгоритмы и анализ сложности. Структуры данных

Руководители учебной практики:

к.п.н., доцент каф. ЭВМ


________________Ю.Г. Плаксина
«___»___________2021 г.
старший преподаватель каф. ЭВМ
________________А.Е. Беляков
«___»___________2021 г.

Автор работы,
студент группы КЭ-105/106
________________Т.Ф. Донбаев
«___»___________2021 г.

Челябинск-2021
МИНИСТЕРСТВО НАУКИ И ВЫСШЕГО ОБРАЗОВАНИЯ РОССИЙСКОЙ ФЕДЕРАЦИИ
Федеральное государственное автономное
образовательное учреждение высшего образования
«Южно-Уральский государственный университет
(национальный исследовательский университет)»
Высшая школа электроники и компьютерных наук
Кафедра «Электронные вычислительные машины»

Задание на учебную практику


студенту группы КЭ-105
Донбаев Тихон Филиппович
обучающемуся по направлению
09.03.01 «Информатика и вычислительная техника»

Задание А10
(Сортировка с помощью двоичного дерева (древесная сортировка) + анализ
сложности по времени)
1) Реализовать алгоритм древесной сортировки — универсальный алгоритм
сортировки, заключающийся в построении двоичного дерева поиска по
ключам массива (списка), с последующей сборкой результирующего массива
путём обхода узлов построенного дерева в необходимом порядке следования
ключей. Данная сортировка является оптимальной при получении данных
путём непосредственного чтения с потока (например с файла, сокета или
консоли).
2) В качестве источника данных для сортировки выбрать файл с
однопроходным последовательным чтением и одновременным
формированием дерева.
3) Сформировать несколько наборов данных (целых чисел) различного
объема. Построить экспериментальный график зависимости времени
выполнения кода программы от объема входных данных.
4) Сопоставить результаты с теоретическими оценками сложности
алгоритмов (по времени исполнения). Сделать выводы о факторах, которых
могли повлиять на точность измерений.
5) Сделать выводы.
Задание B10
(Ориентированный граф + обход в глубину и ширину)
1. Задана матрица смежности или инцидентности (по выбору).

2
2. Построить граф, каждый элемент которого будет представлен в виде:

Дополнительно в структуре предусмотреть хранение веса дуг графа,


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

Срок сдачи студентом законченной работы (защита практики):


27.07.2021.
Руководители учебной практики _______________________/А.Е. Беляков
_______________________/Ю.Г. Плаксина

Студент _______________________/Т.Ф. Донбаев/

3
ОГЛАВЛЕНИЕ

ВВЕДЕНИЕ...............................................................................................................5
1. Краткое описание алгоритма задание А............................................................ 7
2. Схема алгоритма задания А................................................................................ 9
3. Экспериментальный график зависимости времени выполнения кода.....…10
4. Сопоставление полученных результатов с теоретическими оценками
сложности алгоритмов (по времени исполнения)...................................................12
5. Краткое описание алгоритма задание В..........................................................12
6. Схема алгоритма задания В.............................................................................. 13
7. Описание реализации задания В.......................................................................14
БИБЛИОГРАФИЧЕСКИЙ СПИСОК.................................................................. 15
ПРИЛОЖЕНИЕ А ИСХОДНЫЙ КОД задание А............................................. 16
ПРИЛОЖЕНИЕ B ИСХОДНЫЙ КОД задание B..............................................18

4
1.ВВЕДЕНИЕ

1.Сложность алгоритма:
1) Временная сложность алгоритма– Под временной сложностью алгоритма
понимает «время» выполнения алгоритма, измеряемое в «шагах» (инструкциях
алгоритма), которое необходимо выполнить алгоритму для достижения
запланированного результата. В качестве синонима для этого термина
используется «временная сложность» [1].
2) Пространственная сложность алгоритмов –это функция от размера
входных данных определяющая количество занимаемой памяти [2].
Функции сложности алгоритмов и их анализ:
Виды функций сложности алгоритмов в порядке возрастания сложности [7]:
1.C–константа
2.log(log(N))
3.log(N)
4.N^C,0<C<1
5.N
6.N*log(N)
7.N^C,C>1
8.C^N,C>1
9. N!
[5]:
В оценке алгоритмов используются специальные асимптотические обозначения,
задающие следующие классы функций
O(g) – функции, растущие медленнее чем g;
Ω(g) – функции, растущие быстрее чем g;
Θ(g) – функции, растущие с той же скоростью, что и g.

5
Анализируя алгоритм, можно получить представление о том, сколько времени
займет решение данной задачи при помощи данного алгоритма. Время решения
может меняться в зависимости от входных данных
2.структуры данных:
1) Структура данных – это контейнер, который хранит данные в определенном
макете. Этот «макет» позволяет структуре данных быть эффективной в
некоторых операциях и неэффективной в других [7].
Разновидности структур:
2) Массив – это регулярная структура: все его компоненты одного типа,
называемого базовым типом. Все его компоненты могут выбираться
произвольно и являются одинокого доступными. [3]:
3) Стеки и очереди –это динамические множества, элементы из которых
удаляются с помощью предварительно определенной операцией DELETE.
Первым из стека удаляется элемент, который был помещен туда последним: в
стеке реализуется стратегия «последним вошел – первым вышел» (last-in, first-
out–LIFO). Аналогично, в очереди (queue) всегда удаляется элемент, который
содержится в множестве дольше других: в очереди реализуется стратегия
«первым вошел – первым вышел» (first-in, first-out–FIFO) [4]
4) Связанные списки –это структура данных, в которой объекты расположены
в линейном порядке. Однако, в отличии от массива, в котором этот порядок
определяется индексами, порядок в связанном списке определяется указателями
на каждый объект. [4]
5) Бинарные деревья –каждый узел дерева представляет собой отдельный
объект. В каждом узле содержится поле key. [4]
6) Бинарные деревья поиска –как следует из названия, бинарное дерево
поиска в первую очередь является бинарным деревом (Пункт 4). В дополнение к

6
полям ключа key и сопутствующих данных, каждый узе содержит поля left,
right, и p, которые указывают на левый и правый дочерние узлы и на
родительский узел соответственно. Если дочерний или родительский узел
отсутствуют, соответствующее поле содержит значение NIL. Единственный
узел, указатель p которого равен NIL, - это корневой узел дерева. [4]
2.Краткое описание алгоритма задание А
Из элементов массива формируется бинарное дерево поиска. Первым элементом
является корень дерева, остальные добавляются по следующему методу.
Начиная с корня дерева, элемент сравнивается с узлом. Если элемент меньше
чем узел – то спускаемся по левой ветке, если больше – то по правой.
Спустившись до конца, элемент сам становится узлом.
Построенное таким образом дерево можно легко обойти так, чтобы двигаться от
узлов с меньшими значениями к узлам с большими значениями. При этом
получаем все элементы в возрастающем порядке . [6]
Основным преимуществом двоичного дерева поиска перед другими
структурами данных является возможная высокая эффективность
реализации основных на нем алгоритмов поиска и сортировки.
Основным недостатком двоичного дерева поиска перед другими
структурами данных является большое требование к памяти под дерево.
Также необходимо n места под ключи и, кроме того, память на 2
указателя для каждого из них. Поэтому TreeSort обычно используют
там(где не тратится лишняя память ), где построенное дерево можно
также использовать для других задач, помимо основной; данные уже
внесены в дерево; данные можно считывать непосредственно в дерево,
например при потоковом вводе с консоли или из файла

7
Двоичное дерево поиска используется для построения более
абстрактных структур, таких, как множества, мультимножества,
ассоциативные массивы.

Рисунок 1-Алгоритм древесной сортировки [8]

8
Рисунок 2-блок схема алгоритма двоичной сортировки(Paint 3D)

Характеристики алгоритма

Рисунок 3- Характеристики алгоритма двоичной сортировки [8]

9
3.Сравнеие экспериментальных и теоретических результатов
выполнения программы
Для анализа работоспособности программы следует измерить
скорость выполнения алгоритма. Все экспериментальные данные были
занесены в таблицу 1.

Результаты работы алгоритма

Размер массива Кол. итераций Время


100 5049 1805108
200 20099 981807
300 45149 661719
400 80199 838690
500 125249 1292147
700 245349 592982
900 405449 777648
1500 1125749 1579918
2000 2000999 1482968
2500 3126249 3675362
3000 4501499 2380650
3500 6126749 3643558
4000 8001999 4979307
4500 10127249 5804148
5000 12502499 6883417
Таблица 1 -Экспериментальные данные для составления графиков.
На рисунке 4 можно наглядно увидеть зависимость затраченного времени
от размера массива. График построен на основе данных из таблицы 1.

10
8000000

7000000

6000000

5000000

4000000
Размер массива
Время (ннс)
3000000

2000000

1000000

0
1 4 7 1 0 13 1 6 19 2 2 25 2 8 3 1 34 3 7 40 4 3 46 4 9

Рисунок 4 - График зависимости затраченного времени от размера


массива. (excel 2019)
14000000

12000000

10000000

8000000

Размер массива
6000000
Количество итераций

4000000

2000000

0
1 5 9 13 17 21 25 29 33 37 41 45 49

Рисунок 5 - График зависимости количества итераций от размера


массива. (excel 2019)

4.Сопоставление полученных результатов с теоретическими оценками


сложности алгоритма.

11
Из проведенных выше исследований мы можем сделать вывод, что процедура
добавления объекта в бинарное дерево имеет среднюю алгоритмическую
сложность порядка O(log(n)). Это относит данную сортировку к числу
«быстрых сортировок»
Однако, проведя ряд измерений мы можно сказать, что сложность
добавления объекта в разбалансированное дерево может достигать O(n), что
может привести к общей сложности порядка O(n²).
5.Краткое описание алгоритма задания В
Задана матрица смежности или инцидентности (выбрали матрицу
инцидентности). Строим граф, дополнительно в структуре предусматриваем
хранение всех дуг графа, заданных в матрицах. Реализовываем алгоритм обхода
в глубину (DFS-Depth-First Search), (рекурсивно и не используя рекурсию,
применяя стек отложенных заданий) и ширину (BFS- Breadth-First Search),
(применяя очередь) полученного графа, параллельно демонстрируя на экране
процесс перебора вершин.

6.Схема алгоритма задания В

12
Рисунок 6- Блок схема алгоритм обхода в глубину и ширину.(Paint 3D)

13
7.Описание реализации задания В

1)Создаем очередь,
2)Реализовываем алгоритм DFS,
3)Реализовываем алгоритм BFS,
4)Создание узла,
5)Создание графика,
6)Добавляем вершины,
7)Добавляем ребро от источника к месту назначения,
8)Добавляем ребро от места назначения до источника,
9)Проверяем пуста ли очередь,
10)Добавляем элементы в очередь,
11)Распечатываем очередь,
12)Распечатываем график.

14
БИБЛИОГРАФИЧЕСКИЙ СПИСОК

1. Ахо, А.В. Структуры данных и алгоритмы./ А.В. Ахо, Д.Э. Хопкрофт,


Д.Д. Ульман. – Москва, Санкт-Петербург, Киев: Издательство «Вильямс»,
2003. – 384 с.
2. Клейнберг, Д. Алгоритмы. Разработка и применение./ Д. Клейнберг, Е.
Тардос – Москва, Санкт-Петербург, Нижний Новгород, Воронеж, Киев,
Екатеринбург, Самара, Минск: Издательство «Питер», 2016. – 799
3. Н.Вирт. Алгоритмы и структуры данных М.: Мир, 1989, 360 стр.
4. Кормен, Т. Алгоритмы. Построение и анализ./ Т. Кормен, Ч. Лейзерсон, Р.
Ривест, К. Штайн. Издательство «Вильямс», 2019 – 1328с.
5. Анализ сложности алгоритмов [Электронный ресурс] URL: https://pro-
prof.com/archives/1660/ (дата обращения 24.07.2021)
6. Сортировка бинарным деревом [Электронный ресурс]
http://algolab.valemak.com/tree-binary (дата обращения (17.07.2021) )
7. Основные структуры данных. Матчасть. Азы [Электронный ресурс]
URL:https://habr.com/ru/post/422259/ (дата обращения (17.07.2021)
8. Основы алгоритмизация и программирования
https://moodle.tepk.dev/mod/book/view.php?id=3322&chapterid=161 (дата
обращения (17.07.2021) .

15
ПРИЛОЖЕНИЕ А
ИСХОДНЫЙ КОД ДРЕВЕСНОЙ СОРТИРОВКИ

#include <stdio.h>
#include <stdlib.h>

struct tnode
{
int field;
struct tnode* left;
struct tnode* right;
};

void treeprint(struct tnode* tree)


{
if (tree != NULL) {
treeprint(tree->left);
printf("%d ", tree->field);
treeprint(tree->right);
}
}

struct tnode* addnode(int x, struct tnode* tree) {


if (tree == NULL)
{
tree = (struct tnode*)malloc(sizeof(struct tnode));
tree->field = x;
tree->left = NULL;
tree->right = NULL;
}
else
if (x < tree->field)
tree->left = addnode(x, tree->left);
else
tree->right = addnode(x, tree->right);
return(tree);
}

void freemem(struct tnode* tree)


{
if (tree != NULL)
{
freemem(tree->left);
freemem(tree->right);
free(tree);
}
}

int main()
{
struct tnode* root = 0;
system("chcp 1251");
system("cls");
int a;

16
for (int i = 0; i < 8; i++)
{
printf("Введите узел %d: ", i + 1);
scanf("%d", &a);
root = addnode(a, root);
}
treeprint(root);
freemem(root);
getchar(); getchar();
return 0;
}

17
ПРИЛОЖЕНИЕ В
ИСХОДНЫЙ КОД 0 (Ориентированный граф + обход в
глубину и ширину)

#include <locale.h>
#include <stdio.h>
#include <stdlib.h>
#define SIZE 40

struct queue {
int items[SIZE];
int front;
int rear;
};

struct queue* createQueue();


void enqueue(struct queue* q, int);
int dequeue(struct queue* q);
void display(struct queue* q);
int isEmpty(struct queue* q);
void printQueue(struct queue* q);

struct node {
int vertex;
struct node* next;
};

struct node* createNode(int);

struct Graph {
int numVertices;
struct node** adjLists;
int* visited;
};
// Алгоритм DFS
void DFS(struct Graph* graph, int vertex) {
setlocale(LC_ALL, "Rus");
struct node* adjList = graph->adjLists[vertex];
struct node* temp = adjList;

graph->visited[vertex] = 1;
printf("Посетил %d \n", vertex);

while (temp != NULL) {

18
int connectedVertex = temp->vertex;

if (graph->visited[connectedVertex] == 0) {
DFS(graph, connectedVertex);
}
temp = temp->next;
}
}
// Алгоритм BFS
void bfs(struct Graph* graph, int startVertex) {
setlocale(LC_ALL, "Rus");
struct queue* q = createQueue();

graph->visited[startVertex] = 1;
enqueue(q, startVertex);

while (!isEmpty(q)) {
printQueue(q);
int currentVertex = dequeue(q);
printf("Посетил %d\n", currentVertex);

struct node* temp = graph->adjLists[currentVertex];

while (temp) {
int adjVertex = temp->vertex;

if (graph->visited[adjVertex] == 0) {
graph->visited[adjVertex] = 1;
enqueue(q, adjVertex);
}
temp = temp->next;
}
}
}

// Создание узла
struct node* createNode(int v) {
struct node* newNode = malloc(sizeof(struct node));
newNode->vertex = v;
newNode->next = NULL;
return newNode;
}

// Создание графика
struct Graph* createGraph(int vertices) {
struct Graph* graph = malloc(sizeof(struct Graph));
graph->numVertices = vertices;

graph->adjLists = malloc(vertices * sizeof(struct node*));


graph->visited = malloc(vertices * sizeof(int));

int i;
for (i = 0; i < vertices; i++) {
graph->adjLists[i] = NULL;
graph->visited[i] = 0;
}

return graph;
}
19
// Добавляем вершину
void addEdge(struct Graph* graph, int src, int dest) {
// Добавляем ребро от источника к месту назначения
struct node* newNode = createNode(dest);
newNode->next = graph->adjLists[src];
graph->adjLists[src] = newNode;
// Добавляем ребро от места назначения до источника
newNode = createNode(src);
newNode->next = graph->adjLists[dest];
graph->adjLists[dest] = newNode;
}

// Создаем очередь
struct queue* createQueue() {
struct queue* q = malloc(sizeof(struct queue));
q->front = -1;
q->rear = -1;
return q;
}

// Проверяем, пуста ли очередь


int isEmpty(struct queue* q) {
if (q->rear == -1)
return 1;
else
return 0;
}

// Добавляем элементы в очередь


void enqueue(struct queue* q, int value) {
if (q->rear == SIZE - 1)
printf("\nОчередь заполнена!!");
else {
if (q->front == -1)
q->front = 0;
q->rear++;
q->items[q->rear] = value;
}
}

// Удаление элементов из очереди


int dequeue(struct queue* q) {
int item;
if (isEmpty(q)) {
printf("Очередь пуста");
item = -1;
} else {
item = q->items[q->front];
q->front++;
if (q->front > q->rear) {
printf("Сброс очереди ");
q->front = q->rear = -1;
}
}
return item;
}

// Распечатываем очередь
20
void printQueue(struct queue* q) {
int i = q->front;

if (isEmpty(q)) {
printf("Очередь пуста");
} else {
printf("\nОчередь содержит \n");
for (i = q->front; i < q->rear + 1; i++) {
printf("%d ", q->items[i]);
}
}
}
// Распечатываем график
void printGraph(struct Graph* graph) {
int v;
for (v = 0; v < graph->numVertices; v++) {
struct node* temp = graph->adjLists[v];
printf("\n Список смежности вершины %d\n ", v);
while (temp) {
printf("%d -> ", temp->vertex);
temp = temp->next;
}
printf("\n");
}
}
int main() {
printf("Поиск в ширину\nСоздаем и печатаем граф\n");
struct Graph* graph = createGraph(6);
addEdge(graph, 0, 1);
addEdge(graph, 0, 2);
addEdge(graph, 1, 2);
addEdge(graph, 1, 4);
addEdge(graph, 1, 3);
addEdge(graph, 2, 4);
addEdge(graph, 3, 4);
printGraph(graph);
printf("\nВыполняем обход графа в ширину\n");
bfs(graph, 0);
printf("Поиск в глубину\nСоздаем и печатаем граф\n");
struct Graph* graph1 = createGraph(4);
addEdge(graph1, 0, 1);
addEdge(graph1, 0, 2);
addEdge(graph1, 1, 2);
addEdge(graph1, 2, 3);
printGraph(graph1);
printf("\nВыполняем обход графа в глубину\n");
DFS(graph1, 2);

return 0;
}

21