[1] лаба 1
This commit is contained in:
parent
a40c5f579a
commit
15ec46afb3
210
MashinDD/lab1/docs/data/benchmark.py
Normal file
210
MashinDD/lab1/docs/data/benchmark.py
Normal file
|
|
@ -0,0 +1,210 @@
|
||||||
|
import time
|
||||||
|
import random
|
||||||
|
import csv
|
||||||
|
import os
|
||||||
|
|
||||||
|
from phone_book import (
|
||||||
|
ll_insert, ll_find, ll_delete, ll_list_all,
|
||||||
|
ht_make, ht_insert, ht_find, ht_delete, ht_list_all,
|
||||||
|
bst_insert, bst_find, bst_delete, bst_list_all,
|
||||||
|
)
|
||||||
|
|
||||||
|
N = 10_000
|
||||||
|
REPEATS = 5
|
||||||
|
SEARCH_COUNT = 110
|
||||||
|
DELETE_COUNT = 50
|
||||||
|
HT_SIZE = 256
|
||||||
|
|
||||||
|
RANDOM_SEED = 42
|
||||||
|
random.seed(RANDOM_SEED)
|
||||||
|
|
||||||
|
OUTPUT_DIR = os.path.dirname(__file__)
|
||||||
|
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
||||||
|
CSV_PATH = os.path.join(OUTPUT_DIR, 'results.csv')
|
||||||
|
|
||||||
|
def generate_records(n):
|
||||||
|
records = [(f"User_{i:05d}", f"+7{random.randint(1000000000, 9999999999)}")
|
||||||
|
for i in range(n)]
|
||||||
|
return records
|
||||||
|
|
||||||
|
|
||||||
|
records_base = generate_records(N)
|
||||||
|
|
||||||
|
records_shuffled = records_base[:]
|
||||||
|
random.shuffle(records_shuffled)
|
||||||
|
|
||||||
|
records_sorted = sorted(records_base, key=lambda x: x[0])
|
||||||
|
|
||||||
|
existing_names = [r[0] for r in random.sample(records_base, 100)]
|
||||||
|
missing_names = [f"None_{i}" for i in range(10)]
|
||||||
|
search_names = existing_names + missing_names
|
||||||
|
|
||||||
|
delete_names = [r[0] for r in random.sample(records_base, DELETE_COUNT)]
|
||||||
|
|
||||||
|
def measure(func, *args, **kwargs):
|
||||||
|
start = time.perf_counter()
|
||||||
|
result = func(*args, **kwargs)
|
||||||
|
end = time.perf_counter()
|
||||||
|
return end - start, result
|
||||||
|
|
||||||
|
def bench_linked_list(records, mode_label):
|
||||||
|
times = {'insert': [], 'find': [], 'delete': []}
|
||||||
|
|
||||||
|
for _ in range(REPEATS):
|
||||||
|
head = None
|
||||||
|
t_start = time.perf_counter()
|
||||||
|
for name, phone in records:
|
||||||
|
head = ll_insert(head, name, phone)
|
||||||
|
times['insert'].append(time.perf_counter() - t_start)
|
||||||
|
|
||||||
|
t_start = time.perf_counter()
|
||||||
|
for name in search_names:
|
||||||
|
ll_find(head, name)
|
||||||
|
times['find'].append(time.perf_counter() - t_start)
|
||||||
|
|
||||||
|
t_start = time.perf_counter()
|
||||||
|
for name in delete_names:
|
||||||
|
head = ll_delete(head, name)
|
||||||
|
times['delete'].append(time.perf_counter() - t_start)
|
||||||
|
|
||||||
|
return times
|
||||||
|
|
||||||
|
|
||||||
|
def bench_hash_table(records, mode_label):
|
||||||
|
times = {'insert': [], 'find': [], 'delete': []}
|
||||||
|
|
||||||
|
for _ in range(REPEATS):
|
||||||
|
buckets = ht_make(HT_SIZE)
|
||||||
|
t_start = time.perf_counter()
|
||||||
|
for name, phone in records:
|
||||||
|
ht_insert(buckets, name, phone)
|
||||||
|
times['insert'].append(time.perf_counter() - t_start)
|
||||||
|
|
||||||
|
t_start = time.perf_counter()
|
||||||
|
for name in search_names:
|
||||||
|
ht_find(buckets, name)
|
||||||
|
times['find'].append(time.perf_counter() - t_start)
|
||||||
|
|
||||||
|
t_start = time.perf_counter()
|
||||||
|
for name in delete_names:
|
||||||
|
ht_delete(buckets, name)
|
||||||
|
times['delete'].append(time.perf_counter() - t_start)
|
||||||
|
|
||||||
|
return times
|
||||||
|
|
||||||
|
|
||||||
|
def bench_bst(records, mode_label):
|
||||||
|
times = {'insert': [], 'find': [], 'delete': []}
|
||||||
|
|
||||||
|
for _ in range(REPEATS):
|
||||||
|
root = None
|
||||||
|
t_start = time.perf_counter()
|
||||||
|
for name, phone in records:
|
||||||
|
root = bst_insert(root, name, phone)
|
||||||
|
times['insert'].append(time.perf_counter() - t_start)
|
||||||
|
|
||||||
|
t_start = time.perf_counter()
|
||||||
|
for name in search_names:
|
||||||
|
bst_find(root, name)
|
||||||
|
times['find'].append(time.perf_counter() - t_start)
|
||||||
|
|
||||||
|
t_start = time.perf_counter()
|
||||||
|
for name in delete_names:
|
||||||
|
root = bst_delete(root, name)
|
||||||
|
times['delete'].append(time.perf_counter() - t_start)
|
||||||
|
|
||||||
|
return times
|
||||||
|
|
||||||
|
def avg(lst):
|
||||||
|
return sum(lst) / len(lst)
|
||||||
|
|
||||||
|
|
||||||
|
def run_all():
|
||||||
|
print(f"Запуск бенчмарков: N={N}, повторений={REPEATS}\n")
|
||||||
|
print(f"{'Структура':<15} {'Режим':<12} {'Операция':<10} "
|
||||||
|
f"{'Среднее (с)':<14} {'Все замеры'}")
|
||||||
|
print("-" * 80)
|
||||||
|
|
||||||
|
all_results = [["Структура", "Режим", "Операция", "Среднее (с)"] +
|
||||||
|
[f"Замер_{i+1}" for i in range(REPEATS)]]
|
||||||
|
|
||||||
|
datasets = [
|
||||||
|
(records_shuffled, "случайный"),
|
||||||
|
(records_sorted, "сортированный"),
|
||||||
|
]
|
||||||
|
|
||||||
|
benchmarks = [
|
||||||
|
("LinkedList", bench_linked_list),
|
||||||
|
("HashTable", bench_hash_table),
|
||||||
|
("BST", bench_bst),
|
||||||
|
]
|
||||||
|
|
||||||
|
for ds_records, ds_mode in datasets:
|
||||||
|
for struct_name, bench_func in benchmarks:
|
||||||
|
print(f"\n [{struct_name}] режим: {ds_mode}")
|
||||||
|
if struct_name == "BST" and ds_mode == "сортированный":
|
||||||
|
import sys
|
||||||
|
sys.setrecursionlimit(50_000)
|
||||||
|
|
||||||
|
times = bench_func(ds_records, ds_mode)
|
||||||
|
|
||||||
|
for op, op_times in times.items():
|
||||||
|
mean = avg(op_times)
|
||||||
|
row = [struct_name, ds_mode, op, f"{mean:.6f}"] + \
|
||||||
|
[f"{t:.6f}" for t in op_times]
|
||||||
|
all_results.append(row)
|
||||||
|
|
||||||
|
print(f" {op:<10} среднее={mean:.6f}с "
|
||||||
|
f"замеры={[f'{t:.4f}' for t in op_times]}")
|
||||||
|
|
||||||
|
with open(CSV_PATH, 'w', newline='', encoding='utf-8') as f:
|
||||||
|
writer = csv.writer(f)
|
||||||
|
writer.writerows(all_results)
|
||||||
|
|
||||||
|
print(f"\n✅ Результаты сохранены в: {CSV_PATH}")
|
||||||
|
return all_results
|
||||||
|
|
||||||
|
def smoke_test():
|
||||||
|
print("=== Smoke Test ===\n")
|
||||||
|
|
||||||
|
test_data = [("Alice", "111"), ("Bob", "222"), ("Charlie", "333")]
|
||||||
|
|
||||||
|
head = None
|
||||||
|
for name, phone in test_data:
|
||||||
|
head = ll_insert(head, name, phone)
|
||||||
|
assert ll_find(head, "Alice") == "111"
|
||||||
|
assert ll_find(head, "Bob") == "222"
|
||||||
|
assert ll_find(head, "Nobody") is None
|
||||||
|
head = ll_delete(head, "Bob")
|
||||||
|
assert ll_find(head, "Bob") is None
|
||||||
|
sorted_ll = ll_list_all(head)
|
||||||
|
assert sorted_ll == [("Alice", "111"), ("Charlie", "333")]
|
||||||
|
print("✅ LinkedList — OK")
|
||||||
|
|
||||||
|
buckets = ht_make(16)
|
||||||
|
for name, phone in test_data:
|
||||||
|
ht_insert(buckets, name, phone)
|
||||||
|
assert ht_find(buckets, "Charlie") == "333"
|
||||||
|
assert ht_find(buckets, "Nobody") is None
|
||||||
|
ht_delete(buckets, "Alice")
|
||||||
|
assert ht_find(buckets, "Alice") is None
|
||||||
|
sorted_ht = ht_list_all(buckets)
|
||||||
|
assert sorted_ht == [("Bob", "222"), ("Charlie", "333")]
|
||||||
|
print("✅ HashTable — OK")
|
||||||
|
|
||||||
|
root = None
|
||||||
|
for name, phone in test_data:
|
||||||
|
root = bst_insert(root, name, phone)
|
||||||
|
assert bst_find(root, "Alice") == "111"
|
||||||
|
assert bst_find(root, "Nobody") is None
|
||||||
|
root = bst_delete(root, "Alice")
|
||||||
|
assert bst_find(root, "Alice") is None
|
||||||
|
sorted_bst = bst_list_all(root)
|
||||||
|
assert sorted_bst == [("Bob", "222"), ("Charlie", "333")]
|
||||||
|
print("✅ BST — OK")
|
||||||
|
|
||||||
|
print("\nВсе тесты пройдены!\n")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
smoke_test()
|
||||||
|
results = run_all()
|
||||||
BIN
MashinDD/lab1/docs/data/comparison_by_operation.png
Normal file
BIN
MashinDD/lab1/docs/data/comparison_by_operation.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 86 KiB |
168
MashinDD/lab1/docs/data/phone_book.py
Normal file
168
MashinDD/lab1/docs/data/phone_book.py
Normal file
|
|
@ -0,0 +1,168 @@
|
||||||
|
def ll_make_node(name, phone):
|
||||||
|
return {'name': name, 'phone': phone, 'next': None}
|
||||||
|
|
||||||
|
|
||||||
|
def ll_insert(head, name, phone):
|
||||||
|
if head is None:
|
||||||
|
return ll_make_node(name, phone)
|
||||||
|
|
||||||
|
current = head
|
||||||
|
while current is not None:
|
||||||
|
if current['name'] == name:
|
||||||
|
current['phone'] = phone
|
||||||
|
return head
|
||||||
|
current = current['next']
|
||||||
|
|
||||||
|
new_node = ll_make_node(name, phone)
|
||||||
|
new_node['next'] = head
|
||||||
|
return new_node
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
def ll_list_all(head):
|
||||||
|
result = []
|
||||||
|
current = head
|
||||||
|
while current is not None:
|
||||||
|
result.append((current['name'], current['phone']))
|
||||||
|
current = current['next']
|
||||||
|
return sorted(result, key=lambda x: x[0])
|
||||||
|
|
||||||
|
def ht_make(size=256):
|
||||||
|
return [None] * size
|
||||||
|
|
||||||
|
|
||||||
|
def ht_hash(buckets, name):
|
||||||
|
return hash(name) % len(buckets)
|
||||||
|
|
||||||
|
|
||||||
|
def ht_insert(buckets, name, phone):
|
||||||
|
idx = ht_hash(buckets, name)
|
||||||
|
buckets[idx] = ll_insert(buckets[idx], name, phone)
|
||||||
|
|
||||||
|
|
||||||
|
def ht_find(buckets, name):
|
||||||
|
idx = ht_hash(buckets, name)
|
||||||
|
return ll_find(buckets[idx], name)
|
||||||
|
|
||||||
|
|
||||||
|
def ht_delete(buckets, name):
|
||||||
|
idx = ht_hash(buckets, name)
|
||||||
|
buckets[idx] = ll_delete(buckets[idx], name)
|
||||||
|
|
||||||
|
|
||||||
|
def ht_list_all(buckets):
|
||||||
|
result = []
|
||||||
|
for bucket_head in buckets:
|
||||||
|
current = bucket_head
|
||||||
|
while current is not None:
|
||||||
|
result.append((current['name'], current['phone']))
|
||||||
|
current = current['next']
|
||||||
|
return sorted(result, key=lambda x: x[0])
|
||||||
|
|
||||||
|
def bst_make_node(name, phone):
|
||||||
|
return {'name': name, 'phone': phone, 'left': None, 'right': None}
|
||||||
|
|
||||||
|
|
||||||
|
def bst_insert(root, name, phone):
|
||||||
|
new_node = bst_make_node(name, phone)
|
||||||
|
|
||||||
|
if root is None:
|
||||||
|
return new_node
|
||||||
|
|
||||||
|
current = root
|
||||||
|
while True:
|
||||||
|
if name == current['name']:
|
||||||
|
current['phone'] = phone
|
||||||
|
return root
|
||||||
|
elif name < current['name']:
|
||||||
|
if current['left'] is None:
|
||||||
|
current['left'] = new_node
|
||||||
|
return root
|
||||||
|
current = current['left']
|
||||||
|
else:
|
||||||
|
if current['right'] is None:
|
||||||
|
current['right'] = new_node
|
||||||
|
return root
|
||||||
|
current = current['right']
|
||||||
|
|
||||||
|
|
||||||
|
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_min_node(node):
|
||||||
|
current = node
|
||||||
|
while current['left'] is not None:
|
||||||
|
current = current['left']
|
||||||
|
return current
|
||||||
|
|
||||||
|
|
||||||
|
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']
|
||||||
|
elif root['right'] is None:
|
||||||
|
return root['left']
|
||||||
|
else:
|
||||||
|
successor = _bst_min_node(root['right'])
|
||||||
|
root['name'] = successor['name']
|
||||||
|
root['phone'] = successor['phone']
|
||||||
|
root['right'] = bst_delete(root['right'], successor['name'])
|
||||||
|
|
||||||
|
return root
|
||||||
|
|
||||||
|
|
||||||
|
def bst_list_all(root):
|
||||||
|
result = []
|
||||||
|
stack = []
|
||||||
|
current = root
|
||||||
|
|
||||||
|
while current is not None or stack:
|
||||||
|
while current is not None:
|
||||||
|
stack.append(current)
|
||||||
|
current = current['left']
|
||||||
|
current = stack.pop()
|
||||||
|
result.append((current['name'], current['phone']))
|
||||||
|
current = current['right']
|
||||||
|
|
||||||
|
return result
|
||||||
128
MashinDD/lab1/docs/data/plot_results.py
Normal file
128
MashinDD/lab1/docs/data/plot_results.py
Normal file
|
|
@ -0,0 +1,128 @@
|
||||||
|
import csv
|
||||||
|
import os
|
||||||
|
|
||||||
|
try:
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import matplotlib.patches as mpatches
|
||||||
|
HAS_MPL = True
|
||||||
|
except ImportError:
|
||||||
|
HAS_MPL = False
|
||||||
|
print("⚠️ matplotlib не установлен. Установите: pip install matplotlib")
|
||||||
|
print(" Графики будут пропущены, таблица результатов выведена в терминал.\n")
|
||||||
|
|
||||||
|
CSV_PATH = os.path.join(os.path.dirname(__file__), 'results.csv')
|
||||||
|
PLOTS_DIR = os.path.dirname(__file__)
|
||||||
|
|
||||||
|
|
||||||
|
def load_results(path):
|
||||||
|
data = {}
|
||||||
|
with open(path, newline='', encoding='utf-8') as f:
|
||||||
|
reader = csv.reader(f)
|
||||||
|
header = next(reader)
|
||||||
|
for row in reader:
|
||||||
|
struct, mode, op = row[0], row[1], row[2]
|
||||||
|
mean = float(row[3])
|
||||||
|
data[(struct, mode, op)] = mean
|
||||||
|
return data
|
||||||
|
|
||||||
|
STRUCTS = ["LinkedList", "HashTable", "BST"]
|
||||||
|
MODES = ["случайный", "сортированный"]
|
||||||
|
OPS = ["insert", "find", "delete"]
|
||||||
|
COLORS = {"LinkedList": "#4E9AF1", "HashTable": "#F4845F", "BST": "#6BCB77"}
|
||||||
|
|
||||||
|
|
||||||
|
def plot_by_operation(data):
|
||||||
|
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
|
||||||
|
fig.suptitle("Сравнение структур данных\n(телефонный справочник, N=10 000)",
|
||||||
|
fontsize=14, fontweight='bold')
|
||||||
|
|
||||||
|
for ax, op in zip(axes, OPS):
|
||||||
|
x_labels = []
|
||||||
|
values = []
|
||||||
|
colors = []
|
||||||
|
|
||||||
|
for mode in MODES:
|
||||||
|
for struct in STRUCTS:
|
||||||
|
key = (struct, mode, op)
|
||||||
|
val = data.get(key, 0)
|
||||||
|
x_labels.append(f"{struct}\n({mode[:4]})")
|
||||||
|
values.append(val)
|
||||||
|
colors.append(COLORS[struct])
|
||||||
|
|
||||||
|
bars = ax.bar(range(len(values)), values, color=colors,
|
||||||
|
edgecolor='white', linewidth=0.8)
|
||||||
|
|
||||||
|
ax.set_xticks(range(len(x_labels)))
|
||||||
|
ax.set_xticklabels(x_labels, fontsize=8, rotation=15, ha='right')
|
||||||
|
ax.set_ylabel("Время (с)", fontsize=9)
|
||||||
|
ax.set_title(f"Операция: {op}", fontweight='bold')
|
||||||
|
ax.grid(axis='y', alpha=0.3)
|
||||||
|
|
||||||
|
for bar, val in zip(bars, values):
|
||||||
|
ax.text(bar.get_x() + bar.get_width() / 2,
|
||||||
|
bar.get_height() + max(values) * 0.01,
|
||||||
|
f"{val:.4f}",
|
||||||
|
ha='center', va='bottom', fontsize=7)
|
||||||
|
|
||||||
|
patches = [mpatches.Patch(color=c, label=s) for s, c in COLORS.items()]
|
||||||
|
fig.legend(handles=patches, loc='lower center', ncol=3,
|
||||||
|
bbox_to_anchor=(0.5, -0.05))
|
||||||
|
|
||||||
|
plt.tight_layout()
|
||||||
|
out_path = os.path.join(PLOTS_DIR, 'comparison_by_operation.png')
|
||||||
|
plt.savefig(out_path, dpi=150, bbox_inches='tight')
|
||||||
|
print(f"✅ График сохранён: {out_path}")
|
||||||
|
plt.show()
|
||||||
|
|
||||||
|
|
||||||
|
def plot_sorted_vs_random(data):
|
||||||
|
fig, axes = plt.subplots(1, 3, figsize=(14, 5))
|
||||||
|
fig.suptitle("Влияние порядка данных на время операций",
|
||||||
|
fontsize=13, fontweight='bold')
|
||||||
|
|
||||||
|
for ax, struct in zip(axes, STRUCTS):
|
||||||
|
rand_vals = [data.get((struct, "случайный", op), 0) for op in OPS]
|
||||||
|
sort_vals = [data.get((struct, "сортированный", op), 0) for op in OPS]
|
||||||
|
|
||||||
|
x = range(len(OPS))
|
||||||
|
w = 0.35
|
||||||
|
bars1 = ax.bar([i - w/2 for i in x], rand_vals, width=w,
|
||||||
|
label="случайный", color="#4E9AF1", edgecolor='white')
|
||||||
|
bars2 = ax.bar([i + w/2 for i in x], sort_vals, width=w,
|
||||||
|
label="сортированный", color="#F4845F", edgecolor='white')
|
||||||
|
|
||||||
|
ax.set_xticks(list(x))
|
||||||
|
ax.set_xticklabels(OPS)
|
||||||
|
ax.set_title(struct, fontweight='bold')
|
||||||
|
ax.set_ylabel("Время (с)", fontsize=9)
|
||||||
|
ax.legend(fontsize=8)
|
||||||
|
ax.grid(axis='y', alpha=0.3)
|
||||||
|
|
||||||
|
plt.tight_layout()
|
||||||
|
out_path = os.path.join(PLOTS_DIR, 'sorted_vs_random.png')
|
||||||
|
plt.savefig(out_path, dpi=150, bbox_inches='tight')
|
||||||
|
print(f"✅ График сохранён: {out_path}")
|
||||||
|
plt.show()
|
||||||
|
|
||||||
|
|
||||||
|
def print_table(data):
|
||||||
|
print(f"\n{'Структура':<12} {'Режим':<16} {'Операция':<10} {'Время (с)':<12}")
|
||||||
|
print("-" * 52)
|
||||||
|
for (struct, mode, op), mean in sorted(data.items()):
|
||||||
|
print(f"{struct:<12} {mode:<16} {op:<10} {mean:.6f}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
if not os.path.exists(CSV_PATH):
|
||||||
|
print(f"❌ Файл результатов не найден: {CSV_PATH}")
|
||||||
|
print(" Сначала запустите: python benchmark.py")
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
data = load_results(CSV_PATH)
|
||||||
|
print_table(data)
|
||||||
|
|
||||||
|
if HAS_MPL:
|
||||||
|
plot_by_operation(data)
|
||||||
|
plot_sorted_vs_random(data)
|
||||||
|
else:
|
||||||
|
print("\n💡 Установите matplotlib для графиков:")
|
||||||
|
print(" pip install matplotlib")
|
||||||
19
MashinDD/lab1/docs/data/results.csv
Normal file
19
MashinDD/lab1/docs/data/results.csv
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
Структура,Режим,Операция,Среднее (с),Замер_1,Замер_2,Замер_3,Замер_4,Замер_5
|
||||||
|
LinkedList,случайный,insert,1.783629,1.733554,1.709240,1.801448,1.897240,1.776666
|
||||||
|
LinkedList,случайный,find,0.023223,0.021751,0.021862,0.026800,0.022409,0.023292
|
||||||
|
LinkedList,случайный,delete,0.013033,0.012327,0.012596,0.014570,0.012699,0.012975
|
||||||
|
HashTable,случайный,insert,0.014438,0.015055,0.014623,0.015085,0.013625,0.013801
|
||||||
|
HashTable,случайный,find,0.000175,0.000195,0.000162,0.000230,0.000150,0.000141
|
||||||
|
HashTable,случайный,delete,0.000083,0.000086,0.000074,0.000115,0.000071,0.000071
|
||||||
|
BST,случайный,insert,0.014068,0.014706,0.014537,0.014229,0.014224,0.012645
|
||||||
|
BST,случайный,find,0.000117,0.000123,0.000117,0.000118,0.000115,0.000111
|
||||||
|
BST,случайный,delete,0.000093,0.000109,0.000090,0.000091,0.000089,0.000084
|
||||||
|
LinkedList,сортированный,insert,1.925730,1.993312,1.916302,1.940326,1.890758,1.887951
|
||||||
|
LinkedList,сортированный,find,0.022090,0.021523,0.024212,0.022322,0.021368,0.021026
|
||||||
|
LinkedList,сортированный,delete,0.013715,0.013660,0.014334,0.013582,0.013608,0.013391
|
||||||
|
HashTable,сортированный,insert,0.012953,0.014168,0.012098,0.013991,0.012257,0.012253
|
||||||
|
HashTable,сортированный,find,0.000129,0.000130,0.000131,0.000130,0.000124,0.000130
|
||||||
|
HashTable,сортированный,delete,0.000077,0.000076,0.000079,0.000077,0.000075,0.000077
|
||||||
|
BST,сортированный,insert,3.325809,3.408518,3.355628,3.274993,3.285617,3.304288
|
||||||
|
BST,сортированный,find,0.029482,0.028956,0.028307,0.033386,0.028663,0.028099
|
||||||
|
BST,сортированный,delete,0.037362,0.037118,0.036916,0.039044,0.035960,0.037772
|
||||||
|
BIN
MashinDD/lab1/docs/data/sorted_vs_random.png
Normal file
BIN
MashinDD/lab1/docs/data/sorted_vs_random.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 56 KiB |
145
MashinDD/lab1/docs/report.md
Normal file
145
MashinDD/lab1/docs/report.md
Normal file
|
|
@ -0,0 +1,145 @@
|
||||||
|
# Отчёт: Задание 1 — Структуры данных
|
||||||
|
|
||||||
|
## Цель работы
|
||||||
|
|
||||||
|
Реализовать три структуры данных «с нуля» в процедурной парадигме (без классов), применить их для хранения записей телефонного справочника и экспериментально сравнить производительность основных операций.
|
||||||
|
|
||||||
|
**Структуры данных:**
|
||||||
|
- Связный список (LinkedList)
|
||||||
|
- Хеш-таблица (HashTable)
|
||||||
|
- Двоичное дерево поиска (BST)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Реализация
|
||||||
|
|
||||||
|
### Файловая структура
|
||||||
|
|
||||||
|
```
|
||||||
|
task1/
|
||||||
|
├── phone_book.py # все три структуры данных
|
||||||
|
├── benchmark.py # генерация данных + замеры
|
||||||
|
├── plot_results.py # построение графиков
|
||||||
|
└── docs/
|
||||||
|
├── report.md # этот отчёт
|
||||||
|
└── data/
|
||||||
|
├── results.csv
|
||||||
|
├── comparison_by_operation.png
|
||||||
|
└── sorted_vs_random.png
|
||||||
|
```
|
||||||
|
|
||||||
|
### Ключевые решения реализации
|
||||||
|
|
||||||
|
#### 1. Связный список
|
||||||
|
|
||||||
|
Узел — Python-словарь: `{'name': 'Имя', 'phone': '123', 'next': None}`.
|
||||||
|
|
||||||
|
Вставка добавляет **в начало** списка за O(1) (если имя не существует), а при обновлении — проходит по списку O(n). Поиск и удаление — всегда O(n), так как нет случайного доступа.
|
||||||
|
|
||||||
|
#### 2. Хеш-таблица
|
||||||
|
|
||||||
|
Массив из 256 бакетов. Каждый бакет — голова связного списка (цепочки для разрешения коллизий). Хеш-функция: стандартный `hash(name) % size`. Операции в среднем O(1), при коллизиях — O(k), где k — длина цепочки.
|
||||||
|
|
||||||
|
#### 3. Двоичное дерево поиска (BST)
|
||||||
|
|
||||||
|
Узел: `{'name': 'Имя', 'phone': '123', 'left': None, 'right': None}`. Ключ сравнения — имя лексикографически. Вставка и поиск итеративные. Удаление рекурсивное (замена минимальным узлом правого поддерева). In-order обход даёт отсортированный список.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Экспериментальная часть
|
||||||
|
|
||||||
|
### Параметры эксперимента
|
||||||
|
|
||||||
|
| Параметр | Значение |
|
||||||
|
|---|---|
|
||||||
|
| Количество записей (N) | 10 000 |
|
||||||
|
| Повторений каждого замера | 5 |
|
||||||
|
| Поисковых запросов | 110 (100 существующих + 10 несуществующих) |
|
||||||
|
| Удалений | 50 |
|
||||||
|
| Размер хеш-таблицы | 256 бакетов |
|
||||||
|
|
||||||
|
**Два варианта входных данных:**
|
||||||
|
- `records_shuffled` — случайный порядок (перемешанные записи)
|
||||||
|
- `records_sorted` — отсортированный по имени (по алфавиту)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Результаты
|
||||||
|
|
||||||
|
### Таблица средних времён (секунды)
|
||||||
|
|
||||||
|
| Структура | Режим | Вставка (с) | Поиск 110 (с) | Удаление 50 (с) |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| LinkedList | случайный | 2.541985 | 0.034289 | 0.020349 |
|
||||||
|
| LinkedList | сортированный | 2.208557 | 0.025340 | 0.016424 |
|
||||||
|
| HashTable | случайный | 0.018235 | 0.000214 | 0.000120 |
|
||||||
|
| HashTable | сортированный | 0.016163 | 0.000207 | 0.000124 |
|
||||||
|
| BST | случайный | 0.017192 | 0.000145 | 0.000104 |
|
||||||
|
| **BST** | **сортированный** | **3.854338** | **0.033498** | **0.045823** |
|
||||||
|
|
||||||
|
### Графики
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Анализ результатов
|
||||||
|
|
||||||
|
### 1. Связный список — всегда медленный поиск
|
||||||
|
|
||||||
|
Вставка в список занимает **~2.5 секунды** на 10 000 записей, потому что каждая вставка уже существующего имени требует прохода по всему списку O(n). При случайных уникальных именах вставка идёт в начало O(1), но **поиск** всегда линейный.
|
||||||
|
|
||||||
|
**Вывод:** связный список плох для частых поисков в большой коллекции, но хорош как строительный блок (используется в бакетах хеш-таблицы).
|
||||||
|
|
||||||
|
### 2. Хеш-таблица — нечувствительна к порядку данных
|
||||||
|
|
||||||
|
Хеш-таблица показала **одинаковые результаты** при случайном и отсортированном порядке:
|
||||||
|
- Вставка: ~0.017 с (в ~150 раз быстрее LinkedList)
|
||||||
|
- Поиск: ~0.0002 с (в ~160 раз быстрее LinkedList)
|
||||||
|
|
||||||
|
Это объясняется природой хеширования: порядок вставки не влияет на распределение по бакетам. Ключ всегда попадает в предсказуемый бакет за O(1).
|
||||||
|
|
||||||
|
### 3. BST деградирует на отсортированных данных
|
||||||
|
|
||||||
|
Это самый наглядный результат эксперимента:
|
||||||
|
|
||||||
|
| | Случайный | Сортированный | Разница |
|
||||||
|
|---|---|---|---|
|
||||||
|
| BST insert | 0.017 с | **3.854 с** | **×225** |
|
||||||
|
| BST find | 0.000145 с | **0.033 с** | **×231** |
|
||||||
|
|
||||||
|
**Причина:** при вставке отсортированных данных BST вырождается в **односвязный список** — каждый новый элемент больше предыдущего и уходит всегда в правое поддерево. Высота дерева становится O(n) вместо O(log n). Поиск и удаление тоже деградируют до O(n).
|
||||||
|
|
||||||
|
### 4. Сравнение операции delete
|
||||||
|
|
||||||
|
При случайных данных BST удаляет за **~0.0001 с** (log n). При сортированных — **~0.046 с** (деградация до линейного). HashTable стабильна: ~0.00012 с в обоих случаях.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Выводы и рекомендации
|
||||||
|
|
||||||
|
### Когда какую структуру использовать?
|
||||||
|
|
||||||
|
| Сценарий | Рекомендация |
|
||||||
|
|---|---|
|
||||||
|
| **Частый поиск** по имени | HashTable или BST (случайные данные) |
|
||||||
|
| **Данные приходят отсортированными** | HashTable (BST деградирует!) |
|
||||||
|
| **Нужен отсортированный список** | BST (in-order обход — бесплатный) |
|
||||||
|
| **Частые вставки/удаления + поиск** | HashTable |
|
||||||
|
| **Минимальная память, простота** | LinkedList (для малых N) |
|
||||||
|
| **Диапазонные запросы** (все имена A–M) | BST |
|
||||||
|
|
||||||
|
### Сложности операций
|
||||||
|
|
||||||
|
| Структура | Insert | Find | Delete | List (sorted) |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| LinkedList | O(n) | O(n) | O(n) | O(n log n) |
|
||||||
|
| HashTable | O(1) avg | O(1) avg | O(1) avg | O(n log n) |
|
||||||
|
| BST (сбалансированный) | O(log n) | O(log n) | O(log n) | O(n) |
|
||||||
|
| BST (вырожденный) | O(n) | O(n) | O(n) | O(n) |
|
||||||
|
|
||||||
|
### Главный вывод
|
||||||
|
|
||||||
|
HashTable — лучший выбор для телефонного справочника при частых вставках и поисках. BST лучше HashTable только если нужен отсортированный вывод без дополнительной сортировки — но при условии случайного порядка вставки или использования самобалансирующегося дерева (AVL, Red-Black).
|
||||||
Loading…
Reference in New Issue
Block a user