[1] task-1-data-structures #163
16
.gitignore
vendored
16
.gitignore
vendored
|
|
@ -160,3 +160,19 @@ cython_debug/
|
||||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||||
#.idea/
|
#.idea/
|
||||||
|
|
||||||
|
|
||||||
|
# Игнорируем CSV
|
||||||
|
aufgabe-1-data-structures/results/timedata_*.csv
|
||||||
|
|
||||||
|
# разрешаем средние данные
|
||||||
|
!aufgabe-1-data-structures/results/aaverage_*.csv
|
||||||
|
|
||||||
|
# Мусор LaTeX
|
||||||
|
*.aux
|
||||||
|
*.synctex.gz
|
||||||
|
*.toc
|
||||||
|
*.log
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
250
ProninVV/aufgabe-1-data-structures/aufg1.py
Normal file
250
ProninVV/aufgabe-1-data-structures/aufg1.py
Normal file
|
|
@ -0,0 +1,250 @@
|
||||||
|
# LInkedList (Node = List = {'name': 'Имя', 'phone': '123', 'next': None}) имена уникальные (id)
|
||||||
|
|
||||||
|
|
||||||
|
def ll_insert(head, name, phone):
|
||||||
|
|
||||||
|
""" проходит до конца (или сразу добавляет в конец) и возвращает новую голову
|
||||||
|
(если вставка в начало) или изменяет список по ссылке. Удобнее возвращать новую
|
||||||
|
голову, если вставка может быть в начало """
|
||||||
|
|
||||||
|
new_node = {'name': name, 'phone': phone, 'next': None}
|
||||||
|
|
||||||
|
# если списка не было
|
||||||
|
if head is None:
|
||||||
|
return new_node
|
||||||
|
|
||||||
|
# # вставка в начало O(1)
|
||||||
|
# new_node = {'name': name, 'phone': phone, 'next': head}
|
||||||
|
# return new_node
|
||||||
|
|
||||||
|
# вставка в конец O(n)
|
||||||
|
current = head
|
||||||
|
while current['next'] is not None:
|
||||||
|
# проверка существования данного идентификатора (обновляем запись)
|
||||||
|
if current['name'] == name:
|
||||||
|
current['phone'] = phone
|
||||||
|
return head
|
||||||
|
current = current['next']
|
||||||
|
|
||||||
|
# проверка на id
|
||||||
|
if current['name'] == name:
|
||||||
|
current['phone'] = phone
|
||||||
|
else: current['next'] = new_node
|
||||||
|
return head
|
||||||
|
|
||||||
|
|
||||||
|
def ll_find(head, name):
|
||||||
|
|
||||||
|
""" ищет узел, возвращает телефон или None """
|
||||||
|
|
||||||
|
current = head
|
||||||
|
while current is not None:
|
||||||
|
if current['name'] == name:
|
||||||
|
return current['phone']
|
||||||
|
current = current['next']
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def ll_delete(head, name):
|
||||||
|
|
||||||
|
""" удаляет узел, возвращает новую голову """
|
||||||
|
|
||||||
|
if head is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Удаление первого
|
||||||
|
if head['name'] == name:
|
||||||
|
new_head = head['next']
|
||||||
|
head['next'] = None
|
||||||
|
return new_head
|
||||||
|
|
||||||
|
# Если не первый
|
||||||
|
current = head
|
||||||
|
while current['next'] is not None:
|
||||||
|
if current['next']['name'] == name:
|
||||||
|
target = current['next']
|
||||||
|
current['next'] = target['next']
|
||||||
|
target['next'] = None
|
||||||
|
return head
|
||||||
|
current = current['next']
|
||||||
|
|
||||||
|
return head
|
||||||
|
|
||||||
|
|
||||||
|
def ll_list_all(head):
|
||||||
|
|
||||||
|
""" собирает все записи в список и сортирует (сортировка вынесена отдельно) """
|
||||||
|
|
||||||
|
length = ll_Lenght(head)
|
||||||
|
new_list = [None]*length
|
||||||
|
current = head
|
||||||
|
for i in range(length):
|
||||||
|
new_list[i] = (current['name'], current['phone'])
|
||||||
|
current = current['next']
|
||||||
|
sorten(new_list)
|
||||||
|
return new_list
|
||||||
|
|
||||||
|
|
||||||
|
# вспомогательные функции--------------------------------
|
||||||
|
def ll_Lenght(head):
|
||||||
|
# длина связного списка
|
||||||
|
counter = 0
|
||||||
|
curr = head
|
||||||
|
while curr:
|
||||||
|
counter += 1
|
||||||
|
curr = curr['next']
|
||||||
|
return counter
|
||||||
|
|
||||||
|
|
||||||
|
def sorten(arr):
|
||||||
|
n = len(arr)
|
||||||
|
for i in range(n):
|
||||||
|
for j in range(0, n - i - 1):
|
||||||
|
if arr[j][0] > arr[j + 1][0]:
|
||||||
|
arr[j], arr[j + 1] = arr[j + 1], arr[j]
|
||||||
|
# -----------------------------------------------------------
|
||||||
|
|
||||||
|
# HashTable (Хранится как список buckets фиксированной длины, каждый элемент — голова связного списка (или None))
|
||||||
|
|
||||||
|
def hash_table(size):
|
||||||
|
return [None]*size
|
||||||
|
|
||||||
|
|
||||||
|
def hash_func(name, buckets_count):
|
||||||
|
h = 0
|
||||||
|
for char in name:
|
||||||
|
h += ord(char)
|
||||||
|
return h % buckets_count
|
||||||
|
|
||||||
|
|
||||||
|
def ht_insert(buckets, name, phone):
|
||||||
|
|
||||||
|
""" вычисляет индекс, вызывает ll_insert для соответствующего бакета """
|
||||||
|
|
||||||
|
if buckets is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
index = hash_func(name, len(buckets))
|
||||||
|
buckets[index] = ll_insert(buckets[index], name, phone)
|
||||||
|
|
||||||
|
|
||||||
|
def ht_find(buckets, name):
|
||||||
|
""" """
|
||||||
|
idx = hash_func(name, len(buckets))
|
||||||
|
return ll_find(buckets[idx], name)
|
||||||
|
|
||||||
|
|
||||||
|
def ht_delete(buckets, name):
|
||||||
|
idx = hash_func(name, len(buckets))
|
||||||
|
buckets[idx] = ll_delete(buckets[idx], name)
|
||||||
|
|
||||||
|
|
||||||
|
def ht_list_all(buckets):
|
||||||
|
|
||||||
|
""" собирает все записи из всех бакетов и сортирует """
|
||||||
|
total_count = 0
|
||||||
|
for head in buckets:
|
||||||
|
total_count += ll_Lenght(head)
|
||||||
|
|
||||||
|
full_data = [None]*total_count
|
||||||
|
|
||||||
|
k = 0
|
||||||
|
for head in buckets:
|
||||||
|
curr = head
|
||||||
|
while curr:
|
||||||
|
full_data[k] = (curr['name'], curr['phone'])
|
||||||
|
k += 1
|
||||||
|
curr = curr['next']
|
||||||
|
|
||||||
|
sorten(full_data)
|
||||||
|
return full_data
|
||||||
|
|
||||||
|
|
||||||
|
# Двоичное дерево поиска : Узел — словарь: {'name': 'Имя', 'phone': '123', 'left': None, 'right': None}
|
||||||
|
|
||||||
|
def bst_insert(root, name, phone):
|
||||||
|
|
||||||
|
""" рекурсивно или итеративно вставляет, возвращает новый корень (если корень меняется) """
|
||||||
|
|
||||||
|
new_node = {'name': name, 'phone': phone, 'left': None, 'right': None}
|
||||||
|
|
||||||
|
# если дерева нет
|
||||||
|
if root is None:
|
||||||
|
return new_node
|
||||||
|
|
||||||
|
if name < root['name']:
|
||||||
|
root['left'] = bst_insert(root['left'], name, phone)
|
||||||
|
elif name > root['name']:
|
||||||
|
root['right'] = bst_insert(root['right'], name, phone)
|
||||||
|
else:
|
||||||
|
root['phone'] = phone
|
||||||
|
return root
|
||||||
|
|
||||||
|
|
||||||
|
def bst_find(root, name):
|
||||||
|
|
||||||
|
""" поиск """
|
||||||
|
|
||||||
|
if root is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if root['name'] == name:
|
||||||
|
return root['phone']
|
||||||
|
|
||||||
|
elif name < root['name']:
|
||||||
|
return bst_find(root['left'], name)
|
||||||
|
|
||||||
|
elif name > root['name']:
|
||||||
|
return bst_find(root['right'], name)
|
||||||
|
|
||||||
|
|
||||||
|
def bst_delete(root, name):
|
||||||
|
|
||||||
|
""" удаление, возвращает новый корень """
|
||||||
|
|
||||||
|
if root is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# спускаемся к нужному узлу (аналогично поиску)
|
||||||
|
elif name < root['name']:
|
||||||
|
root['left'] = bst_delete(root['left'], name)
|
||||||
|
|
||||||
|
elif name > root['name']:
|
||||||
|
root['right'] = bst_delete(root['right'], name)
|
||||||
|
|
||||||
|
# стоим в нужном узле
|
||||||
|
else:
|
||||||
|
# узла слева нет (вернет правого ребенка или None)
|
||||||
|
if root['left'] is None:
|
||||||
|
return root['right']
|
||||||
|
|
||||||
|
# узла справа нет (вернет левого ребенка)
|
||||||
|
if root['right'] is None:
|
||||||
|
return root['left']
|
||||||
|
|
||||||
|
# два наследника (поиск минимального поддерева в правой ветке)
|
||||||
|
|
||||||
|
successor = root['right']
|
||||||
|
while successor['left'] is not None:
|
||||||
|
successor = successor['left']
|
||||||
|
|
||||||
|
root['name'] = successor['name']
|
||||||
|
root['phone'] = successor['phone']
|
||||||
|
# Удаляем дубликат преемника в правом поддереве
|
||||||
|
root['right'] = bst_delete(root['right'], successor['name'])
|
||||||
|
|
||||||
|
|
||||||
|
return root
|
||||||
|
|
||||||
|
|
||||||
|
def bst_list_all(root, result=None):
|
||||||
|
""" центрированный обход (рекурсивно собирает записи в отсортированном порядке) """
|
||||||
|
if result is None:
|
||||||
|
result = []
|
||||||
|
# сначала спускаемся по левой стороне вниз, затем идем вверх и вправо
|
||||||
|
if root is not None:
|
||||||
|
bst_list_all(root['left'], result)
|
||||||
|
result.append((root['name'], root['phone']))
|
||||||
|
bst_list_all(root['right'], result)
|
||||||
|
return result
|
||||||
20
ProninVV/aufgabe-1-data-structures/data_analysis.py
Normal file
20
ProninVV/aufgabe-1-data-structures/data_analysis.py
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
import pandas as pd
|
||||||
|
import glob
|
||||||
|
|
||||||
|
folder_path = 'results'
|
||||||
|
|
||||||
|
sizes = ['500', '1000', '2000', '5000', '10000']
|
||||||
|
|
||||||
|
|
||||||
|
for size in sizes:
|
||||||
|
files = glob.glob(f'{folder_path}/timedata_{size}_epochs_*.csv')
|
||||||
|
|
||||||
|
data = [pd.read_csv(f)['Время (сек)'] for f in files]
|
||||||
|
|
||||||
|
datatomean = pd.concat(data, axis=1)
|
||||||
|
datamean = datatomean.mean(axis=1)
|
||||||
|
|
||||||
|
df = pd.read_csv(files[0])
|
||||||
|
df['Время (сек)'] = datamean
|
||||||
|
|
||||||
|
df.to_csv(f'results/aaverage_timedata_{size}.csv', index=False)
|
||||||
94
ProninVV/aufgabe-1-data-structures/graphiki.py
Normal file
94
ProninVV/aufgabe-1-data-structures/graphiki.py
Normal file
|
|
@ -0,0 +1,94 @@
|
||||||
|
import pandas as pd
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
from matplotlib.ticker import AutoMinorLocator
|
||||||
|
|
||||||
|
df500 = pd.read_csv("results/aaverage_timedata_500.csv")
|
||||||
|
df1000 = pd.read_csv("results/aaverage_timedata_1000.csv")
|
||||||
|
df2000 = pd.read_csv("results/aaverage_timedata_2000.csv")
|
||||||
|
df5000 = pd.read_csv("results/aaverage_timedata_5000.csv")
|
||||||
|
df10000 = pd.read_csv("results/aaverage_timedata_10000.csv")
|
||||||
|
|
||||||
|
|
||||||
|
def select_data_list(ax):
|
||||||
|
dfs = [df500, df1000, df2000, df5000, df10000]
|
||||||
|
Nvals = [500, 1000, 2000, 5000, 10000]
|
||||||
|
# delete, find, insert
|
||||||
|
# список:
|
||||||
|
valsSort = [list(arr[(arr['Структура'] == "linklist") & (arr['Режим'] == "sorted")]["Время (сек)"]) for arr in dfs]
|
||||||
|
valsShuff = [list(arr[(arr['Структура'] == "linklist") & (arr['Режим'] == "shuffled")]["Время (сек)"]) for arr in dfs]
|
||||||
|
# 0 - sorted 1 - shuffled
|
||||||
|
# delete
|
||||||
|
ax[0].plot(Nvals, [row[0] for row in valsSort], label="delete", color='red')
|
||||||
|
ax[1].plot(Nvals, [row[0] for row in valsShuff], color='red')
|
||||||
|
# find
|
||||||
|
ax[0].plot(Nvals, [row[1] for row in valsSort], label="find", color='blue')
|
||||||
|
ax[1].plot(Nvals, [row[1] for row in valsShuff], color='blue')
|
||||||
|
# insert
|
||||||
|
ax[0].plot(Nvals, [row[2] for row in valsSort], label="insert", color='green')
|
||||||
|
ax[1].plot(Nvals, [row[2] for row in valsShuff], color='green')
|
||||||
|
|
||||||
|
|
||||||
|
def select_data_hasht(ax):
|
||||||
|
dfs = [df500, df1000, df2000, df5000, df10000]
|
||||||
|
Nvals = [500, 1000, 2000, 5000, 10000]
|
||||||
|
# delete, find, insert
|
||||||
|
# список:
|
||||||
|
valsSort = [list(arr[(arr['Структура'] == "hashtable") & (arr['Режим'] == "sorted")]["Время (сек)"]) for arr in dfs]
|
||||||
|
valsShuff = [list(arr[(arr['Структура'] == "hashtable") & (arr['Режим'] == "shuffled")]["Время (сек)"]) for arr in dfs]
|
||||||
|
# 0 - sorted 1 - shuffled
|
||||||
|
# delete
|
||||||
|
ax[0].plot(Nvals, [row[0] for row in valsSort], label="delete", color='red')
|
||||||
|
ax[1].plot(Nvals, [row[0] for row in valsShuff], color='red')
|
||||||
|
# find
|
||||||
|
ax[0].plot(Nvals, [row[1] for row in valsSort], label="find", color='blue')
|
||||||
|
ax[1].plot(Nvals, [row[1] for row in valsShuff], color='blue')
|
||||||
|
# insert
|
||||||
|
ax[0].plot(Nvals, [row[2] for row in valsSort], label="insert", color='green')
|
||||||
|
ax[1].plot(Nvals, [row[2] for row in valsShuff], color='green')
|
||||||
|
|
||||||
|
|
||||||
|
def select_data_tree(ax):
|
||||||
|
dfs = [df500, df1000, df2000, df5000, df10000]
|
||||||
|
Nvals = [500, 1000, 2000, 5000, 10000]
|
||||||
|
# delete, find, insert
|
||||||
|
# список:
|
||||||
|
valsSort = [list(arr[(arr['Структура'] == "bintree") & (arr['Режим'] == "sorted")]["Время (сек)"]) for arr in dfs]
|
||||||
|
valsShuff = [list(arr[(arr['Структура'] == "bintree") & (arr['Режим'] == "shuffled")]["Время (сек)"]) for arr in dfs]
|
||||||
|
# 0 - sorted 1 - shuffled
|
||||||
|
# delete
|
||||||
|
ax[0].plot(Nvals, [row[0] for row in valsSort], label="delete", color='red')
|
||||||
|
ax[1].plot(Nvals, [row[0] for row in valsShuff], color='red')
|
||||||
|
# find
|
||||||
|
ax[0].plot(Nvals, [row[1] for row in valsSort], label="find", color='blue')
|
||||||
|
ax[1].plot(Nvals, [row[1] for row in valsShuff], color='blue')
|
||||||
|
# insert
|
||||||
|
ax[0].plot(Nvals, [row[2] for row in valsSort], label="insert", color='green')
|
||||||
|
ax[1].plot(Nvals, [row[2] for row in valsShuff], color='green')
|
||||||
|
|
||||||
|
# построение графика
|
||||||
|
def design_show_graph(title, version, ymaxlim):
|
||||||
|
fig, ax = plt.subplots(figsize=(10, 5), nrows=1, ncols=2)
|
||||||
|
for i in range(2):
|
||||||
|
match title:
|
||||||
|
case "Tree":
|
||||||
|
select_data_tree(ax)
|
||||||
|
case "Linklist":
|
||||||
|
select_data_list(ax)
|
||||||
|
case "hasht":
|
||||||
|
select_data_hasht(ax)
|
||||||
|
ax[0].set_title(f"График сложностей для {title} (sort)")
|
||||||
|
ax[1].set_title(f"График сложностей для {title} (shuff)")
|
||||||
|
ax[i].set_xlabel("N")
|
||||||
|
ax[i].set_ylabel("сек * ")
|
||||||
|
ax[i].grid(which="major", linewidth=1.5)
|
||||||
|
ax[i].grid(which="minor", color="gray", linewidth=0.5)
|
||||||
|
ax[i].xaxis.set_minor_locator(AutoMinorLocator())
|
||||||
|
ax[i].yaxis.set_minor_locator(AutoMinorLocator())
|
||||||
|
ax[i].legend()
|
||||||
|
ax[i].set_ylim(0, ymaxlim)
|
||||||
|
plt.savefig(f'graphics\{title}{version}.png', dpi=200)
|
||||||
|
plt.savefig(f'graphics\T{title}{version}.eps', dpi=200)
|
||||||
|
plt.show()
|
||||||
|
|
||||||
|
|
||||||
|
design_show_graph("hasht", 2, 0.4)
|
||||||
BIN
ProninVV/aufgabe-1-data-structures/report/document.pdf
Normal file
BIN
ProninVV/aufgabe-1-data-structures/report/document.pdf
Normal file
Binary file not shown.
136
ProninVV/aufgabe-1-data-structures/report/document.tex
Normal file
136
ProninVV/aufgabe-1-data-structures/report/document.tex
Normal file
|
|
@ -0,0 +1,136 @@
|
||||||
|
\input{preambule.tex}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
\begin{document}
|
||||||
|
|
||||||
|
% --- ТИТУЛЬНЫЙ ЛИСТ (упрощенно) ---
|
||||||
|
\begin{titlepage}
|
||||||
|
\centering
|
||||||
|
МИНИСТЕРСТВО НАУКИ И ВЫСШЕГО ОБРАЗОВАНИЯ РФ \\
|
||||||
|
«Национальный исследовательский Нижегородский государственный университет им. Н.И. Лобачевского» \\
|
||||||
|
\vspace{4cm}
|
||||||
|
\Large ОТЧЕТ К ЛАБОРАТОРНОЙ РАБОТЕ \\
|
||||||
|
\vspace{1cm}
|
||||||
|
\large «Реализация и экспериментальное сравнение базовых структур данных на примере телефонного справочника» \\
|
||||||
|
\vspace{4cm}
|
||||||
|
\flushright
|
||||||
|
Выполнил: студент В. В. Пронин \\
|
||||||
|
Преподаватель: Н. С. Морозов \\
|
||||||
|
\vfill
|
||||||
|
Нижний Новгород \\
|
||||||
|
2024
|
||||||
|
\end{titlepage}
|
||||||
|
|
||||||
|
\newpage
|
||||||
|
\tableofcontents
|
||||||
|
\newpage
|
||||||
|
|
||||||
|
\section{Введение}
|
||||||
|
|
||||||
|
Эффективность программных систем во многом определяется выбором способов организации данных в оперативной памяти. Задача разработки телефонного справочника является классическим примером, требующим баланса между скоростью вставки новых записей, поиском по ключу и эффективным удалением.
|
||||||
|
|
||||||
|
В рамках данной работы исследуются три фундаментальные структуры данных, реализованные «с нуля» в процедурной парадигме программирования на языке Python:
|
||||||
|
\begin{itemize}
|
||||||
|
\item \textbf{Связный список (Linked List)} --- динамическая структура, позволяющая оценить базовые механизмы управления указателями и демонстрирующая линейную сложность операций $O(n)$.
|
||||||
|
\item \textbf{Хеш-таблица (Hash Table)} --- ассоциативный массив, использующий хеширование для обеспечения прямого доступа к данным. Реализация позволяет изучить методы разрешения коллизий и преимущества константной сложности $O(1)$.
|
||||||
|
\item \textbf{Двоичное дерево поиска (BST)} --- иерархическая структура, обеспечивающая логарифмическую скорость доступа $O(\log n)$ и поддерживающая упорядоченность данных «из коробки».
|
||||||
|
\end{itemize}
|
||||||
|
|
||||||
|
\textbf{Цель работы:} Изучить внутренние алгоритмы работы перечисленных структур, реализовать их без использования встроенных высокоуровневых контейнеров и экспериментально подтвердить теоретические оценки временной сложности на случайных и отсортированных наборах данных.
|
||||||
|
|
||||||
|
\section{Реализация структур данных}
|
||||||
|
\subsection{Связный список}
|
||||||
|
% Здесь опишите логику ll_insert, ll_find и ll_delete
|
||||||
|
\subsection{Хеш-таблица}
|
||||||
|
% Опишите хеш-функцию и метод цепочек
|
||||||
|
\subsection{Двоичное дерево поиска}
|
||||||
|
% Опишите рекурсивные алгоритмы и проблему деградации
|
||||||
|
|
||||||
|
\section{Методика эксперимента}
|
||||||
|
Замеры производились для наборов данных объемом $N=500, 1000, 2000, 5000, 10000$ элементов. Использовались два сценария: перемешанные (\textit{shuffled}) и отсортированные по алфавиту (\textit{sorted}) записи. Каждая операция выполнялась 5 раз с последующим вычислением среднего арифметического значения с помощью функции \texttt{time.perf\_counter()}.
|
||||||
|
|
||||||
|
\section{Результаты и анализ}
|
||||||
|
Было проведено серию опытов для $N$ от 500 до 10000.
|
||||||
|
\subsection*{1. Бинарное дерево поиска (BST) и влияние порядка}
|
||||||
|
|
||||||
|
\begin{figure}[H]
|
||||||
|
\centering
|
||||||
|
\includegraphics[scale=0.7]{plots/TTree1.eps}
|
||||||
|
\caption{Зависимость времени выполнения операций в BST от объема данных}
|
||||||
|
\end{figure}
|
||||||
|
|
||||||
|
\begin{figure}[H]
|
||||||
|
\centering
|
||||||
|
\includegraphics[scale=0.7]{plots/TTree2.eps}
|
||||||
|
\end{figure}
|
||||||
|
|
||||||
|
\begin{itemize}
|
||||||
|
\item \textbf{Деградация на отсортированных данных:} При вставке отсортированных данных время увеличилось с \textbf{0.124с} ($N=1000$) до \textbf{13.27с} ($N=10000$). Рост времени в 100 раз при увеличении объема данных в 10 раз четко указывает на квадратичную сложность $O(n^2)$ для процесса заполнения всей структуры. Дерево выродилось в линейный список, и поиск места вставки стал занимать $O(n)$ вместо ожидаемого $O(\log n)$.
|
||||||
|
\item \textbf{Эффективность на перемешанных данных:} На \texttt{shuffled} данных вставка 10000 элементов заняла всего \textbf{0.031с}. Это подтверждает логарифмическую сложность $O(\log n)$ для операций в дереве при случайном распределении ключей.
|
||||||
|
\end{itemize}
|
||||||
|
|
||||||
|
\subsection*{2. Хеш-таблица: Стабильность и скорость}
|
||||||
|
|
||||||
|
\begin{figure}[H]
|
||||||
|
\centering
|
||||||
|
\includegraphics[scale=0.7]{plots/Thasht1.eps}
|
||||||
|
\end{figure}
|
||||||
|
|
||||||
|
\begin{figure}[H]
|
||||||
|
\centering
|
||||||
|
\includegraphics[scale=0.7]{plots/Thasht2.eps}
|
||||||
|
\end{figure}
|
||||||
|
|
||||||
|
\begin{itemize}
|
||||||
|
\item \textbf{Чувствительность к порядку:} Хеш-таблица показала идентичные результаты как на \texttt{shuffled}, так и на \texttt{sorted} данных (около \textbf{0.165с} -- \textbf{0.167с} для 10000 вставок). Это объясняется тем, что хеш-функция распределяет ключи по бакетам независимо от их исходного порядка, предотвращая деградацию структуры.
|
||||||
|
\item \textbf{Превосходство:} На больших объемах хеш-таблица оказалась самой быстрой структурой для поиска и удаления ($\approx 0.001$с при $N=10000$), что подтверждает теоретическую среднюю сложность $O(1)$.
|
||||||
|
\item \textbf{Замечание:} Так как реализация использует списки для разрешения коллизий со вставкой в конец, при заполнении таблицы наблюдается рост времени вставки, стремящийся к квадратичному, однако абсолютные значения остаются на порядки ниже, чем у выродившегося BST.
|
||||||
|
\end{itemize}
|
||||||
|
\subsection*{3. Связный список: Линейная зависимость}
|
||||||
|
\begin{figure}[H]
|
||||||
|
\centering
|
||||||
|
\includegraphics[scale=0.7]{plots/Tlinklist1.eps}
|
||||||
|
\end{figure}
|
||||||
|
|
||||||
|
\begin{figure}[H]
|
||||||
|
\centering
|
||||||
|
\includegraphics[scale=0.7]{plots/Tlinklist2.eps}
|
||||||
|
\end{figure}
|
||||||
|
|
||||||
|
\begin{itemize}
|
||||||
|
\item \textbf{Поиск и удаление:} Связный список показал худшие результаты среди всех структур на случайных данных. Время поиска при 10000 элементах (\textbf{0.029с}) значительно медленнее, чем у BST на перемешанных данных (\textbf{0.0002с}). Это подтверждает линейную сложность $O(n)$.
|
||||||
|
\item \textbf{Вставка:} Вставка (вероятно, в конец или с сохранением порядка) дает $O(n^2)$ при заполнении (\textbf{2.83с} -- \textbf{3.00с} на 10000 эл.). Характер роста времени при переходе от $N=5000$ (\textbf{0.71с}) к $N=10000$ подтверждает квадратичную зависимость.
|
||||||
|
\end{itemize}
|
||||||
|
|
||||||
|
\subsection*{Вывод: выбор структуры данных}
|
||||||
|
\begin{enumerate}
|
||||||
|
\item \textbf{Хеш-таблица} — наиболее универсальный выбор. Она обеспечивает стабильное $O(1)$ для поиска и не зависит от порядка входящих данных.
|
||||||
|
\item \textbf{BST} — крайне эффективен ($O(\log n)$) при случайном распределении данных, но без механизмов самобалансировки критически уязвим к отсортированным входным последовательностям, замедляясь до уровня списка.
|
||||||
|
\item \textbf{Связный список} — продемонстрировал самую низкую производительность на операциях поиска и массовой вставки. Его использование оправдано только в специфических сценариях (например, реализация стека), где работа ведется исключительно с головой списка за $O(1)$.
|
||||||
|
\end{enumerate}
|
||||||
|
|
||||||
|
|
||||||
|
\subsection*{Сводная таблица результатов}
|
||||||
|
\begin{table}[H]
|
||||||
|
\centering
|
||||||
|
\small
|
||||||
|
\begin{tabular}{|l|l|c|c|c|c|c|}
|
||||||
|
\hline
|
||||||
|
\textbf{Структура} & \textbf{Режим} & \textbf{Опер.} & \textbf{N=500} & \textbf{N=1000} & \textbf{N=5000} & \textbf{N=10000} \\ \hline
|
||||||
|
\multirow{3}{*}{LinkList} & Shuffled & Insert & 0.0066 & 0.0292 & 0.7089 & 2.8358 \\
|
||||||
|
& Shuffled & Find & 0.0012 & 0.0026 & 0.0147 & 0.0289 \\
|
||||||
|
& Sorted & Insert & 0.0065 & 0.0290 & 0.7637 & 3.0042 \\ \hline
|
||||||
|
\multirow{3}{*}{HashTable} & Shuffled & Insert & 0.0007 & 0.0022 & 0.0468 & 0.1670 \\
|
||||||
|
& Shuffled & Find & 0.0001 & 0.0002 & 0.0008 & 0.0014 \\
|
||||||
|
& Sorted & Insert & 0.0007 & 0.0022 & 0.0448 & 0.1646 \\ \hline
|
||||||
|
\multirow{3}{*}{BinTree} & Shuffled & Insert & 0.0009 & 0.0021 & 0.0145 & 0.0309 \\
|
||||||
|
& Shuffled & Find & 0.0001 & 0.0001 & 0.0002 & 0.0002 \\
|
||||||
|
& Sorted & Insert & \textbf{0.0298} & \textbf{0.1239} & \textbf{3.3052} & \textbf{13.2706} \\ \hline
|
||||||
|
\end{tabular}
|
||||||
|
\caption{Сравнение минимального времени выполнения операций (в секундах) в зависимости от объема данных $N$}
|
||||||
|
\end{table}
|
||||||
|
|
||||||
|
|
||||||
|
\end{document}
|
||||||
5099
ProninVV/aufgabe-1-data-structures/report/plots/TLinklist1.eps
Normal file
5099
ProninVV/aufgabe-1-data-structures/report/plots/TLinklist1.eps
Normal file
File diff suppressed because it is too large
Load Diff
4350
ProninVV/aufgabe-1-data-structures/report/plots/TLinklist2.eps
Normal file
4350
ProninVV/aufgabe-1-data-structures/report/plots/TLinklist2.eps
Normal file
File diff suppressed because it is too large
Load Diff
4887
ProninVV/aufgabe-1-data-structures/report/plots/TList1.eps
Normal file
4887
ProninVV/aufgabe-1-data-structures/report/plots/TList1.eps
Normal file
File diff suppressed because it is too large
Load Diff
5078
ProninVV/aufgabe-1-data-structures/report/plots/TTree1.eps
Normal file
5078
ProninVV/aufgabe-1-data-structures/report/plots/TTree1.eps
Normal file
File diff suppressed because it is too large
Load Diff
4327
ProninVV/aufgabe-1-data-structures/report/plots/TTree2.eps
Normal file
4327
ProninVV/aufgabe-1-data-structures/report/plots/TTree2.eps
Normal file
File diff suppressed because it is too large
Load Diff
4357
ProninVV/aufgabe-1-data-structures/report/plots/Thasht1.eps
Normal file
4357
ProninVV/aufgabe-1-data-structures/report/plots/Thasht1.eps
Normal file
File diff suppressed because it is too large
Load Diff
5836
ProninVV/aufgabe-1-data-structures/report/plots/Thasht2.eps
Normal file
5836
ProninVV/aufgabe-1-data-structures/report/plots/Thasht2.eps
Normal file
File diff suppressed because it is too large
Load Diff
46
ProninVV/aufgabe-1-data-structures/report/preambule.tex
Normal file
46
ProninVV/aufgabe-1-data-structures/report/preambule.tex
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
%\documentclass[a4paper, 12pt]{article}
|
||||||
|
\documentclass[a4paper, 14pt]{extarticle}
|
||||||
|
|
||||||
|
\usepackage[english, russian]{babel}
|
||||||
|
\usepackage[T2A]{fontenc}
|
||||||
|
\usepackage[utf8]{inputenc}
|
||||||
|
\usepackage{comment}
|
||||||
|
|
||||||
|
\usepackage{multirow}
|
||||||
|
\usepackage{fontspec}
|
||||||
|
\setmainfont{Times New Roman}
|
||||||
|
|
||||||
|
\usepackage{amsmath}
|
||||||
|
\usepackage{amssymb}
|
||||||
|
|
||||||
|
\usepackage{geometry}
|
||||||
|
\usepackage{titleps}
|
||||||
|
\usepackage{graphicx}
|
||||||
|
\DeclareGraphicsExtensions{.pdf, .jpg}
|
||||||
|
\usepackage{wrapfig}
|
||||||
|
|
||||||
|
|
||||||
|
\usepackage{indentfirst}
|
||||||
|
|
||||||
|
|
||||||
|
\geometry{top=20mm}
|
||||||
|
\geometry{bottom=25mm}
|
||||||
|
\geometry{left=30mm}
|
||||||
|
\geometry{right=10mm}
|
||||||
|
|
||||||
|
\usepackage{float}
|
||||||
|
\usepackage{wrapfig}
|
||||||
|
|
||||||
|
\newpagestyle{main}{
|
||||||
|
\setheadrule{0.4pt}
|
||||||
|
\sethead{ННГУ им Н.И. Лобачесвкого}{}{В. В. Пронин}
|
||||||
|
|
||||||
|
\setfoot{}{\thepage}{}
|
||||||
|
}
|
||||||
|
\pagestyle{main}
|
||||||
|
%\setcounter{page}{2}
|
||||||
|
|
||||||
|
\linespread{1.5}
|
||||||
|
\setlength{\parindent}{10mm}
|
||||||
|
\setlength{\parskip}{1ex}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
Структура,Режим,Операция,Время (сек)
|
||||||
|
linklist,shuffled,insert,0.02917951999970676
|
||||||
|
linklist,shuffled,find,0.00256621999997146
|
||||||
|
linklist,shuffled,delete,0.0018302000000403
|
||||||
|
hashtable,shuffled,insert,0.00223439999972464
|
||||||
|
hashtable,shuffled,find,0.00018408000032643998
|
||||||
|
hashtable,shuffled,delete,0.00013254000023147998
|
||||||
|
bintree,shuffled,insert,0.00211651999998134
|
||||||
|
bintree,shuffled,find,0.00014015999986434
|
||||||
|
bintree,shuffled,delete,7.299999997485429e-05
|
||||||
|
linklist,sorted,insert,0.02902601999994654
|
||||||
|
linklist,sorted,find,0.00272362000014248
|
||||||
|
linklist,sorted,delete,0.0017690399998172598
|
||||||
|
hashtable,sorted,insert,0.00219620000007122
|
||||||
|
hashtable,sorted,find,0.00018717999992074
|
||||||
|
hashtable,sorted,delete,0.00011756000003512
|
||||||
|
bintree,sorted,insert,0.12391134000008604
|
||||||
|
bintree,sorted,find,0.0079993400002422
|
||||||
|
bintree,sorted,delete,0.004170019999764881
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
Структура,Режим,Операция,Время (сек)
|
||||||
|
linklist,shuffled,insert,2.835846880000099
|
||||||
|
linklist,shuffled,find,0.02894071999999136
|
||||||
|
linklist,shuffled,delete,0.017179720000240158
|
||||||
|
hashtable,shuffled,insert,0.16700972000016914
|
||||||
|
hashtable,shuffled,find,0.0014067599999179402
|
||||||
|
hashtable,shuffled,delete,0.00103095999966166
|
||||||
|
bintree,shuffled,insert,0.030944720000115878
|
||||||
|
bintree,shuffled,find,0.00019450000017964003
|
||||||
|
bintree,shuffled,delete,9.787999988471869e-05
|
||||||
|
linklist,sorted,insert,3.0041990600000643
|
||||||
|
linklist,sorted,find,0.02895102000002222
|
||||||
|
linklist,sorted,delete,0.016321099999913664
|
||||||
|
hashtable,sorted,insert,0.16461017999990868
|
||||||
|
hashtable,sorted,find,0.0014511600000332201
|
||||||
|
hashtable,sorted,delete,0.0010335000002669001
|
||||||
|
bintree,sorted,insert,13.270635900000162
|
||||||
|
bintree,sorted,find,0.08588061999998894
|
||||||
|
bintree,sorted,delete,0.04398507999994758
|
||||||
|
140
ProninVV/aufgabe-1-data-structures/test.py
Normal file
140
ProninVV/aufgabe-1-data-structures/test.py
Normal file
|
|
@ -0,0 +1,140 @@
|
||||||
|
from aufg1 import *
|
||||||
|
import time
|
||||||
|
import random
|
||||||
|
import sys
|
||||||
|
import csv
|
||||||
|
|
||||||
|
sys.setrecursionlimit(20000)
|
||||||
|
|
||||||
|
def phone_number_generate():
|
||||||
|
number = "8"
|
||||||
|
text = "0123456789"
|
||||||
|
for i in range(10):
|
||||||
|
char = random.choice(text)
|
||||||
|
number += char
|
||||||
|
return number
|
||||||
|
|
||||||
|
def create_data(n=100):
|
||||||
|
|
||||||
|
""" создаем сразу обычный массив и остортированный """
|
||||||
|
|
||||||
|
records_sorted = []
|
||||||
|
for i in range(n):
|
||||||
|
name = f"User_{i:05d}"
|
||||||
|
phone = phone_number_generate()
|
||||||
|
records_sorted.append((name, phone))
|
||||||
|
|
||||||
|
records_shuffled = records_sorted[:]
|
||||||
|
random.shuffle(records_shuffled)
|
||||||
|
return records_sorted, records_shuffled
|
||||||
|
|
||||||
|
|
||||||
|
def run_expirement(epoch=1, elements=1000):
|
||||||
|
|
||||||
|
""" распределяем данные по трем структурам данных
|
||||||
|
тестируем время операций (вставки, удаления, перебора) и записываем полученные результаты в файл """
|
||||||
|
header = ["Структура", "Режим", "Операция", "Время (сек)"]
|
||||||
|
|
||||||
|
for j in range(epoch):
|
||||||
|
print(f"эпоха - {j+1}")
|
||||||
|
|
||||||
|
results = [header]
|
||||||
|
# создаем данные
|
||||||
|
records_sorted, records_shuffled = create_data(elements)
|
||||||
|
|
||||||
|
datasets = [
|
||||||
|
("shuffled", records_shuffled),
|
||||||
|
("sorted", records_sorted)]
|
||||||
|
|
||||||
|
# сразу будем обрабатывать и случайны и отсортированный данные
|
||||||
|
for label, arr in datasets:
|
||||||
|
|
||||||
|
linklist = None
|
||||||
|
hashtab = hash_table(elements)
|
||||||
|
bintree = None
|
||||||
|
# заполнение связного списка
|
||||||
|
start = time.perf_counter()
|
||||||
|
for p in arr:
|
||||||
|
linklist = ll_insert(linklist, p[0], p[1])
|
||||||
|
end = time.perf_counter()
|
||||||
|
results.append(["linklist", label, "insert", end-start])
|
||||||
|
|
||||||
|
# поиск 110 имен в связном списке
|
||||||
|
# несуществующие данные
|
||||||
|
nonedata = [(f"None_{i}", phone_number_generate()) for i in range(10)]
|
||||||
|
# случайная комбинация
|
||||||
|
chaossample = random.sample(arr, 100) + nonedata
|
||||||
|
start = time.perf_counter()
|
||||||
|
for p in chaossample:
|
||||||
|
ll_find(linklist, p[0])
|
||||||
|
end = time.perf_counter()
|
||||||
|
results.append(["linklist", label, "find", end-start])
|
||||||
|
|
||||||
|
# удаление 50 имен в св писке
|
||||||
|
deldata = random.sample(arr, 50)
|
||||||
|
start = time.perf_counter()
|
||||||
|
for p in deldata:
|
||||||
|
ll_delete(linklist, p[0])
|
||||||
|
end = time.perf_counter()
|
||||||
|
results.append(["linklist", label, "delete", end-start])
|
||||||
|
|
||||||
|
# заполнение хэш-тфблицы
|
||||||
|
start = time.perf_counter()
|
||||||
|
for p in arr:
|
||||||
|
ht_insert(hashtab, p[0], p[1])
|
||||||
|
end = time.perf_counter()
|
||||||
|
results.append(["hashtable", label, "insert", end-start])
|
||||||
|
|
||||||
|
# поиск 110 имен в хэш таблице
|
||||||
|
# несуществующие данные
|
||||||
|
nonedata = [(f"None_{i}", phone_number_generate()) for i in range(10)]
|
||||||
|
# случайная комбинация
|
||||||
|
chaossample = random.sample(arr, 100) + nonedata
|
||||||
|
start = time.perf_counter()
|
||||||
|
for p in chaossample:
|
||||||
|
ht_find(hashtab, p[0])
|
||||||
|
end = time.perf_counter()
|
||||||
|
results.append(["hashtable", label, "find", end-start])
|
||||||
|
|
||||||
|
# удаление 50 имен в хэш таблице
|
||||||
|
deldata = random.sample(arr, 50)
|
||||||
|
start = time.perf_counter()
|
||||||
|
for p in deldata:
|
||||||
|
ht_delete(hashtab, p[0])
|
||||||
|
end = time.perf_counter()
|
||||||
|
results.append(["hashtable", label, "delete", end-start])
|
||||||
|
|
||||||
|
# заполнение дерева
|
||||||
|
start = time.perf_counter()
|
||||||
|
for p in arr:
|
||||||
|
bintree = bst_insert(bintree, p[0], p[1])
|
||||||
|
end = time.perf_counter()
|
||||||
|
results.append(["bintree", label, "insert", end-start])
|
||||||
|
|
||||||
|
# поиск 110 имен в дереве
|
||||||
|
# несуществующие данные
|
||||||
|
nonedata = [(f"None_{i}", phone_number_generate()) for i in range(10)]
|
||||||
|
# случайная комбинация
|
||||||
|
chaossample = random.sample(arr, 100) + nonedata
|
||||||
|
start = time.perf_counter()
|
||||||
|
for p in chaossample:
|
||||||
|
bst_find(bintree, p[0])
|
||||||
|
end = time.perf_counter()
|
||||||
|
results.append(["bintree", label, "find", end-start])
|
||||||
|
|
||||||
|
# удаление 50 имен в дереве
|
||||||
|
deldata = random.sample(arr, 50)
|
||||||
|
start = time.perf_counter()
|
||||||
|
for p in deldata:
|
||||||
|
bst_delete(bintree, p[0])
|
||||||
|
end = time.perf_counter()
|
||||||
|
results.append(["bintree", label, "delete", end-start])
|
||||||
|
|
||||||
|
filename = f"results/timedata_{elements}_epochs_{j+1}.csv"
|
||||||
|
with open(filename, mode='w', encoding='utf-8', newline='') as file:
|
||||||
|
writer = csv.writer(file)
|
||||||
|
writer.writerows(results)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
run_expirement(epoch=5, elements=5000)
|
||||||
Loading…
Reference in New Issue
Block a user