2026-rff_mp/starikovta/phonebook.py
2026-05-21 22:00:40 +03:00

366 lines
14 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 time
import random
import csv
import sys
sys.setrecursionlimit (50000)
from datetime import datetime
# ==================== СВЯЗНЫЙ СПИСОК ====================
def ll_insert(head, name, phone):
"""Добавляет или обновляет запись в связном списке"""
# Проверяем, не существует ли уже такой name
curr = head
while curr:
if curr['name'] == name:
curr['phone'] = phone # обновляем
return head
curr = curr['next']
# Если не нашли, вставляем в конец
new_node = {'name': name, 'phone': phone, 'next': None}
if head is None:
return new_node
curr = head
while curr['next']:
curr = curr['next']
curr['next'] = new_node
return head
def ll_find(head, name):
"""Ищет запись по имени, возвращает телефон или None"""
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):
"""Собирает все записи и сортирует по имени"""
records = []
curr = head
while curr:
records.append((curr['name'], curr['phone']))
curr = curr['next']
# Сортируем по имени
records.sort(key=lambda x: x[0])
return records
# ==================== ХЕШ-ТАБЛИЦА ====================
def hash_function(name, size):
"""Простая хеш-функция"""
return sum(ord(c) for c in name) % size
def ht_insert(buckets, name, phone):
"""Вставляет запись в хеш-таблицу"""
index = hash_function(name, len(buckets))
# Используем ll_insert для бакета
buckets[index] = ll_insert(buckets[index], name, phone)
return buckets
def ht_find(buckets, name):
"""Ищет запись в хеш-таблице"""
index = hash_function(name, len(buckets))
return ll_find(buckets[index], name)
def ht_delete(buckets, name):
"""Удаляет запись из хеш-таблицы"""
index = hash_function(name, len(buckets))
buckets[index] = ll_delete(buckets[index], name)
return buckets
def ht_list_all(buckets):
"""Собирает все записи из всех бакетов и сортирует"""
all_records = []
for bucket in buckets:
curr = bucket
while curr:
all_records.append((curr['name'], curr['phone']))
curr = curr['next']
all_records.sort(key=lambda x: x[0])
return all_records
# ==================== БИНАРНОЕ ДЕРЕВО ПОИСКА ====================
def bst_insert(root, name, phone):
"""Вставляет запись в BST (рекурсивно)"""
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):
"""Ищет запись в BST"""
if root is None:
return None
if name == root['name']:
return root['phone']
elif name < root['name']:
return bst_find(root['left'], name)
else:
return bst_find(root['right'], name)
def bst_find_min(node):
"""Находит узел с минимальным значением"""
current = node
while current and current['left']:
current = current['left']
return current
def bst_delete(root, name):
"""Удаляет запись из BST"""
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']
elif root['right'] is None:
return root['left']
# Узел с двумя детьми
temp = bst_find_min(root['right'])
root['name'] = temp['name']
root['phone'] = temp['phone']
root['right'] = bst_delete(root['right'], temp['name'])
return root
def bst_list_all(root):
"""Центрированный обход (возвращает отсортированный список)"""
records = []
def inorder_traversal(node):
if node:
inorder_traversal(node['left'])
records.append((node['name'], node['phone']))
inorder_traversal(node['right'])
inorder_traversal(root)
return records
# ==================== ГЕНЕРАЦИЯ ТЕСТОВЫХ ДАННЫХ ====================
def generate_test_data(n=10000):
"""Генерирует shuffled и sorted версии данных"""
# Генерируем имена с небольшим количеством коллизий
names_pool = [f"User_{i:05d}" for i in range(n // 10)] # 1000 уникальных имен
records = []
for i in range(n):
name = random.choice(names_pool) # повторяющиеся имена для коллизий
phone = f"+7-999-{random.randint(1000000, 9999999)}"
records.append((name, phone))
# Создаем shuffled и sorted версии
records_shuffled = records.copy()
random.shuffle(records_shuffled)
records_sorted = sorted(records, key=lambda x: x[0])
return records_shuffled, records_sorted
# ==================== ЗАМЕРЫ ВРЕМЕНИ ====================
def measure_insertion(struct_type, records, buckets_size=None):
"""Замеряет время вставки всех записей"""
if struct_type == "LinkedList":
head = None
start = time.perf_counter()
for name, phone in records:
head = ll_insert(head, name, phone)
end = time.perf_counter()
elif struct_type == "HashTable":
if buckets_size is None:
buckets_size = 1000
buckets = [None] * buckets_size
start = time.perf_counter()
for name, phone in records:
buckets = ht_insert(buckets, name, phone)
end = time.perf_counter()
elif struct_type == "BST":
root = None
start = time.perf_counter()
for name, phone in records:
root = bst_insert(root, name, phone)
end = time.perf_counter()
else:
return None, None
elapsed = end - start
return elapsed, (head if struct_type == "LinkedList" else (buckets if struct_type == "HashTable" else root))
def measure_find(struct_type, data_structure, records, num_existing=100, num_nonexistent=10):
"""Замеряет время поиска записей"""
# Выбираем существующие записи
existing_names = random.sample([name for name, _ in records[:len(records)//2]], min(num_existing, len(records)))
# Создаем несуществующие имена
nonexistent_names = [f"None_{i}" for i in range(num_nonexistent)]
all_queries = existing_names + nonexistent_names
random.shuffle(all_queries)
start = time.perf_counter()
for name in all_queries:
if struct_type == "LinkedList":
ll_find(data_structure, name)
elif struct_type == "HashTable":
ht_find(data_structure, name)
elif struct_type == "BST":
bst_find(data_structure, name)
end = time.perf_counter()
return end - start
def measure_delete(struct_type, data_structure, records, num_to_delete=50):
"""Замеряет время удаления записей"""
# Выбираем случайные имена для удаления
names_to_delete = random.sample([name for name, _ in records[:len(records)//2]], min(num_to_delete, len(records)))
start = time.perf_counter()
for name in names_to_delete:
if struct_type == "LinkedList":
data_structure = ll_delete(data_structure, name)
elif struct_type == "HashTable":
data_structure = ht_delete(data_structure, name)
elif struct_type == "BST":
data_structure = bst_delete(data_structure, name)
end = time.perf_counter()
return end - start
# ==================== ОСНОВНОЙ ЭКСПЕРИМЕНТ ====================
def run_experiment():
"""Запускает все эксперименты и сохраняет результаты"""
print("Генерация тестовых данных...")
records_shuffled, records_sorted = generate_test_data(10000)
structures = ["LinkedList", "HashTable", "BST"]
modes = {"shuffled": records_shuffled, "sorted": records_sorted}
all_results = []
all_results.append(["Структура", "Режим", "Операция", "Повторение", "Время (сек)"])
# Количество повторений
repeats = 5
for struct in structures:
for mode_name, records in modes.items():
print(f"\nТестирование: {struct}, режим {mode_name}")
# Вставка
insertion_times = []
for rep in range(repeats):
print(f" Вставка, повторение {rep+1}/{repeats}...")
elapsed, data_struct = measure_insertion(struct, records)
insertion_times.append(elapsed)
all_results.append([struct, mode_name, "вставка", rep+1, elapsed])
# Используем последнюю структуру для поиска и удаления
# (пересоздаем для чистоты эксперимента)
_, data_struct = measure_insertion(struct, records)
# Поиск
find_times = []
for rep in range(repeats):
print(f" Поиск, повторение {rep+1}/{repeats}...")
elapsed = measure_find(struct, data_struct, records)
find_times.append(elapsed)
all_results.append([struct, mode_name, "поиск", rep+1, elapsed])
# Удаление
delete_times = []
# Создаем свежую структуру для удаления
_, fresh_data_struct = measure_insertion(struct, records)
for rep in range(repeats):
print(f" Удаление, повторение {rep+1}/{repeats}...")
elapsed = measure_delete(struct, fresh_data_struct, records)
delete_times.append(elapsed)
all_results.append([struct, mode_name, "удаление", rep+1, elapsed])
# Выводим средние значения
print(f" Среднее время вставки: {sum(insertion_times)/len(insertion_times):.6f} сек")
print(f" Среднее время поиска: {sum(find_times)/len(find_times):.6f} сек")
print(f" Среднее время удаления: {sum(delete_times)/len(delete_times):.6f} сек")
# Сохраняем результаты в CSV
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
csv_filename = f"experiment_results_{timestamp}.csv"
with open(csv_filename, 'w', newline='', encoding='utf-8') as f:
writer = csv.writer(f)
writer.writerows(all_results)
print(f"\nРезультаты сохранены в файл: {csv_filename}")
# Сохраняем также средние значения для удобства
avg_results = []
avg_results.append(["Структура", "Режим", "Операция", "Среднее время (сек)", "Мин", "Макс"])
# Группируем по структуре, режиму, операции
grouped = {}
for row in all_results[1:]: # пропускаем заголовок
struct, mode, op, rep, time_val = row
key = (struct, mode, op)
if key not in grouped:
grouped[key] = []
grouped[key].append(time_val)
for (struct, mode, op), times in grouped.items():
avg_time = sum(times) / len(times)
min_time = min(times)
max_time = max(times)
avg_results.append([struct, mode, op, avg_time, min_time, max_time])
avg_filename = f"average_results_{timestamp}.csv"
with open(avg_filename, 'w', newline='', encoding='utf-8') as f:
writer = csv.writer(f)
writer.writerows(avg_results)
print(f"Средние значения сохранены в файл: {avg_filename}")
return all_results, avg_results
if __name__ == "__main__":
print("Начало эксперимента...")
print("="*50)
results, avg_results = run_experiment()
print("="*50)
print("Эксперимент завершен!")