Merge pull request '[1] lab1' (#190) from shalovsa/2026-rff_mp:lab1 into develop
Reviewed-on: #190
This commit is contained in:
commit
3b1c21a8b1
BIN
shalovsa/lab1/docs/data/comparison_by_operation.png
Normal file
BIN
shalovsa/lab1/docs/data/comparison_by_operation.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 97 KiB |
210
shalovsa/lab1/docs/data/laba.py
Normal file
210
shalovsa/lab1/docs/data/laba.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()
|
||||||
168
shalovsa/lab1/docs/data/phone_book.py
Normal file
168
shalovsa/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
shalovsa/lab1/docs/data/plot_results.py
Normal file
128
shalovsa/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
shalovsa/lab1/docs/data/results.csv
Normal file
19
shalovsa/lab1/docs/data/results.csv
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
Структура,Режим,Операция,Среднее (с),Замер_1,Замер_2,Замер_3,Замер_4,Замер_5
|
||||||
|
LinkedList,случайный,insert,2.013381,2.030341,1.985393,2.000117,2.000130,2.050923
|
||||||
|
LinkedList,случайный,find,0.026258,0.026263,0.027186,0.026271,0.026350,0.025217
|
||||||
|
LinkedList,случайный,delete,0.015552,0.017207,0.014387,0.015304,0.015317,0.015547
|
||||||
|
HashTable,случайный,insert,0.014448,0.014376,0.014608,0.015115,0.013931,0.014212
|
||||||
|
HashTable,случайный,find,0.000161,0.000162,0.000161,0.000161,0.000158,0.000161
|
||||||
|
HashTable,случайный,delete,0.000088,0.000089,0.000089,0.000088,0.000086,0.000086
|
||||||
|
BST,случайный,insert,0.015575,0.015822,0.015653,0.015507,0.015398,0.015496
|
||||||
|
BST,случайный,find,0.000133,0.000135,0.000133,0.000131,0.000133,0.000133
|
||||||
|
BST,случайный,delete,0.000100,0.000104,0.000100,0.000100,0.000099,0.000099
|
||||||
|
LinkedList,сортированный,insert,1.890415,1.937600,1.916341,1.863388,1.872563,1.862181
|
||||||
|
LinkedList,сортированный,find,0.023136,0.030373,0.021794,0.021670,0.020861,0.020980
|
||||||
|
LinkedList,сортированный,delete,0.014986,0.017694,0.014155,0.014365,0.014293,0.014424
|
||||||
|
HashTable,сортированный,insert,0.017723,0.015734,0.019191,0.019721,0.015843,0.018128
|
||||||
|
HashTable,сортированный,find,0.000223,0.000263,0.000206,0.000226,0.000154,0.000264
|
||||||
|
HashTable,сортированный,delete,0.000125,0.000144,0.000112,0.000132,0.000094,0.000141
|
||||||
|
BST,сортированный,insert,3.535999,3.577140,3.570114,3.581931,3.485064,3.465746
|
||||||
|
BST,сортированный,find,0.030666,0.030318,0.033654,0.030108,0.029537,0.029713
|
||||||
|
BST,сортированный,delete,0.038312,0.037349,0.040109,0.037735,0.036555,0.039810
|
||||||
|
BIN
shalovsa/lab1/docs/data/sorted_vs_random.png
Normal file
BIN
shalovsa/lab1/docs/data/sorted_vs_random.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 62 KiB |
130
shalovsa/lab1/docs/report.md
Normal file
130
shalovsa/lab1/docs/report.md
Normal file
|
|
@ -0,0 +1,130 @@
|
||||||
|
# Отчёт: Задание 1 — Структуры данных
|
||||||
|
|
||||||
|
## Цель работы
|
||||||
|
|
||||||
|
Разработать три структуры данных «с нуля» в процедурном стиле (без ООП), применить их для хранения записей телефонной книги и провести экспериментальное сравнение производительности ключевых операций.
|
||||||
|
|
||||||
|
**Структуры данных:**
|
||||||
|
- Связный список (LinkedList)
|
||||||
|
- Хеш-таблица (HashTable)
|
||||||
|
- Двоичное дерево поиска (BST)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Реализация
|
||||||
|
|
||||||
|
### Основные технические решения
|
||||||
|
|
||||||
|
#### 1. Связный список
|
||||||
|
|
||||||
|
Узел реализован как Python-словарь: `{'name': 'Имя', 'phone': '123', 'next': None}`.
|
||||||
|
|
||||||
|
Новые элементы добавляются **в начало** списка за O(1) (при условии отсутствия имени), обновление требует прохода по списку O(n). Поиск и удаление работают за линейное время из-за отсутствия прямого доступа по индексу.
|
||||||
|
|
||||||
|
#### 2. Хеш-таблица
|
||||||
|
|
||||||
|
Фиксированный массив на 256 корзин. Каждая корзина — указатель на связный список (метод цепочек). Хеш-функция: стандартный `hash(name) % size`. Среднее время операций O(1), при коллизиях — O(k), где k — длина цепочки.
|
||||||
|
|
||||||
|
#### 3. Двоичное дерево поиска (BST)
|
||||||
|
|
||||||
|
Узел: `{'name': 'Имя', 'phone': '123', 'left': None, 'right': None}`. Сравнение ключей — лексикографическое по полю name. Вставка и поиск реализованы итеративно. Удаление — рекурсивное с заменой на минимальный узел правого поддерева. Обход в глубину даёт отсортированный список.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Экспериментальная часть
|
||||||
|
|
||||||
|
### Условия проведения замеров
|
||||||
|
|
||||||
|
| Параметр | Значение |
|
||||||
|
|---|---|
|
||||||
|
| Количество записей (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 раз)
|
||||||
|
- Поиск: ~0.0002 с (быстрее списка в ~160 раз)
|
||||||
|
|
||||||
|
Хеш-функция равномерно распределяет ключи независимо от порядка их поступления, поэтому производительность остаётся стабильной.
|
||||||
|
|
||||||
|
### 3. BST катастрофически деградирует на упорядоченных данных
|
||||||
|
|
||||||
|
Наиболее показательный результат эксперимента:
|
||||||
|
|
||||||
|
| | Случайный | Сортированный | Ухудшение |
|
||||||
|
|---|---|---|---|
|
||||||
|
| BST insert | 0.017 с | **3.854 с** | **×225** |
|
||||||
|
| BST find | 0.000145 с | **0.033 с** | **×231** |
|
||||||
|
|
||||||
|
**Причина:** при вставке отсортированных данных дерево вырождается в линейный список — каждый новый элемент оказывается больше предыдущего и помещается только в правую ветку. Высота дерева становится O(n) вместо O(log n), что превращает все операции в линейные.
|
||||||
|
|
||||||
|
### 4. Операция delete
|
||||||
|
|
||||||
|
На случайных данных BST удаляет за **~0.0001 с** (логарифмическая сложность). На сортированных — **~0.046 с** (линейная деградация). HashTable показывает стабильные **~0.00012 с** независимо от порядка.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Выводы и практические рекомендации
|
||||||
|
|
||||||
|
### Выбор структуры в зависимости от задачи
|
||||||
|
|
||||||
|
| Сценарий | Рекомендация |
|
||||||
|
|---|---|
|
||||||
|
| **Частый поиск по ключу** | HashTable или BST (случайный порядок) |
|
||||||
|
| **Данные поступают упорядоченно** | HashTable (BST непригоден) |
|
||||||
|
| **Требуется отсортированный вывод** | BST (обход даёт порядок за O(n)) |
|
||||||
|
| **Интенсивные вставки/удаления + поиск** | HashTable |
|
||||||
|
| **Ограниченный объём данных, простота** | LinkedList (до сотен элементов) |
|
||||||
|
| **Диапазонные запросы** (например, A–M) | BST |
|
||||||
|
|
||||||
|
### Теоретическая сложность операций
|
||||||
|
|
||||||
|
| Структура | Insert | Find | Delete | Обход (отсорт.) |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| LinkedList | O(n) | O(n) | O(n) | O(n log n) |
|
||||||
|
| HashTable | O(1) в среднем | O(1) в среднем | O(1) в среднем | 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) |
|
||||||
|
|
||||||
|
### Ключевой вывод
|
||||||
|
|
||||||
|
Для телефонного справочника с частыми поисками и обновлениями оптимальный выбор — **хеш-таблица**. BST выигрывает только при необходимости получать отсортированные данные без дополнительной сортировки, но требует либо случайного порядка вставки, либо использования самобалансирующихся вариантов (AVL, красно-чёрное дерево).
|
||||||
Loading…
Reference in New Issue
Block a user