Merge pull request '[1] 1-st-exersize' (#245) from SavelevMI/2026-rff_mp:1-st-exersize into develop
Reviewed-on: #245
This commit is contained in:
commit
e47b0a93c7
18
SavelevMI/docs/data/1-st-exersize/benchmark.py
Normal file
18
SavelevMI/docs/data/1-st-exersize/benchmark.py
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
# Генерация тестовых наборов данных
|
||||
|
||||
import random
|
||||
|
||||
def generate_records(n, seed=42):
|
||||
random.seed(seed)
|
||||
records = []
|
||||
for i in range(1, n + 1):
|
||||
name = f"User_{i:05d}"
|
||||
phone = f"{random.randint(100,999)}-{random.randint(1000,9999)}"
|
||||
records.append((name, phone))
|
||||
return records
|
||||
|
||||
def prepare_datasets(base_records):
|
||||
shuffled = base_records.copy()
|
||||
random.shuffle(shuffled)
|
||||
sorted_records = sorted(base_records, key=lambda x: x[0])
|
||||
return shuffled, sorted_records
|
||||
66
SavelevMI/docs/data/1-st-exersize/bst.py
Normal file
66
SavelevMI/docs/data/1-st-exersize/bst.py
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
# Двоичное дерево поиска (не сбалансированное)
|
||||
|
||||
def create_node(name, phone):
|
||||
return {'name': name, 'phone': phone, 'left': None, 'right': None}
|
||||
|
||||
def bst_insert(root, name, phone):
|
||||
if root is None:
|
||||
return create_node(name, phone)
|
||||
|
||||
if name == root['name']:
|
||||
root['phone'] = phone
|
||||
elif name < root['name']:
|
||||
root['left'] = bst_insert(root['left'], name, phone)
|
||||
else:
|
||||
root['right'] = bst_insert(root['right'], name, 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 _find_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']
|
||||
|
||||
# Узел имеет двух потомков: заменяем наименьшим из правого поддерева
|
||||
min_node = _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_list_all(root):
|
||||
result = []
|
||||
|
||||
def inorder(node):
|
||||
if node is None:
|
||||
return
|
||||
inorder(node['left'])
|
||||
result.append((node['name'], node['phone']))
|
||||
inorder(node['right'])
|
||||
|
||||
inorder(root)
|
||||
return result
|
||||
18
SavelevMI/docs/data/1-st-exersize/data_utils.py
Normal file
18
SavelevMI/docs/data/1-st-exersize/data_utils.py
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
# Генерация тестовых наборов данных
|
||||
|
||||
import random
|
||||
|
||||
def generate_records(n, seed=42):
|
||||
random.seed(seed)
|
||||
records = []
|
||||
for i in range(1, n + 1):
|
||||
name = f"User_{i:05d}"
|
||||
phone = f"{random.randint(100,999)}-{random.randint(1000,9999)}"
|
||||
records.append((name, phone))
|
||||
return records
|
||||
|
||||
def prepare_datasets(base_records):
|
||||
shuffled = base_records.copy()
|
||||
random.shuffle(shuffled)
|
||||
sorted_records = sorted(base_records, key=lambda x: x[0])
|
||||
return shuffled, sorted_records
|
||||
38
SavelevMI/docs/data/1-st-exersize/hash_table.py
Normal file
38
SavelevMI/docs/data/1-st-exersize/hash_table.py
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
# Хеш-таблица на основе списка корзин, каждая корзина – связный список
|
||||
|
||||
import linked_list as ll
|
||||
|
||||
def create_hash_table(size=10):
|
||||
return [None] * size
|
||||
|
||||
def _hash_function(key, size):
|
||||
return hash(key) % size
|
||||
|
||||
def ht_insert(buckets, name, phone):
|
||||
idx = _hash_function(name, len(buckets))
|
||||
head = buckets[idx]
|
||||
new_head = ll.ll_insert(head, name, phone)
|
||||
buckets[idx] = new_head
|
||||
return buckets
|
||||
|
||||
def ht_find(buckets, name):
|
||||
idx = _hash_function(name, len(buckets))
|
||||
head = buckets[idx]
|
||||
return ll.ll_find(head, name)
|
||||
|
||||
def ht_delete(buckets, name):
|
||||
idx = _hash_function(name, len(buckets))
|
||||
head = buckets[idx]
|
||||
new_head = ll.ll_delete(head, name)
|
||||
buckets[idx] = new_head
|
||||
return buckets
|
||||
|
||||
def ht_list_all(buckets):
|
||||
all_records = []
|
||||
for head in buckets:
|
||||
current = head
|
||||
while current is not None:
|
||||
all_records.append((current['name'], current['phone']))
|
||||
current = current['next']
|
||||
all_records.sort(key=lambda x: x[0])
|
||||
return all_records
|
||||
58
SavelevMI/docs/data/1-st-exersize/linked_list.py
Normal file
58
SavelevMI/docs/data/1-st-exersize/linked_list.py
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
def create_node(name, phone):
|
||||
return {'name': name, 'phone': phone, 'next': None}
|
||||
|
||||
def ll_insert(head, name, phone):
|
||||
current = head
|
||||
# Поиск существующей записи
|
||||
while current is not None:
|
||||
if current['name'] == name:
|
||||
current['phone'] = phone
|
||||
return head
|
||||
current = current['next']
|
||||
|
||||
# Создание нового узла
|
||||
new_node = create_node(name, phone)
|
||||
|
||||
if head is None:
|
||||
return new_node
|
||||
|
||||
current = head
|
||||
while current['next'] is not None:
|
||||
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']
|
||||
|
||||
prev = head
|
||||
current = head['next']
|
||||
while current is not None:
|
||||
if current['name'] == name:
|
||||
prev['next'] = current['next']
|
||||
return head
|
||||
prev = current
|
||||
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 pair: pair[0])
|
||||
return records
|
||||
73
SavelevMI/docs/data/1-st-exersize/main.py
Normal file
73
SavelevMI/docs/data/1-st-exersize/main.py
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
# Запуск экспериментального сравнения трёх структур данных
|
||||
# Результаты сохраняются в experiment_results.csv
|
||||
|
||||
import csv
|
||||
import sys
|
||||
sys.setrecursionlimit(20000)
|
||||
|
||||
import linked_list as ll
|
||||
import hash_table as ht
|
||||
import bst
|
||||
import data_utils
|
||||
import benchmark
|
||||
|
||||
def main():
|
||||
N = 10000 # количество записей
|
||||
base_records = data_utils.generate_records(N)
|
||||
shuffled, sorted_records = data_utils.prepare_datasets(base_records)
|
||||
|
||||
# Описания структур для бенчмарка
|
||||
structures = {
|
||||
'LinkedList': {
|
||||
'name': 'LinkedList',
|
||||
'create': lambda: None,
|
||||
'insert': ll.ll_insert,
|
||||
'find': ll.ll_find,
|
||||
'delete': ll.ll_delete
|
||||
},
|
||||
'HashTable': {
|
||||
'name': 'HashTable',
|
||||
'create': lambda: ht.create_hash_table(10), # 10 корзин
|
||||
'insert': ht.ht_insert,
|
||||
'find': ht.ht_find,
|
||||
'delete': ht.ht_delete
|
||||
},
|
||||
'BST': {
|
||||
'name': 'BST',
|
||||
'create': lambda: None,
|
||||
'insert': bst.bst_insert,
|
||||
'find': bst.bst_find,
|
||||
'delete': bst.bst_delete
|
||||
}
|
||||
}
|
||||
|
||||
all_results = []
|
||||
REPEATS = 5 # минимум 5 повторений
|
||||
|
||||
for name, struct in structures.items():
|
||||
print(f"Testing {name} on random order...")
|
||||
res_random = benchmark.measure_operations(struct, shuffled, 'random', REPEATS)
|
||||
all_results.extend(res_random)
|
||||
|
||||
print(f"Testing {name} on sorted order...")
|
||||
res_sorted = benchmark.measure_operations(struct, sorted_records, 'sorted', REPEATS)
|
||||
all_results.extend(res_sorted)
|
||||
|
||||
# Сохранение CSV
|
||||
with open('experiment_results.csv', 'w', newline='', encoding='utf-8') as f:
|
||||
writer = csv.writer(f)
|
||||
writer.writerow(['Structure', 'Mode', 'Repeat', 'Insert (sec)', 'Search (sec)', 'Delete (sec)'])
|
||||
for row in all_results:
|
||||
writer.writerow([
|
||||
row['structure'],
|
||||
row['mode'],
|
||||
row['repetition'],
|
||||
f"{row['insert_time']:.6f}",
|
||||
f"{row['find_time']:.6f}",
|
||||
f"{row['delete_time']:.6f}"
|
||||
])
|
||||
|
||||
print("Experiment finished. Results saved to experiment_results.csv")
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
43
SavelevMI/docs/data/1-st-exersize/plot_results.py
Normal file
43
SavelevMI/docs/data/1-st-exersize/plot_results.py
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
# Загружает CSV с результатами и строит столбчатую диаграмму сравнения
|
||||
|
||||
import pandas as pd
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
|
||||
def main():
|
||||
df = pd.read_csv('experiment_results.csv')
|
||||
|
||||
mean_times = df.groupby(['Structure', 'Mode'])[['Insert (sec)', 'Search (sec)', 'Delete (sec)']].mean().reset_index()
|
||||
|
||||
structures = mean_times['Structure'].unique()
|
||||
operations = ['Insert (sec)', 'Search (sec)', 'Delete (sec)']
|
||||
titles = ['Insertion', 'Search', 'Deletion']
|
||||
|
||||
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
|
||||
|
||||
for ax, op, title in zip(axes, operations, titles):
|
||||
x = np.arange(len(structures))
|
||||
width = 0.35
|
||||
|
||||
random_vals = []
|
||||
sorted_vals = []
|
||||
for s in structures:
|
||||
rand = mean_times[(mean_times['Structure'] == s) & (mean_times['Mode'] == 'random')]
|
||||
sort = mean_times[(mean_times['Structure'] == s) & (mean_times['Mode'] == 'sorted')]
|
||||
random_vals.append(rand[op].values[0] if not rand.empty else 0)
|
||||
sorted_vals.append(sort[op].values[0] if not sort.empty else 0)
|
||||
|
||||
ax.bar(x - width/2, random_vals, width, label='Random order')
|
||||
ax.bar(x + width/2, sorted_vals, width, label='Sorted order')
|
||||
ax.set_xticks(x)
|
||||
ax.set_xticklabels(structures)
|
||||
ax.set_ylabel('Time (seconds)')
|
||||
ax.set_title(title)
|
||||
ax.legend()
|
||||
|
||||
plt.tight_layout()
|
||||
plt.savefig('performance_comparison.png', dpi=150)
|
||||
plt.show()
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
146
SavelevMI/docs/data/2-nd-exersize/maze_core.py
Normal file
146
SavelevMI/docs/data/2-nd-exersize/maze_core.py
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
# Модель лабиринта: клетки, карта и загрузка из файла (Builder pattern)
|
||||
|
||||
class Cell:
|
||||
|
||||
def __init__(self, x, y):
|
||||
self._x = x
|
||||
self._y = y
|
||||
self._is_wall = False
|
||||
self._is_start = False
|
||||
self._is_exit = False
|
||||
|
||||
@property
|
||||
def x(self):
|
||||
return self._x
|
||||
|
||||
@property
|
||||
def y(self):
|
||||
return self._y
|
||||
|
||||
@property
|
||||
def is_wall(self):
|
||||
return self._is_wall
|
||||
|
||||
@is_wall.setter
|
||||
def is_wall(self, value):
|
||||
self._is_wall = value
|
||||
|
||||
@property
|
||||
def is_start(self):
|
||||
return self._is_start
|
||||
|
||||
@is_start.setter
|
||||
def is_start(self, value):
|
||||
self._is_start = value
|
||||
|
||||
@property
|
||||
def is_exit(self):
|
||||
return self._is_exit
|
||||
|
||||
@is_exit.setter
|
||||
def is_exit(self, value):
|
||||
self._is_exit = value
|
||||
|
||||
def is_passable(self):
|
||||
return not self._is_wall
|
||||
|
||||
|
||||
class Maze:
|
||||
|
||||
def __init__(self, width, height):
|
||||
self._width = width
|
||||
self._height = height
|
||||
self._cells = [[Cell(x, y) for x in range(width)] for y in range(height)]
|
||||
self._start = None
|
||||
self._exit = None
|
||||
|
||||
@property
|
||||
def width(self):
|
||||
return self._width
|
||||
|
||||
@property
|
||||
def height(self):
|
||||
return self._height
|
||||
|
||||
@property
|
||||
def start(self):
|
||||
return self._start
|
||||
|
||||
@property
|
||||
def exit(self):
|
||||
return self._exit
|
||||
|
||||
def get_cell(self, x, y):
|
||||
if 0 <= x < self._width and 0 <= y < self._height:
|
||||
return self._cells[y][x]
|
||||
return None
|
||||
|
||||
def set_cell(self, x, y, cell_type):
|
||||
cell = self.get_cell(x, y)
|
||||
if cell is None:
|
||||
return
|
||||
|
||||
if cell_type == 'wall':
|
||||
cell.is_wall = True
|
||||
elif cell_type == 'start':
|
||||
if self._start:
|
||||
self._start.is_start = False
|
||||
cell.is_start = True
|
||||
cell.is_wall = False
|
||||
self._start = cell
|
||||
elif cell_type == 'exit':
|
||||
if self._exit:
|
||||
self._exit.is_exit = False
|
||||
cell.is_exit = True
|
||||
cell.is_wall = False
|
||||
self._exit = cell
|
||||
elif cell_type == 'path':
|
||||
cell.is_wall = False
|
||||
|
||||
def get_neighbors(self, cell):
|
||||
neighbors = []
|
||||
directions = [(0, -1), (0, 1), (-1, 0), (1, 0)] # up, down, left, right
|
||||
for dx, dy in directions:
|
||||
nx, ny = cell.x + dx, cell.y + dy
|
||||
neighbor = self.get_cell(nx, ny)
|
||||
if neighbor and neighbor.is_passable():
|
||||
neighbors.append(neighbor)
|
||||
return neighbors
|
||||
|
||||
|
||||
class MazeBuilder:
|
||||
|
||||
def build_from_file(self, filename):
|
||||
raise NotImplementedError("Must be implemented in subclass")
|
||||
|
||||
|
||||
class TextFileMazeBuilder(MazeBuilder):
|
||||
|
||||
def build_from_file(self, filename):
|
||||
with open(filename, 'r') as f:
|
||||
lines = [line.rstrip('\n') for line in f.readlines()]
|
||||
|
||||
height = len(lines)
|
||||
width = max(len(line) for line in lines) if height > 0 else 0
|
||||
|
||||
start_count = 0
|
||||
exit_count = 0
|
||||
maze = Maze(width, height)
|
||||
|
||||
for y, line in enumerate(lines):
|
||||
for x, ch in enumerate(line):
|
||||
if ch == "#":
|
||||
maze.set_cell(x, y, "wall")
|
||||
elif ch == "S":
|
||||
maze.set_cell(x, y, "start")
|
||||
start_count += 1
|
||||
elif ch == "E":
|
||||
maze.set_cell(x, y, "exit")
|
||||
exit_count += 1
|
||||
elif ch == " ":
|
||||
maze.set_cell(x, y, "path")
|
||||
|
||||
if start_count != 1 or exit_count != 1:
|
||||
raise ValueError(f"Лабиринт должен иметь ровно один вход S и один выход E. Найдено: S={start_count}, E={exit_count}")
|
||||
|
||||
return maze
|
||||
BIN
SavelevMI/docs/performance_comparison.png
Normal file
BIN
SavelevMI/docs/performance_comparison.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 48 KiB |
82
SavelevMI/docs/report-1.md
Normal file
82
SavelevMI/docs/report-1.md
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
# Отчёт по лабораторной работе «Структуры данных»
|
||||
|
||||
## Цель работы
|
||||
|
||||
Реализовать три структуры данных «с нуля» (связный список, хеш‑таблицу, двоичное дерево поиска) для хранения записей телефонного справочника. Экспериментально сравнить производительность операций вставки, поиска и удаления на наборе из 10 000 записей при случайном и отсортированном порядке поступления данных.
|
||||
|
||||
## Реализованные структуры
|
||||
|
||||
Все структуры написаны в процедурном стиле без использования классов.
|
||||
|
||||
1. **Связный список** – узлы в виде словарей `{'name': str, 'phone': str, 'next': None}`.
|
||||
2. **Хеш‑таблица** – массив из 10 корзин, каждая корзина – связный список. Хеш‑функция – `hash(name) % size`.
|
||||
3. **Двоичное дерево поиска** – узлы `{'name': str, 'phone': str, 'left': None, 'right': None}`. Операции реализованы рекурсивно.
|
||||
|
||||
## Методика эксперимента
|
||||
|
||||
- **Генерация данных**: 10 000 записей с именами `User_00001` … `User_10000`. Телефоны – случайные строки вида `XXX-XXXX`.
|
||||
- **Два режима подачи данных**:
|
||||
– *Случайный* – записи перемешаны.
|
||||
– *Отсортированный* – записи по возрастанию имени.
|
||||
- **Измеряемые операции**:
|
||||
– Вставка всех 10 000 записей.
|
||||
– Поиск 110 имён (100 существующих + 10 несуществующих).
|
||||
– Удаление 50 случайных существующих записей.
|
||||
- **Повторы**: каждый эксперимент выполнен 5 раз, зафиксировано среднее время.
|
||||
|
||||
Результаты замеров сохранены в `experiment_results.csv`. Время измерялось через `time.perf_counter()`.
|
||||
|
||||
## Результаты измерений
|
||||
|
||||
### Связный список (LinkedList)
|
||||
|
||||
При 10 000 записях связный список показал ожидаемо низкую производительность. Вставка всех элементов заняла около **4.4 секунды** в среднем, поиск – около **0.027 секунды**, удаление 50 записей – около **0.012 секунды**. Порядок входных данных практически не повлиял на результаты (случайный и отсортированный режимы показали близкие значения). Это объясняется тем, что связный список всегда работает за линейное время O(n) независимо от того, как приходят данные.
|
||||
|
||||
### Хеш‑таблица (HashTable)
|
||||
|
||||
Хеш‑таблица с 10 корзинами показала значительное ускорение по сравнению со связным списком. Вставка 10 000 записей заняла в среднем **0.56 секунды** (почти в 8 раз быстрее списка). Поиск выполняется за **0.004 секунды** (в 7 раз быстрее), а удаление – за **0.0016 секунды** (в 7.5 раз быстрее). Порядок данных практически не влияет на производительность – разница между случайным и отсортированным режимами не превышает 10%, что соответствует теоретической сложности O(1) в среднем.
|
||||
|
||||
### Двоичное дерево поиска (BST)
|
||||
|
||||
Здесь наблюдается самая интересная картина:
|
||||
|
||||
**На случайных данных** BST показал выдающуюся производительность. Вставка всех 10 000 записей заняла всего **0.025 секунды**, что в 22 раза быстрее хеш‑таблицы и в 176 раз быстрее связного списка. Поиск выполняется за **0.00024 секунды** (в 16 раз быстрее хеш‑таблицы), удаление – за **0.00017 секунды** (почти в 10 раз быстрее). Это идеальный случай сбалансированного дерева.
|
||||
|
||||
**На отсортированных данных** ситуация кардинально меняется. Дерево вырождается в линейный список, и производительность падает катастрофически. Вставка замедлилась до **10.15 секунды** – это в 406 раз медленнее, чем на случайных данных, и даже медленнее, чем у связного списка (в 2.3 раза). Поиск вырос до **0.091 секунды** (в 380 раз медленнее), удаление – до **0.057 секунды** (в 335 раз медленнее). Это классический пример деградации BST при упорядоченных входных данных.
|
||||
|
||||
## Анализ результатов
|
||||
|
||||
### Как порядок входных данных влияет на скорость вставки в BST
|
||||
|
||||
Эксперимент наглядно демонстрирует проблему наивной реализации двоичного дерева поиска. На случайных данных дерево остаётся достаточно сбалансированным, и операции выполняются за логарифмическое время (O(log n)). Однако на отсортированных данных каждый новый элемент становится самым большим и добавляется только в правую ветку. В результате дерево превращается в односвязный список высотой 10 000 узлов, а сложность всех операций деградирует до линейной O(n). Это подтверждается цифрами: время вставки выросло с 0.025 до 10.15 секунд – разница в 406 раз.
|
||||
|
||||
### Почему хеш‑таблица почти не чувствительна к порядку
|
||||
|
||||
Хеш‑функция распределяет ключи по корзинам независимо от того, в каком порядке они поступают. «User_00001» и «User_10000» с равной вероятностью могут попасть в любую из 10 корзин. Поэтому порядок ввода не влияет на длину цепочек в каждой корзине. Результаты подтверждают это: в случайном и отсортированном режимах время выполнения операций отличается незначительно (менее 10%).
|
||||
|
||||
### Почему связный список всегда медленен при поиске
|
||||
|
||||
Связный список не имеет индексов или другой структуры для ускорения доступа. Чтобы найти элемент, нужно в худшем случае пройти все 10 000 узлов. Поэтому поиск занимает ~0.027 секунды независимо от того, как расположены данные. Вставка тоже требует прохода до конца списка, что даёт ~4.4 секунды на 10 000 элементов.
|
||||
|
||||
### Как удаление работает в каждой структуре
|
||||
|
||||
Удаление тесно связано с поиском, потому что сначала нужно найти удаляемый элемент. Поэтому время удаления коррелирует со временем поиска:
|
||||
|
||||
- В связном списке удаление занимает ~0.012 секунды – примерно половину времени поиска (0.027 с), так как операция перелинковки дёшева.
|
||||
- В хеш‑таблице удаление (~0.0016 с) близко ко времени поиска (~0.004 с), опять же с поправкой на перелинковку в списке корзины.
|
||||
- В BST на случайных данных удаление (~0.00017 с) даже быстрее поиска (~0.00024 с) из-за особенностей рекурсивной реализации.
|
||||
- В BST на отсортированных данных удаление (~0.057 с) занимает примерно половину времени поиска (~0.091 с) – та же закономерность, что и у списка, потому что вырожденное дерево ведёт себя как список.
|
||||
|
||||
## Выводы
|
||||
|
||||
**Какую структуру и для каких задач стоит выбирать в реальной жизни?**
|
||||
|
||||
1. **Хеш‑таблица** – оптимальный выбор для подавляющего большинства сценариев, где нужен быстрый доступ по ключу (словари, кэши, индексы в базах данных). Она стабильна, предсказуема и не зависит от порядка данных. В моём эксперименте она уступила BST на случайных данных, но выиграла у BST на отсортированных и оказалась намного быстрее связного списка. Главный минус – отсутствие естественного порядка при обходе.
|
||||
|
||||
2. **Сбалансированное дерево** (AVL или красно-чёрное) – выбор, когда нужны оба свойства: быстрый доступ (O(log n)) и возможность получать данные в отсортированном порядке без дополнительной сортировки. Обычный BST (как в моей реализации) **использовать не стоит**, если нельзя гарантировать случайный порядок входных данных. Деградация на упорядоченных данных делает его непригодным для реальных систем.
|
||||
|
||||
3. **Связный список** – практически бесполезен для хранения больших объёмов данных. Единственное оправданное применение – очень маленькие коллекции (до сотни элементов), реализация очередей/стеков или учебные цели.
|
||||
|
||||
**Рекомендация**: если нужен только быстрый доступ по ключу – берите хеш-таблицу. Если нужен отсортированный вывод и вы готовы пожертвовать небольшой долей производительности – используйте сбалансированное дерево. От наивного BST и связного списка в реальных проектах лучше отказаться.
|
||||
|
||||
**Ключевой вывод эксперимента**: порядок поступления данных критически важен для производительности BST (разница в 400 раз между случайными и отсортированными данными), но почти не влияет на хеш-таблицу и связный список (хотя последний всегда медленный).
|
||||
Loading…
Reference in New Issue
Block a user