diff --git a/MochalovAE/docs/data/1.py/benchmark.py b/MochalovAE/docs/data/1.py/benchmark.py new file mode 100644 index 0000000..d7ebaef --- /dev/null +++ b/MochalovAE/docs/data/1.py/benchmark.py @@ -0,0 +1,405 @@ +import time +import random +import csv +import os + +def create_node(name, phone): + return {'name': name, 'phone': phone, 'next': None} + +def ll_insert(head, name, phone): + if head is None: + return create_node(name, phone) + + if head['name'] == name: + head['phone'] = phone + return head + + current = head + while current['next'] is not None: + if current['next']['name'] == name: + current['next']['phone'] = phone + return head + current = current['next'] + + current['next'] = create_node(name, phone) + return head + +def ll_find(head, name): + 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: + return head['next'] + + current = head + while current['next'] is not None: + if current['next']['name'] == name: + current['next'] = current['next']['next'] + return head + current = current['next'] + + return head + +def ll_list_all(head): + records = [] + current = head + while current is not None: + records.append((current['name'], current['phone'])) + current = current['next'] + + records.sort(key=lambda x: x[0]) + return records + +def hash_function(name, table_size): + hash_value = 0 + for char in name: + hash_value = (hash_value * 31 + ord(char)) % table_size + return hash_value + +def create_hash_table(size=1000): + return [None] * size + +def ht_insert(buckets, name, phone): + index = hash_function(name, len(buckets)) + 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: + if bucket is not None: + records = ll_list_all(bucket) + all_records.extend(records) + + all_records.sort(key=lambda x: x[0]) + return all_records + +def bst_create_node(name, phone): + return { + 'name': name, + 'phone': phone, + 'left': None, + 'right': None + } + +def bst_insert(root, name, phone): + if root is None: + return bst_create_node(name, phone) + + current = root + while True: + if name < current['name']: + if current['left'] is None: + current['left'] = bst_create_node(name, phone) + return root + current = current['left'] + elif name > current['name']: + if current['right'] is None: + current['right'] = bst_create_node(name, phone) + return root + current = current['right'] + else: + current['phone'] = phone + return root + +def bst_find(root, name): + current = root + while current is not None: + if name == current['name']: + return current['phone'] + elif name < current['name']: + current = current['left'] + else: + current = current['right'] + return None + +def bst_find_min(node): + current = node + while current['left'] is not None: + current = current['left'] + return current + +def bst_delete(root, name): + if root is None: + return None + + if name < root['name']: + root['left'] = bst_delete(root['left'], name) + return root + elif name > root['name']: + root['right'] = bst_delete(root['right'], name) + return root + + if root['left'] is None: + return root['right'] + elif root['right'] is None: + return root['left'] + + min_node = bst_find_min(root['right']) + root['name'] = min_node['name'] + root['phone'] = min_node['phone'] + root['right'] = bst_delete(root['right'], min_node['name']) + + return root + +def bst_inorder_collect(root, records): + stack = [] + current = root + while stack or current: + while current is not None: + stack.append(current) + current = current['left'] + current = stack.pop() + records.append((current['name'], current['phone'])) + current = current['right'] + +def bst_list_all(root): + records = [] + bst_inorder_collect(root, records) + return records + +def generate_test_data(n=1000): + names = [] + for i in range(n): + name = f"User_{i:06d}" + phone = f"+7-999-{random.randint(1000000, 9999999)}" + names.append((name, phone)) + + shuffled = names.copy() + random.shuffle(shuffled) + + sorted_records = sorted(names, key=lambda x: x[0]) + + return shuffled, sorted_records + +def run_insert_benchmark(struct_type, data, struct_params=None): + if struct_type == "LinkedList": + head = None + start = time.perf_counter() + for name, phone in data: + head = ll_insert(head, name, phone) + end = time.perf_counter() + return end - start, head + + elif struct_type == "HashTable": + size = struct_params.get('size', 1000) if struct_params else 1000 + buckets = create_hash_table(size) + start = time.perf_counter() + for name, phone in data: + buckets = ht_insert(buckets, name, phone) + end = time.perf_counter() + return end - start, buckets + + elif struct_type == "BST": + root = None + start = time.perf_counter() + for name, phone in data: + root = bst_insert(root, name, phone) + end = time.perf_counter() + return end - start, root + +def run_find_benchmark(struct, struct_type, existing_names, non_existing_names): + start = time.perf_counter() + + for name in existing_names: + if struct_type == "LinkedList": + result = ll_find(struct, name) + elif struct_type == "HashTable": + result = ht_find(struct, name) + elif struct_type == "BST": + result = bst_find(struct, name) + + for name in non_existing_names: + if struct_type == "LinkedList": + result = ll_find(struct, name) + elif struct_type == "HashTable": + result = ht_find(struct, name) + elif struct_type == "BST": + result = bst_find(struct, name) + + end = time.perf_counter() + return end - start + +def run_delete_benchmark(struct, struct_type, names_to_delete): + start = time.perf_counter() + + for name in names_to_delete: + if struct_type == "LinkedList": + struct = ll_delete(struct, name) + elif struct_type == "HashTable": + struct = ht_delete(struct, name) + elif struct_type == "BST": + struct = bst_delete(struct, name) + + end = time.perf_counter() + return end - start, struct + +def run_experiment(n_records=1000, n_find=100, n_delete=50, n_repeats=3): + print("Генерация тестовых данных...") + shuffled_data, sorted_data = generate_test_data(n_records) + + all_names = [name for name, _ in shuffled_data] + find_names = random.sample(all_names, min(n_find, len(all_names))) + delete_names = random.sample(all_names, min(n_delete, len(all_names))) + + non_existing = [f"None_{i}" for i in range(10)] + + results = [] + + structures = ["LinkedList", "HashTable", "BST"] + modes = ["random", "sorted"] + + print("\nНачало эксперимента...") + print("=" * 80) + + for struct_type in structures: + for mode in modes: + data = shuffled_data if mode == "random" else sorted_data + mode_rus = "случайный" if mode == "random" else "отсортированный" + + print(f"\nТестирование: {struct_type}, режим: {mode_rus}") + + for repeat in range(n_repeats): + print(f" Повторение {repeat + 1}/{n_repeats}") + + insert_time, struct = run_insert_benchmark( + struct_type, data, + {'size': n_records} if struct_type == "HashTable" else None + ) + results.append([ + struct_type, mode_rus, "вставка", repeat + 1, insert_time + ]) + + find_time = run_find_benchmark( + struct, struct_type, find_names, non_existing + ) + results.append([ + struct_type, mode_rus, "поиск", repeat + 1, find_time + ]) + + delete_time, struct = run_delete_benchmark( + struct, struct_type, delete_names + ) + results.append([ + struct_type, mode_rus, "удаление", repeat + 1, delete_time + ]) + + return results + +def save_results_to_csv(results, filename="docs/data/results.csv"): + os.makedirs("docs/data", exist_ok=True) + + with open(filename, 'w', newline='', encoding='utf-8') as f: + writer = csv.writer(f) + writer.writerow(["Структура", "Режим", "Операция", "Повторение", "Время (сек)"]) + writer.writerows(results) + + print(f"\nРезультаты сохранены в {filename}") + +def print_statistics(results): + print("\n" + "=" * 80) + print("СТАТИСТИКА РЕЗУЛЬТАТОВ") + print("=" * 80) + + stats = {} + for row in results: + struct, mode, op, _, time_val = row + key = (struct, mode, op) + if key not in stats: + stats[key] = [] + stats[key].append(time_val) + + for (struct, mode, op), times in stats.items(): + avg_time = sum(times) / len(times) + min_time = min(times) + max_time = max(times) + + print(f"\n{struct} - {mode} - {op}:") + print(f" Среднее: {avg_time:.6f} сек") + print(f" Мин: {min_time:.6f} сек") + print(f" Макс: {max_time:.6f} сек") + +def verify_correctness(): + print("\n" + "=" * 80) + print("ПРОВЕРКА КОРРЕКТНОСТИ РАБОТЫ") + print("=" * 80) + + test_data = [ + ("Alice", "123-456"), + ("Bob", "789-012"), + ("Charlie", "345-678"), + ("Alice", "999-999"), + ] + + structures = { + "LinkedList": (None, ll_insert, ll_find, ll_delete, ll_list_all), + "HashTable": (create_hash_table(10), ht_insert, ht_find, ht_delete, ht_list_all), + "BST": (None, bst_insert, bst_find, bst_delete, bst_list_all) + } + + for name, (struct, insert_func, find_func, delete_func, list_func) in structures.items(): + print(f"\n{name}:") + + for n, p in test_data: + struct = insert_func(struct, n, p) + + print(f" Поиск Alice: {find_func(struct, 'Alice')}") + print(f" Поиск Bob: {find_func(struct, 'Bob')}") + print(f" Поиск Unknown: {find_func(struct, 'Unknown')}") + + struct = delete_func(struct, "Bob") + print(f" После удаления Bob: {find_func(struct, 'Bob')}") + + all_records = list_func(struct) + print(f" Все записи: {all_records}") + +def main(): + print("ТЕЛЕФОННЫЙ СПРАВОЧНИК - СРАВНЕНИЕ СТРУКТУР ДАННЫХ") + print("=" * 80) + + verify_correctness() + + print("\n" + "=" * 80) + print("ЗАПУСК ЭКСПЕРИМЕНТА") + print("=" * 80) + + N_RECORDS = 1000 + N_FIND = 100 + N_DELETE = 50 + N_REPEATS = 3 + + print(f"\nПараметры эксперимента:") + print(f" Количество записей: {N_RECORDS}") + print(f" Поиск: {N_FIND} существующих + 10 отсутствующих") + print(f" Удаление: {N_DELETE} записей") + print(f" Повторений: {N_REPEATS}") + + results = run_experiment(N_RECORDS, N_FIND, N_DELETE, N_REPEATS) + + save_results_to_csv(results) + + print_statistics(results) + + print("\n" + "=" * 80) + print("ЭКСПЕРИМЕНТ ЗАВЕРШЕН") + print("=" * 80) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/MochalovAE/docs/data/1.py/bst_phonebook.py b/MochalovAE/docs/data/1.py/bst_phonebook.py new file mode 100644 index 0000000..89c18fb --- /dev/null +++ b/MochalovAE/docs/data/1.py/bst_phonebook.py @@ -0,0 +1,70 @@ + +def bst_create_node(name, phone): + return { + 'name': name, + 'phone': phone, + 'left': None, + 'right': None + } + +def bst_insert(root, name, phone): + if root is None: + return bst_create_node(name, phone) + + 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 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['left'] is not None: + current = current['left'] + return current + +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'] + elif root['right'] is None: + return root['left'] + + min_node = bst_find_min(root['right']) + root['name'] = min_node['name'] + root['phone'] = min_node['phone'] + root['right'] = bst_delete(root['right'], min_node['name']) + + return root + +def bst_inorder_collect(root, records): + if root is not None: + bst_inorder_collect(root['left'], records) + records.append((root['name'], root['phone'])) + bst_inorder_collect(root['right'], records) + +def bst_list_all(root): + records = [] + bst_inorder_collect(root, records) + return records \ No newline at end of file diff --git a/MochalovAE/docs/data/1.py/hash_table_phonebook.py b/MochalovAE/docs/data/1.py/hash_table_phonebook.py new file mode 100644 index 0000000..d953640 --- /dev/null +++ b/MochalovAE/docs/data/1.py/hash_table_phonebook.py @@ -0,0 +1,35 @@ + +from src.linked_list_phonebook import ll_insert, ll_find, ll_delete, ll_list_all + +def hash_function(name, table_size): + hash_value = 0 + for char in name: + hash_value = (hash_value * 31 + ord(char)) % table_size + return hash_value + +def create_hash_table(size=1000): + return [None] * size + +def ht_insert(buckets, name, phone): + index = hash_function(name, len(buckets)) + 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: + if bucket is not None: + records = ll_list_all(bucket) + all_records.extend(records) + + all_records.sort(key=lambda x: x[0]) + return all_records \ No newline at end of file diff --git a/MochalovAE/docs/data/1.py/linked_list_phonebook.py b/MochalovAE/docs/data/1.py/linked_list_phonebook.py new file mode 100644 index 0000000..166f199 --- /dev/null +++ b/MochalovAE/docs/data/1.py/linked_list_phonebook.py @@ -0,0 +1,55 @@ + +def create_node(name, phone): + return {'name': name, 'phone': phone, 'next': None} + +def ll_insert(head, name, phone): + if head is None: + return create_node(name, phone) + + if head['name'] == name: + head['phone'] = phone + return head + + current = head + while current['next'] is not None: + if current['next']['name'] == name: + current['next']['phone'] = phone + return head + current = current['next'] + + current['next'] = create_node(name, phone) + return head + +def ll_find(head, name): + 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: + return head['next'] + + current = head + while current['next'] is not None: + if current['next']['name'] == name: + current['next'] = current['next']['next'] + return head + current = current['next'] + + return head + +def ll_list_all(head): + records = [] + current = head + while current is not None: + records.append((current['name'], current['phone'])) + current = current['next'] + + records.sort(key=lambda x: x[0]) + return records \ No newline at end of file diff --git a/MochalovAE/docs/data/1.py/plot_results.py b/MochalovAE/docs/data/1.py/plot_results.py new file mode 100644 index 0000000..e3e3a7d --- /dev/null +++ b/MochalovAE/docs/data/1.py/plot_results.py @@ -0,0 +1,160 @@ +import matplotlib.pyplot as plt +import csv +import numpy as np +import os + +plt.rcParams['font.size'] = 10 +plt.rcParams['font.family'] = 'sans-serif' + +def plot_results(csv_file="docs/data/results.csv"): + if not os.path.exists(csv_file): + print(f"Файл {csv_file} не найден. Сначала запустите benchmark.py") + return + + data = {} + + with open(csv_file, 'r', encoding='utf-8') as f: + reader = csv.reader(f) + next(reader) + for row in reader: + if len(row) < 5: + continue + struct, mode, op, _, time_val = row + try: + time_val = float(time_val) + except: + continue + + if struct not in data: + data[struct] = {} + if mode not in data[struct]: + data[struct][mode] = {} + if op not in data[struct][mode]: + data[struct][mode][op] = [] + data[struct][mode][op].append(time_val) + + operations = ['вставка', 'поиск', 'удаление'] + op_names = ['Вставка', 'Поиск', 'Удаление'] + structures = ['LinkedList', 'HashTable', 'BST'] + modes = ['случайный', 'отсортированный'] + colors = {'LinkedList': '#3498db', 'HashTable': '#2ecc71', 'BST': '#e74c3c'} + + fig, axes = plt.subplots(1, 3, figsize=(14, 5)) + fig.suptitle('Сравнение производительности структур данных\n(500 записей, 3 повторения)', fontsize=14, fontweight='bold') + + for idx, (op, op_name) in enumerate(zip(operations, op_names)): + ax = axes[idx] + x_positions = [] + labels = [] + values = [] + errors = [] + colors_list = [] + + position = 0 + for struct in structures: + for mode in modes: + if struct in data and mode in data[struct] and op in data[struct][mode]: + times = data[struct][mode][op] + if times: + avg_time = np.mean(times) + std_time = np.std(times) if len(times) > 1 else 0 + + x_positions.append(position) + labels.append(f'{struct}\n({mode[:4]})') + values.append(avg_time) + errors.append(std_time) + colors_list.append(colors[struct]) + position += 1 + + bars = ax.bar(x_positions, values, yerr=errors, capsize=5, color=colors_list, alpha=0.8, edgecolor='black', linewidth=0.5) + ax.set_xticks(x_positions) + ax.set_xticklabels(labels, fontsize=8, rotation=45, ha='right') + ax.set_ylabel('Время (секунды)', fontsize=10) + ax.set_title(f'{op_name}', fontsize=12, fontweight='bold') + ax.grid(True, alpha=0.3, axis='y') + + for bar, val in zip(bars, values): + if val > 0: + height = bar.get_height() + ax.text(bar.get_x() + bar.get_width()/2., height + max(values)*0.02, + f'{val:.4f}', ha='center', va='bottom', fontsize=7, rotation=0) + + plt.tight_layout() + plt.savefig('docs/data/performance_graphs.png', dpi=300, bbox_inches='tight') + plt.show() + + fig2, ax2 = plt.subplots(figsize=(10, 6)) + fig2.suptitle('Влияние порядка данных на производительность BST', fontsize=14, fontweight='bold') + + if 'BST' in data: + bst_data = data['BST'] + x_pos = [] + labels = [] + random_vals = [] + sorted_vals = [] + + for idx, op in enumerate(operations): + random_time = 0 + sorted_time = 0 + + if 'случайный' in bst_data and op in bst_data['случайный']: + random_time = np.mean(bst_data['случайный'][op]) + if 'отсортированный' in bst_data and op in bst_data['отсортированный']: + sorted_time = np.mean(bst_data['отсортированный'][op]) + + x_pos.append(idx) + x_pos.append(idx + 0.35) + labels.append(op) + random_vals.append(random_time) + sorted_vals.append(sorted_time) + + width = 0.35 + bars1 = ax2.bar([i - width/2 for i in range(len(operations))], random_vals, width, + label='Случайный порядок', color='#2ecc71', alpha=0.8, edgecolor='black') + bars2 = ax2.bar([i + width/2 for i in range(len(operations))], sorted_vals, width, + label='Отсортированный порядок', color='#e74c3c', alpha=0.8, edgecolor='black') + + ax2.set_xticks(range(len(operations))) + ax2.set_xticklabels(['Вставка', 'Поиск', 'Удаление'], fontsize=10) + ax2.set_ylabel('Время (секунды)', fontsize=10) + ax2.set_xlabel('Операция', fontsize=10) + ax2.legend(fontsize=10) + ax2.grid(True, alpha=0.3, axis='y') + + for bar, val in zip(bars1, random_vals): + if val > 0: + ax2.text(bar.get_x() + bar.get_width()/2., bar.get_height() + max(random_vals + sorted_vals)*0.02, + f'{val:.4f}', ha='center', va='bottom', fontsize=8) + + for bar, val in zip(bars2, sorted_vals): + if val > 0: + ax2.text(bar.get_x() + bar.get_width()/2., bar.get_height() + max(random_vals + sorted_vals)*0.02, + f'{val:.4f}', ha='center', va='bottom', fontsize=8) + + plt.tight_layout() + plt.savefig('docs/data/bst_comparison.png', dpi=300, bbox_inches='tight') + plt.show() + + print("\n" + "="*60) + print("ИТОГОВАЯ ТАБЛИЦА РЕЗУЛЬТАТОВ") + print("="*60) + print(f"{'Структура':<15} {'Режим':<12} {'Вставка':<12} {'Поиск':<12} {'Удаление':<12}") + print("-"*60) + + for struct in structures: + for mode in modes: + if struct in data and mode in data[struct]: + insert_times = data[struct][mode].get('вставка', [0]) + find_times = data[struct][mode].get('поиск', [0]) + delete_times = data[struct][mode].get('удаление', [0]) + + insert_avg = np.mean(insert_times) if insert_times else 0 + find_avg = np.mean(find_times) if find_times else 0 + delete_avg = np.mean(delete_times) if delete_times else 0 + + print(f"{struct:<15} {mode:<12} {insert_avg:<12.6f} {find_avg:<12.6f} {delete_avg:<12.6f}") + + print("="*60) + +if __name__ == "__main__": + plot_results() \ No newline at end of file