From 744a44c5bcc03a2644517e185e58d881c508ad12 Mon Sep 17 00:00:00 2001 From: Anton_Viniuchuk Date: Sat, 23 May 2026 15:58:18 +0300 Subject: [PATCH 1/5] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB?= =?UTF-8?q?=20=D0=BA=D0=BE=D0=B4=20=D0=B7=D0=B0=D0=B4=D0=B0=D0=BD=D0=B8?= =?UTF-8?q?=D1=8F=201,=20=D0=BE=D1=82=D1=87=D1=91=D1=82=20=D0=B8=20=D1=80?= =?UTF-8?q?=D0=B5=D0=B7=D1=83=D0=BB=D1=8C=D1=82=D0=B0=D1=82=D1=8B=20=D0=B7?= =?UTF-8?q?=D0=B0=D0=BC=D0=B5=D1=80=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- vinichukan/427.md | 0 vinichukan/docs/data/ conclusion.md | 83 +++++++++++++++++++++++++ vinichukan/docs/report_task1.md | 95 +++++++++++++++++++++++++++++ vinichukan/src/benchmark.py | 65 ++++++++++++++++++++ vinichukan/src/bst.py | 66 ++++++++++++++++++++ vinichukan/src/hash_table.py | 27 ++++++++ vinichukan/src/linked_list.py | 57 +++++++++++++++++ vinichukan/src/results.csv | 7 +++ 8 files changed, 400 insertions(+) create mode 100644 vinichukan/427.md create mode 100644 vinichukan/docs/data/ conclusion.md create mode 100644 vinichukan/docs/report_task1.md create mode 100644 vinichukan/src/benchmark.py create mode 100644 vinichukan/src/bst.py create mode 100644 vinichukan/src/hash_table.py create mode 100644 vinichukan/src/linked_list.py create mode 100644 vinichukan/src/results.csv diff --git a/vinichukan/427.md b/vinichukan/427.md new file mode 100644 index 0000000..e69de29 diff --git a/vinichukan/docs/data/ conclusion.md b/vinichukan/docs/data/ conclusion.md new file mode 100644 index 0000000..b9cd6d7 --- /dev/null +++ b/vinichukan/docs/data/ conclusion.md @@ -0,0 +1,83 @@ +## Анализ результатов + +### **1. Как порядок входных данных влияет на скорость вставки в BST** + +BST работает быстро только если дерево сбалансировано. +При случайном порядке глубина дерева ≈ `O(log n)`, поэтому вставка быстрая. +Но при отсортированных данных дерево вырождается в цепочку. +Вставка становится: + +- **O(n)** на одну операцию +- **O(n²)** на вставку всех элементов + +Это приводит к резкому росту времени (в эксперименте: **0.02 сек → 5.23 сек**). + +--- + +### **2. Почему хеш‑таблица почти не чувствительна к порядку** + +Хеш‑функция распределяет элементы по бакетам независимо от порядка входа. +Поэтому: + +- вставка ≈ **O(1)** +- поиск ≈ **O(1)** +- удаление ≈ **O(1)** + +Даже если данные отсортированы, хеш‑таблица работает одинаково быстро. + +--- + +### **3. Почему связный список всегда медленный при поиске** + +Связный список не имеет индексов. +Поиск идёт последовательно: head → next → next → ... + +Поэтому: + +- поиск = **O(n)** +- вставка в конец = **O(n)** +- удаление = **O(n)** + +Это делает его самой медленной структурой для телефонного справочника. + +--- + +### **4. Как работает удаление в каждой структуре** + +#### Связный список +- ищем предыдущий узел +- перенаправляем ссылки +- сложность: **O(n)** + +#### Хеш‑таблица +- вычисляем бакет +- удаляем элемент в маленьком списке внутри бакета +- сложность: **O(1)** в среднем + +#### BST +3 случая: +1. лист +2. один потомок +3. два потомка (замена на inorder‑преемника) + +Сложность: + +- **O(log n)** в среднем +- **O(n)** в худшем случае (несбалансированное дерево) + +--- + +### **5. Какую структуру выбирать в реальной жизни** + +| Задача | Лучшая структура | Причина | +|--------|------------------|---------| +| Частые вставки | **HashTable** | O(1) | +| Частый поиск | **HashTable** | O(1) | +| Частое удаление | **HashTable** | O(1) | +| Нужен отсортированный вывод | **BST** | in‑order обход | +| Маленькие данные | Любая | Разницы нет | +| Учебные цели | LinkedList | Простая структура | + +### **Общий вывод:** +> В реальных приложениях телефонный справочник почти всегда реализуют через **хеш‑таблицу**, так как она обеспечивает лучшую производительность для вставки, поиска и удаления. + diff --git a/vinichukan/docs/report_task1.md b/vinichukan/docs/report_task1.md new file mode 100644 index 0000000..8f1c07d --- /dev/null +++ b/vinichukan/docs/report_task1.md @@ -0,0 +1,95 @@ +# Отчёт по заданию 1 +## Структуры данных: LinkedList, HashTable, BST +### Ветка: `Task1(vinichukan)` + +--- + +# 1. Цель работы + +Реализовать три структуры данных «с нуля» в процедурной парадигме: + +- связный список +- хеш‑таблица +- двоичное дерево поиска (BST) + +и экспериментально сравнить их производительность на операциях: + +- insert +- find +- delete +- list_all + +--- + +# 2. Реализованные структуры данных + +Код расположен в папке `src/`: + +- `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/vinichukan/src/benchmark.py b/vinichukan/src/benchmark.py new file mode 100644 index 0000000..056936a --- /dev/null +++ b/vinichukan/src/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() diff --git a/vinichukan/src/bst.py b/vinichukan/src/bst.py new file mode 100644 index 0000000..06d8c1f --- /dev/null +++ b/vinichukan/src/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/vinichukan/src/hash_table.py b/vinichukan/src/hash_table.py new file mode 100644 index 0000000..b840ffa --- /dev/null +++ b/vinichukan/src/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/vinichukan/src/linked_list.py b/vinichukan/src/linked_list.py new file mode 100644 index 0000000..bac4305 --- /dev/null +++ b/vinichukan/src/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 diff --git a/vinichukan/src/results.csv b/vinichukan/src/results.csv new file mode 100644 index 0000000..4708b0c --- /dev/null +++ b/vinichukan/src/results.csv @@ -0,0 +1,7 @@ +Structure,Mode,Operation,Time +LinkedList,shuffled,insert,3.3623596000015823 +HashTable,shuffled,insert,0.2035665000003064 +BST,shuffled,insert,0.020500900000115507 +LinkedList,sorted,insert,2.8638613000002806 +HashTable,sorted,insert,0.18161420000069484 +BST,sorted,insert,5.237768099999812 From c3fb6c4bd98bb1affead62a5a205b8f35b96cbb7 Mon Sep 17 00:00:00 2001 From: Anton_Viniuchuk Date: Sat, 23 May 2026 21:15:35 +0300 Subject: [PATCH 2/5] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB?= =?UTF-8?q?=20=D0=BA=D0=BE=D0=B4=20=D0=B7=D0=B0=D0=B4=D0=B0=D0=BD=D0=B8?= =?UTF-8?q?=D1=8F=201,=20=D0=BE=D1=82=D1=87=D1=91=D1=82=20=D0=B8=20=D1=80?= =?UTF-8?q?=D0=B5=D0=B7=D1=83=D0=BB=D1=8C=D1=82=D0=B0=D1=82=D1=8B=20=D0=B7?= =?UTF-8?q?=D0=B0=D0=BC=D0=B5=D1=80=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/.gitignore | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .idea/.gitignore diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..b58b603 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,5 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ From a50bd41ccf13dbc596ef735326022e37531360d5 Mon Sep 17 00:00:00 2001 From: Anton_Viniuchuk Date: Sat, 23 May 2026 21:31:27 +0300 Subject: [PATCH 3/5] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB?= =?UTF-8?q?=20=D0=BA=D0=BE=D0=B4=20=D0=B7=D0=B0=D0=B4=D0=B0=D0=BD=D0=B8?= =?UTF-8?q?=D1=8F=201,=20=D0=BE=D1=82=D1=87=D1=91=D1=82=20=D0=B8=20=D1=80?= =?UTF-8?q?=D0=B5=D0=B7=D1=83=D0=BB=D1=8C=D1=82=D0=B0=D1=82=D1=8B=20=D0=B7?= =?UTF-8?q?=D0=B0=D0=BC=D0=B5=D1=80=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/2026-rff_mp_ViniuchukAN.iml | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .idea/2026-rff_mp_ViniuchukAN.iml diff --git a/.idea/2026-rff_mp_ViniuchukAN.iml b/.idea/2026-rff_mp_ViniuchukAN.iml new file mode 100644 index 0000000..c03f621 --- /dev/null +++ b/.idea/2026-rff_mp_ViniuchukAN.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file From 6a4e82001cc3cc3c018fa3932c9cc36acd15f200 Mon Sep 17 00:00:00 2001 From: Anton_Vinichuk Date: Sun, 24 May 2026 08:58:04 +0000 Subject: [PATCH 4/5] =?UTF-8?q?=D0=A3=D0=B4=D0=B0=D0=BB=D0=B8=D1=82=D1=8C?= =?UTF-8?q?=20.idea/.gitignore?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/.gitignore | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 .idea/.gitignore diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index b58b603..0000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml -# Editor-based HTTP Client requests -/httpRequests/ From f79bddbef7aa4eeed81a0a9714ac8225ea47aac2 Mon Sep 17 00:00:00 2001 From: Anton_Vinichuk Date: Sun, 24 May 2026 08:58:50 +0000 Subject: [PATCH 5/5] =?UTF-8?q?=D0=A3=D0=B4=D0=B0=D0=BB=D0=B8=D1=82=D1=8C?= =?UTF-8?q?=20.idea/2026-rff=5Fmp=5FViniuchukAN.iml?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/2026-rff_mp_ViniuchukAN.iml | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 .idea/2026-rff_mp_ViniuchukAN.iml diff --git a/.idea/2026-rff_mp_ViniuchukAN.iml b/.idea/2026-rff_mp_ViniuchukAN.iml deleted file mode 100644 index c03f621..0000000 --- a/.idea/2026-rff_mp_ViniuchukAN.iml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file