diff --git a/groshevava/docs/data/bst.py b/groshevava/docs/data/bst.py new file mode 100644 index 0000000..9dd503c --- /dev/null +++ b/groshevava/docs/data/bst.py @@ -0,0 +1,153 @@ +#реализация справочника на основе бинарного дерева поиска (BST). + +#создаём узел +def bst_create_node(name, phone): + return { + 'name': name, + 'phone': phone, + 'left': None, + 'right': None + } + +#Вставляет запись в BST или обновляет.Возвращает корень дерева +def bst_insert(root, name, phone): + new_node = bst_create_node(name, phone) + + if root is None: + return new_node + + current = root + parent = None + + while current is not None: + parent = current + if name < current['name']: + current = current['left'] + elif name > current['name']: + current = current['right'] + else: + current['phone'] = phone + return root + + #вставляем новый узел + if name < parent['name']: + parent['left'] = new_node + else: + parent['right'] = new_node + + return root + +#ищет запись в BST по имени. Возвращает телефон или None +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 + +#удаляет запись из BST по имени. Возвращает новый корень дерева +def bst_delete(root, name): + if root is None: + return None + + parent = None + current = root + + while current is not None and current['name'] != name: + parent = current + if name < current['name']: + current = current['left'] + else: + current = current['right'] + + if current is None: + return root + + if current['left'] is None and current['right'] is None: + if parent is None: + return None + elif parent['left'] == current: + parent['left'] = None + else: + parent['right'] = None + return root + + if current['left'] is None: + if parent is None: + return current['right'] + elif parent['left'] == current: + parent['left'] = current['right'] + else: + parent['right'] = current['right'] + return root + + if current['right'] is None: + if parent is None: + return current['left'] + elif parent['left'] == current: + parent['left'] = current['left'] + else: + parent['right'] = current['left'] + return root + + successor_parent = current + successor = current['right'] + + while successor['left'] is not None: + successor_parent = successor + successor = successor['left'] + + current['name'] = successor['name'] + current['phone'] = successor['phone'] + + if successor_parent == current: + successor_parent['right'] = successor['right'] + else: + successor_parent['left'] = successor['right'] + + return root + +#рекурсивно собирает записи дерева по возрастанию имён +def _bst_in_order_collect(node, records): + if node is not None: + _bst_in_order_collect(node['left'], records) + records.append((node['name'], node['phone'])) + _bst_in_order_collect(node['right'], records) + +#отсортированный список всех записей +def bst_list_all(root): + records = [] + # Для очень глубоких деревьев лучше использовать итеративный обход + _bst_in_order_iterative(root, records) + return records + +#центрированный обход дерева. +def _bst_in_order_iterative(root, records): + stack = [] + current = root + + while current is not None or len(stack) > 0: + # доходим до самого левого узла + while current is not None: + stack.append(current) + current = current['left'] + + # обрабатываем узел + current = stack.pop() + records.append((current['name'], current['phone'])) + + #переходим к правому поддереву + current = current['right'] \ No newline at end of file diff --git a/groshevava/docs/data/experiments.py b/groshevava/docs/data/experiments.py new file mode 100644 index 0000000..16d1e54 --- /dev/null +++ b/groshevava/docs/data/experiments.py @@ -0,0 +1,198 @@ +#проведение экспериментов и замер производительности + +import time +import random +import csv +from linked_list import ll_insert, ll_find, ll_delete, ll_list_all +from hash_table import ht_create, ht_insert, ht_find, ht_delete, ht_list_all +from bst import bst_insert, bst_find, bst_delete, bst_list_all + +#генерирует записи справочника. shuffled- случайный порядок, sorted-отсортированный по имени +def generate_test_data(n=10000, seed=42): + random.seed(seed) + + names = [f"User_{i:05d}" for i in range(n)] + phones = [f"+7-999-{i:07d}" for i in range(n)] + + records = list(zip(names, phones)) + + records_shuffled = records.copy() + random.shuffle(records_shuffled) + + records_sorted = sorted(records, key=lambda x: x[0]) + + return records_shuffled, records_sorted + +#замеряем время +def measure_insert(ll_structure, ht_structure, bst_structure, records, mode_name): + results = [] + + # Связный список + start = time.perf_counter() + head = None + for name, phone in records: + head = ll_insert(head, name, phone) + end = time.perf_counter() + results.append(["LinkedList", mode_name, "вставка", end - start]) + + # Хеш-таблица + start = time.perf_counter() + buckets = ht_create(256) + for name, phone in records: + ht_insert(buckets, name, phone) + end = time.perf_counter() + results.append(["HashTable", mode_name, "вставка", end - start]) + + # BST + start = time.perf_counter() + root = None + for name, phone in records: + root = bst_insert(root, name, phone) + end = time.perf_counter() + results.append(["BST", mode_name, "вставка", end - start]) + + return results, head, buckets, root + + +def measure_find(head, buckets, root, all_names, mode_name): + results = [] + + # Выбираем 100 случайных существующих и 10 несуществующих имён + existing_names = random.sample(all_names, 100) + non_existing_names = [f"None_{i}" for i in range(10)] + search_names = existing_names + non_existing_names + + # Связный список + start = time.perf_counter() + for name in search_names: + ll_find(head, name) + end = time.perf_counter() + results.append(["LinkedList", mode_name, "поиск", end - start]) + + # Хеш-таблица + start = time.perf_counter() + for name in search_names: + ht_find(buckets, name) + end = time.perf_counter() + results.append(["HashTable", mode_name, "поиск", end - start]) + + # BST + start = time.perf_counter() + for name in search_names: + bst_find(root, name) + end = time.perf_counter() + results.append(["BST", mode_name, "поиск", end - start]) + + return results + + +def measure_delete(head, buckets, root, all_names, mode_name): + results = [] + + # Выбираем 50 случайных имён для удаления + delete_names = random.sample(all_names, 50) + + # Связный список + start = time.perf_counter() + for name in delete_names: + head = ll_delete(head, name) + end = time.perf_counter() + results.append(["LinkedList", mode_name, "удаление", end - start]) + + # Хеш-таблица + start = time.perf_counter() + for name in delete_names: + ht_delete(buckets, name) + end = time.perf_counter() + results.append(["HashTable", mode_name, "удаление", end - start]) + + # BST + start = time.perf_counter() + for name in delete_names: + root = bst_delete(root, name) + end = time.perf_counter() + results.append(["BST", mode_name, "удаление", end - start]) + + return results + +#проводим эксперименты +def run_experiments(records_shuffled, records_sorted, repetitions=5): + + all_results = [ + ["Структура", "Режим", "Операция", "Время (сек)"] + ] + + all_names = [record[0] for record in records_shuffled] + + for rep in range(repetitions): + print(f"Повторение {rep + 1}/{repetitions}") + + # Шаффлированные данные + results, head, buckets, root = measure_insert( + None, None, None, records_shuffled, "случайный" + ) + all_results.extend(results) + + results = measure_find(head, buckets, root, all_names, "случайный") + all_results.extend(results) + + results = measure_delete(head, buckets, root, all_names, "случайный") + all_results.extend(results) + + # Отсортированные данные + results, head, buckets, root = measure_insert( + None, None, None, records_sorted, "отсортированный" + ) + all_results.extend(results) + + results = measure_find(head, buckets, root, all_names, "отсортированный") + all_results.extend(results) + + results = measure_delete(head, buckets, root, all_names, "отсортированный") + all_results.extend(results) + + return all_results + + +def save_results(results, filename="results.csv"): + with open(filename, 'w', newline='', encoding='utf-8') as f: + writer = csv.writer(f) + writer.writerows(results) + print(f"Результаты сохранены в {filename}") + + +def analyze_results(filename="results.csv"): + from collections import defaultdict + + with open(filename, 'r', encoding='utf-8') as f: + reader = csv.reader(f) + next(reader) # пропускаем заголовок + data = list(reader) + + # Группируем для вычисления средних + stats = defaultdict(list) + for row in data: + structure, mode, operation, time_str = row + key = (structure, mode, operation) + stats[key].append(float(time_str)) + + print("\nСредние времена выполнения (сек):") + print("-" * 60) + print(f"{'Структура':<15} {'Режим':<20} {'Операция':<10} {'Время':<10}") + print("-" * 60) + + for (structure, mode, operation), times in sorted(stats.items()): + avg_time = sum(times) / len(times) + print(f"{structure:<15} {mode:<20} {operation:<10} {avg_time:<10.6f}") + + +if __name__ == "__main__": + print("Генерация тестовых данных...") + records_shuffled, records_sorted = generate_test_data(10000) + + print("Запуск экспериментов...") + results = run_experiments(records_shuffled, records_sorted, repetitions=5) + + save_results(results) + + analyze_results() \ No newline at end of file diff --git a/groshevava/docs/data/hash_table.py b/groshevava/docs/data/hash_table.py new file mode 100644 index 0000000..32e1020 --- /dev/null +++ b/groshevava/docs/data/hash_table.py @@ -0,0 +1,43 @@ +#Телефонного справочник на основе хеш-таблицы. +#Использует метод цепочек + +from linked_list import ll_insert, ll_find, ll_delete, ll_list_all + +#Хеш-функция для строки имени. + #Использует полиномиальное хеширование. +def ht_hash(name, bucket_count): + hash_value = 0 + p = 31 + for char in name: + hash_value = (hash_value * p + ord(char)) % bucket_count + return hash_value + +#Создаёт пустую таблицу +def ht_create(bucket_count=128): + return [None] * bucket_count + +#Добавляет запись в таблицу. + #Вычисляет хэш, затем вставляет в нужный бакет. +def ht_insert(buckets, name, phone): + index = ht_hash(name, len(buckets)) + buckets[index] = ll_insert(buckets[index], name, phone) + +#ищет запись в таблице +def ht_find(buckets, name): + index = ht_hash(name, len(buckets)) + return ll_find(buckets[index], name) + +#удаляет запись +def ht_delete(buckets, name): + index = ht_hash(name, len(buckets)) + buckets[index] = ll_delete(buckets[index], name) + +#сортировка по имени +def ht_list_all(buckets): + all_records = [] + for bucket_head in buckets: + records = ll_list_all(bucket_head) + all_records.extend(records) + + all_records.sort(key=lambda x: x[0]) + return all_records \ No newline at end of file diff --git a/groshevava/docs/data/linked_list.py b/groshevava/docs/data/linked_list.py new file mode 100644 index 0000000..92de475 --- /dev/null +++ b/groshevava/docs/data/linked_list.py @@ -0,0 +1,64 @@ +#Телефонного справочник - связный список + +#создаёт новый узел +def ll_create_node(name, phone): + return {'name': name, 'phone': phone, 'next': None} + +#добавляет запись в конец списка или обновляет. +def ll_insert(head, name, phone): + new_node = ll_create_node(name, phone) + + if head is None: + return new_node + + if head['name'] == name: + new_node['next'] = head['next'] + return new_node + + current = head + while current['next'] is not None: + if current['next']['name'] == name: + new_node['next'] = current['next']['next'] + current['next'] = new_node + return head + current = current['next'] + + current['next'] = new_node + 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 + +#собирает все записи связного списка в список name-phone, сортирует по имени +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/groshevava/docs/data/results.csv b/groshevava/docs/data/results.csv new file mode 100644 index 0000000..0edb5fb --- /dev/null +++ b/groshevava/docs/data/results.csv @@ -0,0 +1,91 @@ +Структура,Режим,Операция,Время (сек) +LinkedList,случайный,вставка,7.0379601 +HashTable,случайный,вставка,0.06370939999999958 +BST,случайный,вставка,0.027558299999999925 +LinkedList,случайный,поиск,0.05380779999999952 +HashTable,случайный,поиск,0.0008359000000002226 +BST,случайный,поиск,0.00036180000000030077 +LinkedList,случайный,удаление,0.03279429999999994 +HashTable,случайный,удаление,0.00046319999999955286 +BST,случайный,удаление,0.0002371000000005452 +LinkedList,отсортированный,вставка,6.794934100000001 +HashTable,отсортированный,вставка,0.06352280000000121 +BST,отсортированный,вставка,6.0836668 +LinkedList,отсортированный,поиск,0.06371549999999715 +HashTable,отсортированный,поиск,0.0013053000000020631 +BST,отсортированный,поиск,0.05756839999999741 +LinkedList,отсортированный,удаление,0.038222600000000995 +HashTable,отсортированный,удаление,0.0011298000000010688 +BST,отсортированный,удаление,0.036374399999999696 +LinkedList,случайный,вставка,7.183893999999999 +HashTable,случайный,вставка,0.06642779999999959 +BST,случайный,вставка,0.025029599999999874 +LinkedList,случайный,поиск,0.05042710000000028 +HashTable,случайный,поиск,0.0008175000000001376 +BST,случайный,поиск,0.00032500000000013074 +LinkedList,случайный,удаление,0.04681619999999853 +HashTable,случайный,удаление,0.0006166999999983602 +BST,случайный,удаление,0.0002557000000003029 +LinkedList,отсортированный,вставка,7.153371900000003 +HashTable,отсортированный,вставка,0.05732309999999785 +BST,отсортированный,вставка,6.7899777999999955 +LinkedList,отсортированный,поиск,0.1498364000000052 +HashTable,отсортированный,поиск,0.0021344999999968195 +BST,отсортированный,поиск,0.08021600000000007 +LinkedList,отсортированный,удаление,0.04531419999999997 +HashTable,отсортированный,удаление,0.0005183999999971434 +BST,отсортированный,удаление,0.032904900000005455 +LinkedList,случайный,вставка,7.787066500000002 +HashTable,случайный,вставка,0.06794790000000006 +BST,случайный,вставка,0.028658900000003484 +LinkedList,случайный,поиск,0.055633000000000266 +HashTable,случайный,поиск,0.0011372000000022808 +BST,случайный,поиск,0.00041319999999700485 +LinkedList,случайный,удаление,0.04464529999999911 +HashTable,случайный,удаление,0.0006264000000015812 +BST,случайный,удаление,0.00025480000000044356 +LinkedList,отсортированный,вставка,7.047079400000001 +HashTable,отсортированный,вставка,0.07149469999999525 +BST,отсортированный,вставка,6.004278499999998 +LinkedList,отсортированный,поиск,0.059245700000005286 +HashTable,отсортированный,поиск,0.0008623000000014258 +BST,отсортированный,поиск,0.061085500000004345 +LinkedList,отсортированный,удаление,0.038738300000005665 +HashTable,отсортированный,удаление,0.00047229999999842676 +BST,отсортированный,удаление,0.03476669999999871 +LinkedList,случайный,вставка,7.814853800000002 +HashTable,случайный,вставка,0.06793270000000007 +BST,случайный,вставка,0.026476199999990513 +LinkedList,случайный,поиск,0.056167000000002076 +HashTable,случайный,поиск,0.0007876000000095473 +BST,случайный,поиск,0.0003126000000008844 +LinkedList,случайный,удаление,0.042319900000009625 +HashTable,случайный,удаление,0.000520099999988588 +BST,случайный,удаление,0.00023889999999937572 +LinkedList,отсортированный,вставка,7.297540700000013 +HashTable,отсортированный,вставка,0.06405399999999872 +BST,отсортированный,вставка,6.252882799999995 +LinkedList,отсортированный,поиск,0.058841400000005706 +HashTable,отсортированный,поиск,0.0008604000000076439 +BST,отсортированный,поиск,0.05284110000000908 +LinkedList,отсортированный,удаление,0.03360689999999522 +HashTable,отсортированный,удаление,0.00047010000000113905 +BST,отсортированный,удаление,0.02865070000000003 +LinkedList,случайный,вставка,7.937439900000001 +HashTable,случайный,вставка,0.06798210000000893 +BST,случайный,вставка,0.042214500000000044 +LinkedList,случайный,поиск,0.0645776000000069 +HashTable,случайный,поиск,0.0007535999999959131 +BST,случайный,поиск,0.0003451000000040949 +LinkedList,случайный,удаление,0.04297359999999628 +HashTable,случайный,удаление,0.0005001999999905138 +BST,случайный,удаление,0.00021639999999933934 +LinkedList,отсортированный,вставка,7.474806200000003 +HashTable,отсортированный,вставка,0.06952760000000069 +BST,отсортированный,вставка,6.475523199999998 +LinkedList,отсортированный,поиск,0.054521199999996384 +HashTable,отсортированный,поиск,0.0008888999999925318 +BST,отсортированный,поиск,0.049161900000001424 +LinkedList,отсортированный,удаление,0.03957100000000935 +HashTable,отсортированный,удаление,0.0004850999999916894 +BST,отсортированный,удаление,0.03728519999999946