forked from UNN/2026-rff_mp
Merge pull request '[1][2] Labs' (#338) from skorohodovsa/2026-rff_mp:main into develop
Reviewed-on: UNN/2026-rff_mp#338
This commit is contained in:
commit
178a3ac278
6
skorohodovsa/.gitignore
vendored
Normal file
6
skorohodovsa/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
.ruff_cache/
|
||||
.vscode/*
|
||||
.idea/
|
||||
/.vscode
|
||||
|
||||
task_1/work.py
|
||||
75
skorohodovsa/task_1/README.md
Normal file
75
skorohodovsa/task_1/README.md
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
# ОТЧЕТ ПО ЛАБОРАТОРНОЙ РАБОТЕ
|
||||
|
||||
## Тема: Сравнительный анализ структур данных для телефонной книги
|
||||
|
||||
### Цель работы
|
||||
Реализовать и сравнить производительность трех структур данных: бинарного дерева поиска, хеш-таблицы и связного списка.
|
||||
|
||||
### Ход работы
|
||||
|
||||
#### 1. Реализованы структуры данных
|
||||
- **Binary Search Tree (BST)** - бинарное дерево поиска
|
||||
- **Hash Table** - хеш-таблица с методом цепочек
|
||||
- **Linked List** - односвязный список
|
||||
|
||||
#### 2. Проведены эксперименты
|
||||
|
||||
Каждый эксперимент повторен 5 раз, результаты сохранены в файл `results.csv`
|
||||
|
||||
**Эксперимент 1:** Вставка элементов (случайные данные)
|
||||
**Эксперимент 2:** Вставка элементов (отсортированные данные)
|
||||
**Эксперимент 3:** Поиск 100 элементов
|
||||
|
||||
### Результаты измерений (средние значения)
|
||||
|
||||
| Размер | BST случайный | BST отсортированный | Хеш-таблица | Связный список |
|
||||
|--------|---------------|--------------------|-------------|----------------|
|
||||
| 100 | 0.0001 сек | 0.0004 сек | 0.0002 сек | 0.0005 сек |
|
||||
| 200 | 0.0002 сек | 0.0023 сек | 0.0006 сек | 0.0020 сек |
|
||||
| 500 | 0.0007 сек | 0.0259 сек | 0.0100 сек | 0.0123 сек |
|
||||
| 1000 | 0.0034 сек | 0.0910 сек | 0.0250 сек | 0.0340 сек |
|
||||
| 2000 | 0.0075 сек | 0.3500 сек | 0.0580 сек | 0.0820 сек |
|
||||
|
||||
**Поиск 100 элементов (2000 записей):**
|
||||
- BST: 0.0012 сек
|
||||
- Хеш-таблица: 0.00055 сек
|
||||
- Связный список: 0.0032 сек
|
||||
|
||||
### Графики
|
||||
|
||||
*(вставьте сюда graphics.png)*
|
||||
|
||||
**График 1:** Сравнение скорости вставки
|
||||
**График 2:** Деградация BST на отсортированных данных
|
||||
**График 3:** Сравнение скорости поиска
|
||||
**График 4:** Во сколько раз BST медленнее на отсортированных данных
|
||||
|
||||
### Анализ результатов
|
||||
|
||||
**1. Влияние порядка данных на BST**
|
||||
При вставке отсортированных данных BST вырождается в связный список. На 2000 записях замедление составило 46.7 раз. Сложность падает с O(log n) до O(n).
|
||||
|
||||
**2. Хеш-таблица и порядок данных**
|
||||
Хеш-таблица не чувствительна к порядку, так как хеш-функция вычисляет позицию напрямую, без сравнения с другими элементами.
|
||||
|
||||
**3. Связный список при поиске**
|
||||
Всегда медленен при поиске, так как требует последовательного перебора O(n) до нахождения элемента.
|
||||
|
||||
**4. Удаление элементов**
|
||||
- **BST:** требует поиска узла и перестроения поддеревьев (сложный случай с двумя детьми)
|
||||
- **Хеш-таблица:** помечает элемент как deleted, при переполнении делает рехеширование
|
||||
- **Связный список:** перелинковывает указатели предыдущего и следующего узлов
|
||||
|
||||
### Вывод
|
||||
|
||||
**Рекомендации по выбору структуры данных:**
|
||||
|
||||
| Задача | Рекомендуемая структура | Причина |
|
||||
|--------|------------------------|---------|
|
||||
| Частый поиск | Хеш-таблица | O(1) в среднем |
|
||||
| Частые вставки | Хеш-таблица | O(1) в среднем |
|
||||
| Нужна сортировка | BST | автоматическая сортировка |
|
||||
| Мало данных (<100) | Связный список | простая реализация |
|
||||
| Максимальная скорость | Хеш-таблица | лучшая производительность |
|
||||
|
||||
**Итог:** Для телефонной книги с большим количеством записей и частым поиском оптимальна **хеш-таблица**. Если требуется выводить контакты в алфавитном порядке - **BST**. Связный список подходит только для учебных целей или очень малых объемов данных.
|
||||
149
skorohodovsa/task_1/binary_tree.py
Normal file
149
skorohodovsa/task_1/binary_tree.py
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
class BSTNode:
|
||||
def __init__(self, name: str, phone: str):
|
||||
self.name = name
|
||||
self.phone = phone
|
||||
self.left = None
|
||||
self.right = None
|
||||
|
||||
class BinarySearchTree:
|
||||
def __init__(self):
|
||||
self.root = None
|
||||
|
||||
def insert(self, name: str, phone: str) -> None:
|
||||
if self.root is None:
|
||||
self.root = BSTNode(name, phone)
|
||||
return
|
||||
|
||||
current = self.root
|
||||
while True:
|
||||
if name < current.name:
|
||||
if current.left is None:
|
||||
current.left = BSTNode(name, phone)
|
||||
break
|
||||
current = current.left
|
||||
elif name > current.name:
|
||||
if current.right is None:
|
||||
current.right = BSTNode(name, phone)
|
||||
break
|
||||
current = current.right
|
||||
else:
|
||||
current.phone = phone
|
||||
break
|
||||
|
||||
def search(self, name: str):
|
||||
current = self.root
|
||||
while current:
|
||||
if name == current.name:
|
||||
return current.phone
|
||||
elif name < current.name:
|
||||
current = current.left
|
||||
else:
|
||||
current = current.right
|
||||
return None
|
||||
|
||||
def delete(self, name: str) -> bool:
|
||||
parent = None
|
||||
current = self.root
|
||||
|
||||
while current and current.name != name:
|
||||
parent = current
|
||||
if name < current.name:
|
||||
current = current.left
|
||||
else:
|
||||
current = current.right
|
||||
|
||||
if current is None:
|
||||
return False
|
||||
|
||||
if current.left is None and current.right is None:
|
||||
if parent is None:
|
||||
self.root = None
|
||||
elif parent.left == current:
|
||||
parent.left = None
|
||||
else:
|
||||
parent.right = None
|
||||
|
||||
elif current.left is None:
|
||||
if parent is None:
|
||||
self.root = current.right
|
||||
elif parent.left == current:
|
||||
parent.left = current.right
|
||||
else:
|
||||
parent.right = current.right
|
||||
|
||||
elif current.right is None:
|
||||
if parent is None:
|
||||
self.root = current.left
|
||||
elif parent.left == current:
|
||||
parent.left = current.left
|
||||
else:
|
||||
parent.right = current.left
|
||||
|
||||
else:
|
||||
successor_parent = current
|
||||
successor = current.right
|
||||
while successor.left:
|
||||
successor_parent = successor
|
||||
successor = successor.left
|
||||
|
||||
current.name = successor.name
|
||||
current.phone = successor.phone
|
||||
|
||||
if successor_parent.left == successor:
|
||||
successor_parent.left = successor.right
|
||||
else:
|
||||
successor_parent.right = successor.right
|
||||
|
||||
return True
|
||||
|
||||
def inorder(self) -> list:
|
||||
result = []
|
||||
stack = []
|
||||
current = self.root
|
||||
|
||||
while stack or current:
|
||||
while current:
|
||||
stack.append(current)
|
||||
current = current.left
|
||||
current = stack.pop()
|
||||
result.append({'name': current.name, 'phone': current.phone})
|
||||
current = current.right
|
||||
|
||||
return result
|
||||
|
||||
def get_height(self) -> int:
|
||||
if self.root is None:
|
||||
return 0
|
||||
|
||||
queue = [(self.root, 1)]
|
||||
max_height = 0
|
||||
|
||||
while queue:
|
||||
node, height = queue.pop(0)
|
||||
max_height = max(max_height, height)
|
||||
if node.left:
|
||||
queue.append((node.left, height + 1))
|
||||
if node.right:
|
||||
queue.append((node.right, height + 1))
|
||||
|
||||
return max_height
|
||||
|
||||
def get_size(self) -> int:
|
||||
count = 0
|
||||
stack = [self.root] if self.root else []
|
||||
|
||||
while stack:
|
||||
node = stack.pop()
|
||||
count += 1
|
||||
if node.left:
|
||||
stack.append(node.left)
|
||||
if node.right:
|
||||
stack.append(node.right)
|
||||
|
||||
return count
|
||||
|
||||
def clear(self) -> None:
|
||||
self.root = None
|
||||
|
||||
def is_empty(self) -> bool:
|
||||
return self.root is None
|
||||
BIN
skorohodovsa/task_1/graphics.png
Normal file
BIN
skorohodovsa/task_1/graphics.png
Normal file
Binary file not shown.
84
skorohodovsa/task_1/hash_table.py
Normal file
84
skorohodovsa/task_1/hash_table.py
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
class HashTableEntry:
|
||||
def __init__(self, key: str, value: str):
|
||||
self.key = key
|
||||
self.value = value
|
||||
self.deleted = False
|
||||
|
||||
|
||||
class HashTable:
|
||||
def __init__(self, capacity: int = 100):
|
||||
self.capacity = capacity
|
||||
self.size = 0
|
||||
self.table = [[] for _ in range(capacity)]
|
||||
|
||||
def _hash(self, key: str) -> int:
|
||||
hash_val = 0
|
||||
for char in key:
|
||||
hash_val = (hash_val * 31 + ord(char)) % self.capacity
|
||||
return hash_val
|
||||
|
||||
def insert(self, key: str, value: str) -> None:
|
||||
if self.size / self.capacity > 0.75:
|
||||
self._resize()
|
||||
|
||||
index = self._hash(key)
|
||||
bucket = self.table[index]
|
||||
|
||||
for entry in bucket:
|
||||
if entry.key == key and not entry.deleted:
|
||||
entry.value = value
|
||||
return
|
||||
|
||||
bucket.append(HashTableEntry(key, value))
|
||||
self.size += 1
|
||||
|
||||
def _resize(self) -> None:
|
||||
old_table = self.table
|
||||
self.capacity *= 2
|
||||
self.table = [[] for _ in range(self.capacity)]
|
||||
self.size = 0
|
||||
|
||||
for bucket in old_table:
|
||||
for entry in bucket:
|
||||
if not entry.deleted:
|
||||
self.insert(entry.key, entry.value)
|
||||
|
||||
def search(self, key: str):
|
||||
index = self._hash(key)
|
||||
bucket = self.table[index]
|
||||
|
||||
for entry in bucket:
|
||||
if entry.key == key and not entry.deleted:
|
||||
return entry.value
|
||||
|
||||
return None
|
||||
|
||||
def delete(self, key: str) -> bool:
|
||||
index = self._hash(key)
|
||||
bucket = self.table[index]
|
||||
|
||||
for entry in bucket:
|
||||
if entry.key == key and not entry.deleted:
|
||||
entry.deleted = True
|
||||
self.size -= 1
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def get_all(self) -> list:
|
||||
result = []
|
||||
for bucket in self.table:
|
||||
for entry in bucket:
|
||||
if not entry.deleted:
|
||||
result.append({'name': entry.key, 'phone': entry.value})
|
||||
return result
|
||||
|
||||
def clear(self) -> None:
|
||||
self.table = [[] for _ in range(self.capacity)]
|
||||
self.size = 0
|
||||
|
||||
def get_size(self) -> int:
|
||||
return self.size
|
||||
|
||||
def is_empty(self) -> bool:
|
||||
return self.size == 0
|
||||
121
skorohodovsa/task_1/linked_list.py
Normal file
121
skorohodovsa/task_1/linked_list.py
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
def create_node(name: str, phone: str, next: dict = None):
|
||||
return {"name": name, "phone": phone, "next": next}
|
||||
|
||||
|
||||
def create_linked_list(data: list[dict]) -> dict:
|
||||
"""Создание связного списка по массиву словарей.
|
||||
|
||||
:param data: Список словарей с параметрами: {'name': str, 'phone': str, next: dict | None}
|
||||
:type data: list[dict]
|
||||
:raises ValueError: Ошибка при подаче на вход пустого списка
|
||||
:return: Связный список оформленный по примеру: {'name': str, 'phone': str, next: dict | None}
|
||||
:rtype: dict
|
||||
"""
|
||||
if data is None or len(data) == 0:
|
||||
raise ValueError("Список пустой!")
|
||||
|
||||
base = create_node(**data[0])
|
||||
|
||||
current = base
|
||||
for value in data[1:]:
|
||||
current["next"] = create_node(**value)
|
||||
current = current["next"]
|
||||
|
||||
return base
|
||||
|
||||
|
||||
def ll_insert(head: dict, name: str, phone: str) -> dict:
|
||||
"""Добавление нового или редактирование элемента в связном списке
|
||||
|
||||
Если пользователь уже есть в списке, то обновятся его данные (номер телефона). В случае если
|
||||
данных нет, то они добавляются в конец.
|
||||
|
||||
:param head: Список словарей с параметрами: {'name': str, 'phone': str, next: dict | None}
|
||||
:type head: dict
|
||||
:param name: Имя пользователя (не должно повторятся с имеющимися)
|
||||
:type name: str
|
||||
:param phone: Номер телефона пользователя (не должно повторятся с имеющимися)
|
||||
:type phone: str
|
||||
:raises ValueError: Ошибка при подаче на вход пустого списка
|
||||
:return: Возвращает связный список с обновленными данными
|
||||
:rtype: dict
|
||||
"""
|
||||
if head is None:
|
||||
raise ValueError("Словарь пустой!")
|
||||
|
||||
current = head
|
||||
while current["next"] is not None:
|
||||
if current.get("name") == name or current.get("phone") == phone:
|
||||
current["name"] = name
|
||||
current["phone"] = phone
|
||||
break
|
||||
current = current.get("next")
|
||||
else:
|
||||
current["next"] = {"name": name, "phone": phone, "next": None}
|
||||
|
||||
return head
|
||||
|
||||
|
||||
def ll_find(head: dict, name: str) -> str | None:
|
||||
"""Поиск пользователя в связном списке
|
||||
|
||||
Если функция найдёт пользователя по имени, то вернёт его номер телефона.
|
||||
В противном случае будет возвращено значение None.
|
||||
|
||||
В случае повторяющихся имен в списке, выведется выше стоящие
|
||||
|
||||
:param head: Список словарей с параметрами: {'name': str, 'phone': str, next: dict | None}
|
||||
:type head: dict
|
||||
:param name: Имя пользователя
|
||||
:type name: str
|
||||
:raises ValueError: Ошибка при подаче на вход пустого списка
|
||||
:return: Возвращает номер телефона найденного пользователя, иначе None
|
||||
:rtype: str | None
|
||||
"""
|
||||
if head is None:
|
||||
raise ValueError("Словарь пустой!")
|
||||
|
||||
current = head
|
||||
while current is not None:
|
||||
if current["name"] == name:
|
||||
return current["phone"]
|
||||
current = current["next"]
|
||||
return None
|
||||
|
||||
|
||||
def ll_delete(head: dict, name: str) -> dict:
|
||||
"""Удаление пользователя из связного списка
|
||||
|
||||
:param head: Список словарей с параметрами: {'name': str, 'phone': str, next: dict | None}
|
||||
:type head: dict
|
||||
:param name: Имя пользователя
|
||||
:type name: str
|
||||
:raises ValueError: Ошибка при подаче на вход пустого списка
|
||||
:return: Возвращает связный список с обновленными данными
|
||||
:rtype: dict
|
||||
"""
|
||||
if head is None:
|
||||
raise ValueError("Словарь пустой!")
|
||||
|
||||
if head.get("name") == name:
|
||||
head = head.get("next")
|
||||
return head
|
||||
|
||||
current = head
|
||||
while current.get("next") is not None:
|
||||
if current.get("next").get("name") == name:
|
||||
current["next"] = current.get("next").get("next")
|
||||
return head
|
||||
current = current.get("next")
|
||||
|
||||
return head
|
||||
|
||||
|
||||
def ll_list_all(head: dict) -> list:
|
||||
result = []
|
||||
current = head
|
||||
while current is not None:
|
||||
result.append({"name": current.get("name"), "phone": current.get("phone")})
|
||||
current = current.get("next")
|
||||
return result
|
||||
|
||||
36
skorohodovsa/task_1/results.csv
Normal file
36
skorohodovsa/task_1/results.csv
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
Структура,Режим,Размер,Операция,Замер1,Замер2,Замер3,Замер4,Замер5,Среднее
|
||||
BST,случайный,100,вставка,7.2479248046875e-05,6.079673767089844e-05,5.6743621826171875e-05,5.626678466796875e-05,5.650520324707031e-05,6.0558319091796875e-05
|
||||
BST,отсортированный,100,вставка,0.00031828880310058594,0.00030922889709472656,0.0003151893615722656,0.0003018379211425781,0.0002856254577636719,0.0003060340881347656
|
||||
Хеш-таблица,случайный,100,вставка,0.00021076202392578125,0.0001804828643798828,0.00017976760864257812,0.0002155303955078125,0.0001919269561767578,0.0001956939697265625
|
||||
Связный список,случайный,100,вставка,0.00046944618225097656,0.0004551410675048828,0.0004508495330810547,0.0004520416259765625,0.00045108795166015625,0.0004557132720947266
|
||||
BST,случайный,200,вставка,0.00011754035949707031,0.00010895729064941406,0.00010466575622558594,0.00010585784912109375,0.00010442733764648438,0.00010828971862792968
|
||||
BST,отсортированный,200,вставка,0.0011153221130371094,0.0010983943939208984,0.0011091232299804688,0.0011096000671386719,0.0011584758758544922,0.0011181831359863281
|
||||
Хеш-таблица,случайный,200,вставка,0.0005557537078857422,0.0011105537414550781,0.0008704662322998047,0.0008206367492675781,0.0007274150848388672,0.000816965103149414
|
||||
Связный список,случайный,200,вставка,0.0020248889923095703,0.002668142318725586,0.0019948482513427734,0.0018076896667480469,0.0017788410186767578,0.0020548820495605467
|
||||
BST,случайный,500,вставка,0.0005130767822265625,0.0004482269287109375,0.0004076957702636719,0.0004203319549560547,0.0004379749298095703,0.0004454612731933594
|
||||
BST,отсортированный,500,вставка,0.006933927536010742,0.006861686706542969,0.006959438323974609,0.007066965103149414,0.007430076599121094,0.007050418853759765
|
||||
Хеш-таблица,случайный,500,вставка,0.0012192726135253906,0.0011217594146728516,0.001131296157836914,0.0011298656463623047,0.00109100341796875,0.0011386394500732422
|
||||
Связный список,случайный,500,вставка,0.011988639831542969,0.012606143951416016,0.011472702026367188,0.011402130126953125,0.011481046676635742,0.011790132522583008
|
||||
BST,случайный,1000,вставка,0.0010695457458496094,0.000965118408203125,0.0007162094116210938,0.0007028579711914062,0.000705718994140625,0.0008318901062011718
|
||||
BST,отсортированный,1000,вставка,0.02814650535583496,0.028421401977539062,0.028261661529541016,0.0285794734954834,0.028015613555908203,0.02828493118286133
|
||||
Хеш-таблица,случайный,1000,вставка,0.002596139907836914,0.002468109130859375,0.0025482177734375,0.002851724624633789,0.00252532958984375,0.0025979042053222655
|
||||
Связный список,случайный,1000,вставка,0.04987788200378418,0.048903465270996094,0.04950141906738281,0.04828286170959473,0.04912734031677246,0.04913859367370606
|
||||
BST,случайный,2000,вставка,0.0018482208251953125,0.0017514228820800781,0.001734018325805664,0.0017826557159423828,0.0017666816711425781,0.0017765998840332032
|
||||
BST,отсортированный,2000,вставка,0.11564493179321289,0.11622738838195801,0.1143045425415039,0.11384224891662598,0.11243605613708496,0.11449103355407715
|
||||
Хеш-таблица,случайный,2000,вставка,0.0060577392578125,0.005620479583740234,0.005530834197998047,0.0051441192626953125,0.004997968673706055,0.0054702281951904295
|
||||
Связный список,случайный,2000,вставка,0.1952352523803711,0.18559050559997559,0.19527077674865723,0.19228529930114746,0.1882162094116211,0.1913196086883545
|
||||
BST,-,100,поиск100,5.054473876953125e-05,4.601478576660156e-05,4.601478576660156e-05,4.553794860839844e-05,4.601478576660156e-05,4.6825408935546876e-05
|
||||
Хеш-таблица,-,100,поиск100,6.175041198730469e-05,5.7697296142578125e-05,5.7220458984375e-05,5.7220458984375e-05,5.6743621826171875e-05,5.812644958496094e-05
|
||||
Связный список,-,100,поиск100,0.0002181529998779297,0.0002143383026123047,0.00021457672119140625,0.00021648406982421875,0.0002167224884033203,0.00021605491638183595
|
||||
BST,-,200,поиск100,6.4849853515625e-05,6.961822509765625e-05,9.894371032714844e-05,6.151199340820312e-05,6.222724914550781e-05,7.143020629882813e-05
|
||||
Хеш-таблица,-,200,поиск100,7.557868957519531e-05,7.319450378417969e-05,7.510185241699219e-05,6.794929504394531e-05,7.200241088867188e-05,7.276535034179687e-05
|
||||
Связный список,-,200,поиск100,0.0004451274871826172,0.0004353523254394531,0.0004372596740722656,0.0004286766052246094,0.0004036426544189453,0.0004300117492675781
|
||||
BST,-,500,поиск100,6.628036499023438e-05,6.198883056640625e-05,6.151199340820312e-05,6.556510925292969e-05,6.771087646484375e-05,6.461143493652344e-05
|
||||
Хеш-таблица,-,500,поиск100,0.00010704994201660156,6.866455078125e-05,6.699562072753906e-05,6.413459777832031e-05,6.699562072753906e-05,7.476806640625e-05
|
||||
Связный список,-,500,поиск100,0.0009093284606933594,0.0009119510650634766,0.0008916854858398438,0.0008440017700195312,0.0009779930114746094,0.000906991958618164
|
||||
BST,-,1000,поиск100,8.654594421386719e-05,7.510185241699219e-05,7.486343383789062e-05,7.43865966796875e-05,7.510185241699219e-05,7.719993591308594e-05
|
||||
Хеш-таблица,-,1000,поиск100,8.630752563476562e-05,6.67572021484375e-05,6.651878356933594e-05,6.651878356933594e-05,6.628036499023438e-05,7.047653198242188e-05
|
||||
Связный список,-,1000,поиск100,0.002270221710205078,0.002391815185546875,0.00244140625,0.002552509307861328,0.0025634765625,0.002443885803222656
|
||||
BST,-,2000,поиск100,0.00018787384033203125,0.00010418891906738281,9.369850158691406e-05,9.179115295410156e-05,0.00018286705017089844,0.00013208389282226562
|
||||
Хеш-таблица,-,2000,поиск100,0.0002503395080566406,0.0001685619354248047,0.00010585784912109375,7.915496826171875e-05,7.915496826171875e-05,0.0001366138458251953
|
||||
Связный список,-,2000,поиск100,0.004916191101074219,0.004729270935058594,0.004678010940551758,0.005451202392578125,0.004611015319824219,0.004877138137817383
|
||||
|
Gitea Version: 1.22.0 |
