Автор работы,
студент группы КЭ-105/106
________________Т.Ф. Донбаев
«___»___________2021 г.
Челябинск-2021
МИНИСТЕРСТВО НАУКИ И ВЫСШЕГО ОБРАЗОВАНИЯ РОССИЙСКОЙ ФЕДЕРАЦИИ
Федеральное государственное автономное
образовательное учреждение высшего образования
«Южно-Уральский государственный университет
(национальный исследовательский университет)»
Высшая школа электроники и компьютерных наук
Кафедра «Электронные вычислительные машины»
Задание А10
(Сортировка с помощью двоичного дерева (древесная сортировка) + анализ
сложности по времени)
1) Реализовать алгоритм древесной сортировки — универсальный алгоритм
сортировки, заключающийся в построении двоичного дерева поиска по
ключам массива (списка), с последующей сборкой результирующего массива
путём обхода узлов построенного дерева в необходимом порядке следования
ключей. Данная сортировка является оптимальной при получении данных
путём непосредственного чтения с потока (например с файла, сокета или
консоли).
2) В качестве источника данных для сортировки выбрать файл с
однопроходным последовательным чтением и одновременным
формированием дерева.
3) Сформировать несколько наборов данных (целых чисел) различного
объема. Построить экспериментальный график зависимости времени
выполнения кода программы от объема входных данных.
4) Сопоставить результаты с теоретическими оценками сложности
алгоритмов (по времени исполнения). Сделать выводы о факторах, которых
могли повлиять на точность измерений.
5) Сделать выводы.
Задание B10
(Ориентированный граф + обход в глубину и ширину)
1. Задана матрица смежности или инцидентности (по выбору).
2
2. Построить граф, каждый элемент которого будет представлен в виде:
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
Двоичное дерево поиска используется для построения более
абстрактных структур, таких, как множества, мультимножества,
ассоциативные массивы.
8
Рисунок 2-блок схема алгоритма двоичной сортировки(Paint 3D)
Характеристики алгоритма
9
3.Сравнеие экспериментальных и теоретических результатов
выполнения программы
Для анализа работоспособности программы следует измерить
скорость выполнения алгоритма. Все экспериментальные данные были
занесены в таблицу 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
12000000
10000000
8000000
Размер массива
6000000
Количество итераций
4000000
2000000
0
1 5 9 13 17 21 25 29 33 37 41 45 49
11
Из проведенных выше исследований мы можем сделать вывод, что процедура
добавления объекта в бинарное дерево имеет среднюю алгоритмическую
сложность порядка O(log(n)). Это относит данную сортировку к числу
«быстрых сортировок»
Однако, проведя ряд измерений мы можно сказать, что сложность
добавления объекта в разбалансированное дерево может достигать O(n), что
может привести к общей сложности порядка O(n²).
5.Краткое описание алгоритма задания В
Задана матрица смежности или инцидентности (выбрали матрицу
инцидентности). Строим граф, дополнительно в структуре предусматриваем
хранение всех дуг графа, заданных в матрицах. Реализовываем алгоритм обхода
в глубину (DFS-Depth-First Search), (рекурсивно и не используя рекурсию,
применяя стек отложенных заданий) и ширину (BFS- Breadth-First Search),
(применяя очередь) полученного графа, параллельно демонстрируя на экране
процесс перебора вершин.
12
Рисунок 6- Блок схема алгоритм обхода в глубину и ширину.(Paint 3D)
13
7.Описание реализации задания В
1)Создаем очередь,
2)Реализовываем алгоритм DFS,
3)Реализовываем алгоритм BFS,
4)Создание узла,
5)Создание графика,
6)Добавляем вершины,
7)Добавляем ребро от источника к месту назначения,
8)Добавляем ребро от места назначения до источника,
9)Проверяем пуста ли очередь,
10)Добавляем элементы в очередь,
11)Распечатываем очередь,
12)Распечатываем график.
14
БИБЛИОГРАФИЧЕСКИЙ СПИСОК
15
ПРИЛОЖЕНИЕ А
ИСХОДНЫЙ КОД ДРЕВЕСНОЙ СОРТИРОВКИ
#include <stdio.h>
#include <stdlib.h>
struct tnode
{
int field;
struct tnode* left;
struct tnode* right;
};
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 node {
int vertex;
struct node* next;
};
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);
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);
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;
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;
}
// Распечатываем очередь
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