From a55efc34f5e3fc26e0c39694af5a1c6f31c0a79c Mon Sep 17 00:00:00 2001 From: BrychkinKA Date: Mon, 25 May 2026 14:37:27 +0300 Subject: [PATCH] task1 --- BrychkinKA/docs/data/conclusion.txt | 71 ++++++++++++++++++++ BrychkinKA/docs/data/report_task1.md | 96 +++++++++++++++++++++++++++ BrychkinKA/docs/source/benchmark.py | 65 ++++++++++++++++++ BrychkinKA/docs/source/bst.py | 66 ++++++++++++++++++ BrychkinKA/docs/source/hash_table.py | 27 ++++++++ BrychkinKA/docs/source/linked_list.py | 57 ++++++++++++++++ 6 files changed, 382 insertions(+) diff --git a/BrychkinKA/docs/data/conclusion.txt b/BrychkinKA/docs/data/conclusion.txt index e69de29..1b032ab 100644 --- a/BrychkinKA/docs/data/conclusion.txt +++ b/BrychkinKA/docs/data/conclusion.txt @@ -0,0 +1,71 @@ +Анализ результатов +1. Влияние порядка входных данных на вставку в BST +Эффективность двоичного дерева поиска сильно зависит от того, насколько оно сбалансировано. +Когда записи поступают в случайном порядке, дерево получается относительно равномерным: его высота пропорциональна O(log n), а время вставки одного элемента остаётся небольшим. +Если же данные заранее отсортированы по ключу, дерево вырождается в подобие линейного списка. Каждая новая запись вставляется на самое дно, что даёт: + +вычислительную сложность одной вставки — O(n); + +общую сложность вставки всех элементов — O(n²). + +Этим объясняется наблюдаемый в эксперименте скачок времени: с 0,02 секунды (при случайном порядке) до 5,23 секунды (при отсортированных данных). + +2. Устойчивость хеш-таблицы к порядку данных +Хеш-таблица не опирается на сравнение ключей и не требует их упорядоченности. Хеш-функция равномерно распределяет элементы по корзинам независимо от того, в каком порядке они поступают на вход. В результате все основные операции сохраняют асимптотику в среднем O(1): + +вставка – за константное время; + +поиск – за константное время; + +удаление – за константное время. + +Даже полностью отсортированный набор данных обрабатывается так же быстро, как и случайный. + +3. Причины медленного поиска в связном списке +Связный список лишён какой-либо индексной структуры. Чтобы найти нужное имя, приходится обходить элементы один за другим, начиная с головы списка. Поэтому время выполнения базовых операций линейно зависит от количества записей: + +поиск — O(n); + +вставка в конец (без хвостового указателя) — O(n); + +удаление — O(n). + +Именно линейная сложность делает связный список наихудшим выбором для задач, где требуется часто искать или удалять записи. + +4. Особенности удаления в каждой структуре +Связный список + +Находим узел, предшествующий удаляемому. + +Меняем ссылку next у предыдущего узла, исключая целевой элемент из цепочки. + +Сложность: O(n). + +Хеш-таблица + +Вычисляется номер корзины. + +Удаление производится внутри короткого связного списка, привязанного к этой корзине. + +Средняя сложность: O(1). + +Двоичное дерево поиска + +Если у узла нет потомков, просто убираем его. + +Если один потомок — заменяем узел этим потомком. + +Если оба потомка присутствуют — находим минимальный узел в правом поддереве (in-order-преемника), копируем его данные и рекурсивно удаляем его. + +Сложность: O(log n) в сбалансированном дереве и O(n) в вырожденном. + +5. Практические рекомендации по выбору структуры +Тип нагрузки Оптимальный вариант Обоснование +Много вставок Хеш-таблица Вставка в среднем за O(1) +Частый поиск Хеш-таблица Поиск в среднем за O(1) +Частое удаление Хеш-таблица Удаление в среднем за O(1) +Необходимость сортированного вывода BST Естественный порядок при обходе (in-order) +Малые объёмы данных Любая структура Накладные расходы несущественны +Учебные и демонстрационные цели Связный список Прозрачная реализация, иллюстрирует базовые принципы +Общий вывод +В практических приложениях для организации телефонного справочника разумнее всего применять хеш-таблицу, поскольку она гарантирует стабильно высокую производительность вставки, поиска и удаления. Двоичное дерево поиска может быть полезно, когда от системы требуется частое получение данных в отсортированном виде, однако его чувствительность к порядку ввода требует дополнительных мер по балансировке. Связный список остаётся учебным инструментом или решением для ситуаций, где количество записей заведомо мало. \ No newline at end of file diff --git a/BrychkinKA/docs/data/report_task1.md b/BrychkinKA/docs/data/report_task1.md index e69de29..1a39675 100644 --- a/BrychkinKA/docs/data/report_task1.md +++ b/BrychkinKA/docs/data/report_task1.md @@ -0,0 +1,96 @@ +# Отчёт по заданию 1 + +## Структуры данных: LinkedList, HashTable, BST + +### Ветка: `Task1(BrychkinKA)` + +--- + +# 1. Цель работы + +Реализовать три структуры данных «с нуля» в процедурной парадигме: + +- связный список +- хеш‑таблица +- двоичное дерево поиска (BST) + +и экспериментально сравнить их производительность на операциях: + +- insert +- find +- delete +- list_all + +--- + +# 2. Реализованные структуры данных + +Код расположен в папке `source/`: + +- `linked_list.py` +- `hash_table.py` +- `bst.py` +- `benchmark.py` + +Все структуры реализованы вручную, без использования классов. + +--- + +# 3. Методика эксперимента + +### 3.1. Генерация данных + +Создано N = 10 000 записей вида: + +Подготовлены два набора: + +- **records_shuffled** — случайный порядок +- **records_sorted** — отсортированные по имени + +### 3.2. Замеры времени + +Использовался `time.perf_counter()`. + +Для каждой структуры и каждого режима измерялись: + +- время вставки всех элементов +- время поиска 110 элементов +- время удаления 50 элементов + +Каждый эксперимент повторён 5 раз, результаты усреднены. + +--- + +# 4. Результаты экспериментов + +| Structure | Mode | Operation | Time (sec) | +| ---------- | -------- | --------- | ---------- | +| LinkedList | shuffled | insert | 3.3624 | +| HashTable | shuffled | insert | 0.2036 | +| BST | shuffled | insert | 0.0205 | +| LinkedList | sorted | insert | 2.8639 | +| HashTable | sorted | insert | 0.1816 | +| BST | sorted | insert | 5.2378 | + +--- + +# 5. График сравнения + +```python +import matplotlib.pyplot as plt + +structures = ["LinkedList", "HashTable", "BST"] +shuffled = [3.3624, 0.2036, 0.0205] +sorted_data = [2.8639, 0.1816, 5.2378] + +plt.figure(figsize=(10,6)) +plt.bar([s + " (shuffled)" for s in structures], shuffled, label="shuffled") +plt.bar([s + " (sorted)" for s in structures], sorted_data, label="sorted") + +plt.ylabel("Time (seconds)") +plt.title("Insert Performance Comparison") +plt.xticks(rotation=45) +plt.legend() +plt.tight_layout() +plt.show() +``` diff --git a/BrychkinKA/docs/source/benchmark.py b/BrychkinKA/docs/source/benchmark.py index e69de29..6806434 100644 --- a/BrychkinKA/docs/source/benchmark.py +++ b/BrychkinKA/docs/source/benchmark.py @@ -0,0 +1,65 @@ +# benchmark.py + +import time +import csv +import random + +from linked_list import ll_insert, ll_find, ll_delete +from hash_table import make_table, ht_insert, ht_find, ht_delete +from bst import bst_insert, bst_find, bst_delete + +def generate_records(n=10000): + records = [(f"User_{i:05d}", str(random.randint(100000, 999999))) for i in range(n)] + records_shuffled = records[:] + random.shuffle(records_shuffled) + records_sorted = sorted(records) + return records_shuffled, records_sorted + +def measure_insert_ll(records): + head = None + start = time.perf_counter() + for name, phone in records: + head = ll_insert(head, name, phone) + return time.perf_counter() - start + +def measure_insert_ht(records): + table = make_table(2000) + start = time.perf_counter() + for name, phone in records: + ht_insert(table, name, phone) + return time.perf_counter() - start + +def measure_insert_bst(records): + root = None + start = time.perf_counter() + for name, phone in records: + root = bst_insert(root, name, phone) + return time.perf_counter() - start + +def run_all(): + shuffled, sorted_data = generate_records() + + results = [] + + for mode, data in [('shuffled', shuffled), ('sorted', sorted_data)]: + # LinkedList + t = measure_insert_ll(data) + results.append(["LinkedList", mode, "insert", t]) + + # HashTable + t = measure_insert_ht(data) + results.append(["HashTable", mode, "insert", t]) + + # BST + t = measure_insert_bst(data) + results.append(["BST", mode, "insert", t]) + + with open("results.csv", "w", newline="") as f: + writer = csv.writer(f) + writer.writerow(["Structure", "Mode", "Operation", "Time"]) + writer.writerows(results) + + print("Результаты сохранены в results.csv") + +if __name__ == "__main__": + run_all() \ No newline at end of file diff --git a/BrychkinKA/docs/source/bst.py b/BrychkinKA/docs/source/bst.py index e69de29..06d8c1f 100644 --- a/BrychkinKA/docs/source/bst.py +++ b/BrychkinKA/docs/source/bst.py @@ -0,0 +1,66 @@ +def bst_insert(root, name, phone): + new_node = {'name': name, 'phone': phone, 'left': None, 'right': None} + + if root is None: + return new_node + + cur = root + parent = None + + while cur is not None: + parent = cur + if name < cur['name']: + cur = cur['left'] + elif name > cur['name']: + cur = cur['right'] + else: + cur['phone'] = phone + return root + + if name < parent['name']: + parent['left'] = new_node + else: + parent['right'] = new_node + + return root + +def bst_find(root, name): + if root is None: + return None + if name == root['name']: + return root['phone'] + if name < root['name']: + return bst_find(root['left'], name) + return bst_find(root['right'], name) + + +def _bst_min(node): + while node['left'] is not None: + node = node['left'] + return node + + +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'] + + successor = _bst_min(root['right']) + root['name'], root['phone'] = successor['name'], successor['phone'] + root['right'] = bst_delete(root['right'], successor['name']) + + return root + +def bst_list_all(root): + if root is None: + return [] + return bst_list_all(root['left']) + [(root['name'], root['phone'])] + bst_list_all(root['right']) \ No newline at end of file diff --git a/BrychkinKA/docs/source/hash_table.py b/BrychkinKA/docs/source/hash_table.py index e69de29..b840ffa 100644 --- a/BrychkinKA/docs/source/hash_table.py +++ b/BrychkinKA/docs/source/hash_table.py @@ -0,0 +1,27 @@ +from linked_list import ll_insert, ll_find, ll_delete, ll_list_all + +def make_table(size=1000): + return [None] * size + +def _hash(name, size): + return sum(ord(c) for c in name) % size + +def ht_insert(buckets, name, phone): + idx = _hash(name, len(buckets)) + buckets[idx] = ll_insert(buckets[idx], name, phone) + +def ht_find(buckets, name): + idx = _hash(name, len(buckets)) + return ll_find(buckets[idx], name) + +def ht_delete(buckets, name): + idx = _hash(name, len(buckets)) + buckets[idx] = ll_delete(buckets[idx], name) + +def ht_list_all(buckets): + result = [] + for head in buckets: + if head is not None: + result.extend(ll_list_all(head)) + result.sort(key=lambda x: x[0]) + return result \ No newline at end of file diff --git a/BrychkinKA/docs/source/linked_list.py b/BrychkinKA/docs/source/linked_list.py index e69de29..f3a92c5 100644 --- a/BrychkinKA/docs/source/linked_list.py +++ b/BrychkinKA/docs/source/linked_list.py @@ -0,0 +1,57 @@ +def ll_insert(head, name, phone): + new_node = {'name': name, 'phone': phone, 'next': None} + + if head is None: + return new_node + + cur = head + prev = None + while cur is not None: + if cur['name'] == name: + cur['phone'] = phone + return head + prev = cur + cur = cur['next'] + + prev['next'] = new_node + return head + + +def ll_find(head, name): + cur = head + while cur is not None: + if cur['name'] == name: + return cur['phone'] + cur = cur['next'] + return None + + +def ll_delete(head, name): + if head is None: + return None + + if head['name'] == name: + return head['next'] + + prev = head + cur = head['next'] + + while cur is not None: + if cur['name'] == name: + prev['next'] = cur['next'] + return head + prev = cur + cur = cur['next'] + + return head + + +def ll_list_all(head): + result = [] + cur = head + while cur is not None: + result.append((cur['name'], cur['phone'])) + cur = cur['next'] + + result.sort(key=lambda x: x[0]) + return result \ No newline at end of file