2026-rff_mp/stepinim/lab1_structure/test.py
2026-05-21 13:40:02 +03:00

376 lines
13 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import sys
sys.setrecursionlimit(30000) # Увеличиваю лимит рекурсии для BST
# Связный список
def ll_insert(head, name, phone):
new_node = {'name': name, 'phone': phone, 'next': None} # Создаю новый узел
if head is None: # Если список пуст
return new_node # Возвращаю узел как голову
curr = head # Указатель для обхода
prev = None # Храню предыдущий узел
while curr is not None: # Иду по списку
if curr['name'] == name: # Если нашел такое же имя
curr['phone'] = phone # Обновляю телефон
return head
prev = curr
curr = curr['next']
prev['next'] = new_node # Добавляю в конец
return head
def ll_find(head, name):
curr = head # Начинаю с головы
while curr: # Иду по всему списку
if curr['name'] == name: # Сравниваю имена
return curr['phone'] # Возвращаю телефон
curr = curr['next'] # Перехожу к следующему
return None # Не нашел
def ll_delete(head, name):
if head is None: # Пустой список
return None
if head['name'] == name: # Удаляю голову
return head['next'] # Возвращаю второй элемент
curr = head
while curr['next']: # Иду пока есть следующий
if curr['next']['name'] == name: # Нашел элемент для удаления
curr['next'] = curr['next']['next'] # Перепрыгиваю через него
return head
curr = curr['next']
return head
def ll_list_all(head):
result = []
curr = head
while curr: # Собираю все элементы
result.append((curr['name'], curr['phone']))
curr = curr['next']
result.sort(key=lambda x: x[0]) # Сортирую по имени
return result
# Хэш-таблица
HASH_SIZE = 1009 # Размер таблицы - простое число
def _hash_name(name):
return hash(name) % HASH_SIZE # Беру остаток от деления - это индекс корзины
def ht_insert(buckets, name, phone):
idx = _hash_name(name) # Вычисляю индекс корзины
buckets[idx] = ll_insert(buckets[idx], name, phone) # Метод цепочек - вставляю в список
def ht_find(buckets, name):
idx = _hash_name(name) # Нахожу корзину
return ll_find(buckets[idx], name) # Ищу в цепочке
def ht_delete(buckets, name):
idx = _hash_name(name) # Нахожу корзину
buckets[idx] = ll_delete(buckets[idx], name) # Удаляю из цепочки
def ht_list_all(buckets):
all_entries = []
for bucket in buckets: # Прохожу по всем корзинам
if bucket is not None:
curr = bucket
while curr: # Собираю всю цепочку
all_entries.append((curr['name'], curr['phone']))
curr = curr['next']
all_entries.sort(key=lambda x: x[0])
return all_entries
# Двоичное дерево поиска
def bst_insert(root, name, phone):
if root is None: # Пустое место - создаю узел
return {'name': name, 'phone': phone, 'left': None, 'right': None}
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):
curr = root
while curr: # Итеративный спуск по дереву
if name == curr['name']: # Нашел
return curr['phone']
elif name < curr['name']: # Искомое меньше - налево
curr = curr['left']
else: # Искомое больше - направо
curr = curr['right']
return None
def bst_delete(root, name):
if root is None:
return None
if name < root['name']: # Ищу в левом поддереве
root['left'] = bst_delete(root['left'], name)
elif name > root['name']: # Ищу в правом поддереве
root['right'] = bst_delete(root['right'], name)
else: # Нашел узел для удаления
if root['left'] is None: # Нет левого ребенка
return root['right'] # Заменяю правым
if root['right'] is None: # Нет правого ребенка
return root['left'] # Заменяю левым
# Есть оба ребенка - ищу минимальный в правом поддереве
min_node = root['right']
while min_node['left']: # Иду до самого левого
min_node = min_node['left']
root['name'] = min_node['name'] # Копирую данные преемника
root['phone'] = min_node['phone']
root['right'] = bst_delete(root['right'], min_node['name']) # Удаляю преемника
return root
def bst_list_all(root):
result = []
def inorder(node): # Симметричный обход
if node:
inorder(node['left']) # Сначала левое
result.append((node['name'], node['phone'])) # Потом корень
inorder(node['right']) # Потом правое
inorder(root)
return result
# ============================================================
# TECT
# ============================================================
import os
import random
import time
import csv
import pandas as pd
import matplotlib.pyplot as plt
# ============================================================
# ПОДГОТОВКА ПАПОК
# ============================================================
DATA_DIR = os.path.join("docs", "data")
os.makedirs(DATA_DIR, exist_ok=True)
csv_path = os.path.join(DATA_DIR, "lab1_results.csv")
graph_path = os.path.join(DATA_DIR, "lab1_graph.png")
# ============================================================
# ТЕСТОВЫЕ ДАННЫЕ
# ============================================================
random.seed(42) # Фиксирую seed для повторяемости
N = 3000 # 3000 записей
base_records = [
(f"User_{i:05d}", f"123-{i:05d}")
for i in range(N)
]
records_shuffled = base_records.copy()
random.shuffle(records_shuffled) # Перемешанный порядок
records_sorted = sorted(base_records, key=lambda x: x[0]) # Отсортированный порядок
# Данные для поиска
search_existing = [
name for name, _ in random.sample(base_records, 100) # 100 существующих имен
]
search_nonexist = [
f"None_{i}"
for i in range(10) # 10 несуществующих имен
]
# Данные для удаления
delete_names = [
name for name, _ in random.sample(base_records, 50) # 50 имен для удаления
]
# ============================================================
# СОЗДАНИЕ СТРУКТУР
# ============================================================
def build_structure(records, struct_type):
if struct_type == "ll":
structure = None
for name, phone in records:
structure = ll_insert(structure, name, phone) # Последовательная вставка
return structure
elif struct_type == "ht":
structure = [None] * HASH_SIZE
for name, phone in records:
ht_insert(structure, name, phone) # Вставка с хэшированием
return structure
elif struct_type == "bst":
structure = None
for name, phone in records:
structure = bst_insert(structure, name, phone) # Вставка с ветвлением
return structure
# ============================================================
# INSERT
# ============================================================
def measure_insert(records, struct_type):
start = time.perf_counter()
build_structure(records, struct_type) # Замеряю время построения структуры
end = time.perf_counter()
return end - start
# ============================================================
# SEARCH
# ============================================================
def measure_search(records, struct_type):
structure = build_structure(records, struct_type) # Строю структуру
start = time.perf_counter()
if struct_type == "ll":
for name in search_existing + search_nonexist:
ll_find(structure, name) # Поиск перебором
elif struct_type == "ht":
for name in search_existing + search_nonexist:
ht_find(structure, name) # Поиск через хэш
elif struct_type == "bst":
for name in search_existing + search_nonexist:
bst_find(structure, name) # Поиск спуском по дереву
end = time.perf_counter()
return end - start
# ============================================================
# DELETE
# ============================================================
def measure_delete(records, struct_type):
structure = build_structure(records, struct_type) # Строю структуру
start = time.perf_counter()
if struct_type == "ll":
for name in delete_names:
structure = ll_delete(structure, name) # Удаление со сдвигом
elif struct_type == "ht":
for name in delete_names:
ht_delete(structure, name) # Удаление из цепочки
elif struct_type == "bst":
for name in delete_names:
structure = bst_delete(structure, name) # Удаление с ребалансировкой
end = time.perf_counter()
return end - start
# ============================================================
# ЗАМЕРЫ
# ============================================================
all_data = []
experiments = [
("LinkedList", "ll"),
("HashTable", "ht"),
("BST", "bst")
]
modes = [
("shuffled", records_shuffled), # Тест на случайных данных
("sorted", records_sorted) # Тест на отсортированных данных
]
for struct_name, struct_type in experiments:
for mode_name, records in modes:
for rep in range(1, 4): # 3 повтора для усреднения
insert_time = measure_insert(records, struct_type)
search_time = measure_search(records, struct_type)
delete_time = measure_delete(records, struct_type)
all_data.append([struct_name, mode_name, rep, "insert", insert_time])
all_data.append([struct_name, mode_name, rep, "search", search_time])
all_data.append([struct_name, mode_name, rep, "delete", delete_time])
# ============================================================
# CSV
# ============================================================
with open(csv_path, "w", newline="", encoding="utf-8") as f:
writer = csv.writer(f)
writer.writerow(["Структура", "Режим", "Повтор", "Операция", "Время (сек)"])
writer.writerows(all_data)
print(f"CSV сохранён: {csv_path}")
# ============================================================
# ГРАФИК
# ============================================================
df = pd.read_csv(csv_path)
df_avg = (
df.groupby(["Структура", "Режим", "Операция"])["Время (сек)"]
.mean()
.reset_index()
)
fig, ax = plt.subplots(figsize=(12, 6))
ops = ["insert", "search", "delete"]
x = range(len(ops))
width = 0.12
configs = [
("LinkedList", "shuffled"),
("LinkedList", "sorted"),
("HashTable", "shuffled"),
("HashTable", "sorted"),
("BST", "shuffled"),
("BST", "sorted")
]
for i, (struct, mode) in enumerate(configs):
subset = df_avg[
(df_avg["Структура"] == struct) &
(df_avg["Режим"] == mode)
]
times = [
subset[subset["Операция"] == op]["Время (сек)"].values[0]
for op in ops
]
ax.bar(
[p + i * width for p in x],
times,
width,
label=f"{struct} ({mode})"
)
ax.set_xticks([p + 2.5 * width for p in x])
ax.set_xticklabels(ops)
ax.set_ylabel("Среднее время (сек)")
ax.set_title("Сравнение структур данных")
ax.legend(bbox_to_anchor=(1.05, 1), loc="upper left")
plt.tight_layout()
plt.savefig(graph_path)
print(f"График сохранён: {graph_path}")
plt.show()