diff --git a/zverevem/lab1/docs/data/experiments.py b/zverevem/lab1/docs/data/experiments.py new file mode 100644 index 0000000..349e57b --- /dev/null +++ b/zverevem/lab1/docs/data/experiments.py @@ -0,0 +1,282 @@ +import random +import time +import csv +import os +from phonebook import * + +def generate_test_data(n=10000): + + records = [(f"User_{i:05d}", f"+7-999-{i:07d}") for i in range(n)] + + records_shuffled = records.copy() + random.shuffle(records_shuffled) + + records_sorted = sorted(records, key=lambda x: x[0]) + + return records_shuffled, records_sorted + +def get_random_names(records, n=100): + return[name for name, _ in random.sample(records, min(n, len(records)))] + +def run_linked_experiments(records, mode_name): + + print(f"\n связный список ({mode_name}):") + + print("вставка 10000 записей:") + + insert_times = [] + for run in range(5): + start = time.perf_counter() + head = None + for name, phone in records: + head = ll_insert(head, name, phone) + end = time.perf_counter() + insert_times.append(end - start) + print(f"Вставка {run+1}/5: {insert_times[-1]:.6f} сек") + + avg_insert = sum(insert_times) / 5 + print(f"среднее: {avg_insert:.6f} сек") + + print("поиск 110 записей:") + + exist_names = get_random_names(records, 100) + non_exist_names = [f"None_{i}" for i in range(10)] + + find_times = [] + for run in range(5): + start = time.perf_counter() + + for name in exist_names: + ll_find(head, name) + for name in non_exist_names: + ll_find(head, name) + + end = time.perf_counter() + find_times.append(end - start) + print(f"поиск {run+1}/5: {find_times[-1]:.6f} сек") + + avg_find = sum(find_times) / 5 + print(f"среднее: {avg_find:.6f} сек") + + print("удаление 50 случайных записей:") + + to_delete = get_random_names(records,50) + + delete_times = [] + for run in range(5): + current_head = head + start = time.perf_counter() + for name in to_delete: + current_head = ll_delete(current_head, name) + end = time.perf_counter() + delete_times.append(end - start) + print(f"удаление {run+1}/5: {delete_times[-1]:.6f} сек") + + avg_delete = sum(delete_times) / 5 + print(f"среднее: {avg_delete:.6f} сек") + + return{ + 'structure': 'LinkedList', + 'mode': mode_name, + 'insert_avg': avg_insert, + 'insert_all': insert_times, + 'find_avg': avg_find, + 'find_all': find_times, + 'delete_avg': avg_delete, + 'delete_all': delete_times + } + +def run_hash_experiments(records, mode_name): + + print(f"\n хеш-таблица({mode_name})") + + print("вставка 10000 записей:") + + insert_times = [] + for run in range(5): + start = time.perf_counter() + + buckets = ht_create(1000) + for name, phone in records: + buckets = ht_insert(buckets, name, phone) + + end = time.perf_counter() + insert_times.append(end - start) + print(f"Вставка {run+1}/5: {insert_times[-1]:.6f} сек") + + avg_insert = sum(insert_times) / 5 + print(f"среднее: {avg_insert:.6f} сек") + + print("поиск 110 записей:") + + exist_names = get_random_names(records, 100) + non_exist_names = [f"None_{i}" for i in range(10)] + + find_times = [] + for run in range(5): + start = time.perf_counter() + + for name in exist_names: + ht_find(buckets, name) + for name in non_exist_names: + ht_find(buckets, name) + + end = time.perf_counter() + find_times.append(end - start) + print(f"поиск {run+1}/5: {find_times[-1]:.6f} сек") + + avg_find = sum(find_times) / 5 + print(f"среднее: {avg_find:.6f} сек") + + print("удаление 50 случайных записей:") + + to_delete = get_random_names(records,50) + + delete_times = [] + for run in range(5): + current_buckets = buckets.copy() + start = time.perf_counter() + for name in to_delete: + current_buckets = ht_delete(current_buckets, name) + end = time.perf_counter() + delete_times.append(end - start) + print(f"удаление {run+1}/5: {delete_times[-1]:.6f} сек") + + avg_delete = sum(delete_times) / 5 + print(f"среднее: {avg_delete:.6f} сек") + + return{ + 'structure': 'HashTable', + 'mode': mode_name, + 'insert_avg': avg_insert, + 'insert_all': insert_times, + 'find_avg': avg_find, + 'find_all': find_times, + 'delete_avg': avg_delete, + 'delete_all': delete_times + } + +def run_bst_experiments(records, mode_name): + + print(f"\n двоичное дерево({mode_name})") + + print("вставка 10000 записей:") + + insert_times = [] + for run in range(5): + start = time.perf_counter() + + root = None + for name, phone in records: + root = bst_insert(root, name, phone) + + end = time.perf_counter() + insert_times.append(end - start) + print(f"Вставка {run+1}/5: {insert_times[-1]:.6f} сек") + + avg_insert = sum(insert_times) / 5 + print(f"среднее: {avg_insert:.6f} сек") + + print("поиск 110 записей:") + + exist_names = get_random_names(records, 100) + non_exist_names = [f"None_{i}" for i in range(10)] + + find_times = [] + for run in range(5): + start = time.perf_counter() + + for name in exist_names: + bst_find(root, name) + for name in non_exist_names: + bst_find(root, name) + + end = time.perf_counter() + find_times.append(end - start) + print(f"поиск {run+1}/5: {find_times[-1]:.6f} сек") + + avg_find = sum(find_times) / 5 + print(f"среднее: {avg_find:.6f} сек") + + print("удаление 50 случайных записей:") + + to_delete = get_random_names(records,50) + + delete_times = [] + for run in range(5): + current_root = root + start = time.perf_counter() + for name in to_delete: + current_root = bst_delete(current_root, name) + end = time.perf_counter() + delete_times.append(end - start) + print(f"удаление {run+1}/5: {delete_times[-1]:.6f} сек") + + avg_delete = sum(delete_times) / 5 + print(f"среднее: {avg_delete:.6f} сек") + + return{ + 'structure': 'BST', + 'mode': mode_name, + 'insert_avg': avg_insert, + 'insert_all': insert_times, + 'find_avg': avg_find, + 'find_all': find_times, + 'delete_avg': avg_delete, + 'delete_all': delete_times + } + +def save_results_to_csv(all_results): + + os.makedirs("docs/data", exist_ok=True) + + with open("docs/data/results.csv", "w", encoding="utf-8") as f: + + f.write("Структура, Режим, Операция, Замер, Время (сек)\n") + + for res in all_results: + struct = res['structure'] + mode = res['mode'] + + + for i, t in enumerate(res['insert_all']): + f.write(f"{struct},{mode},вставка,{i+1},{t}\n") + f.write(f"{struct},{mode},вставка,среднее,{res['insert_avg']}\n") + + + for i, t in enumerate(res['find_all']): + f.write(f"{struct},{mode},поиск,{i+1},{t}\n") + f.write(f"{struct},{mode},поиск,среднее,{res['find_avg']}\n") + + + for i, t in enumerate(res['delete_all']): + f.write(f"{struct},{mode},удаление,{i+1},{t}\n") + f.write(f"{struct},{mode},удаление,среднее,{res['delete_avg']}\n") + + +def main(): + print("эксперименты по замеру производительности") + + records_shuffled, records_sorted = generate_test_data(10000) + + all_results = [] + + print("режим: случайный порядок") + + all_results.append(run_linked_experiments(records_shuffled, "случайный")) + all_results.append(run_hash_experiments(records_shuffled, "случайный")) + all_results.append(run_bst_experiments(records_shuffled, "случайный")) + + print("режим: отсортированный порядок") + + all_results.append(run_linked_experiments(records_sorted, "отсортированный")) + all_results.append(run_hash_experiments(records_sorted, "отсортированный")) + all_results.append(run_bst_experiments(records_sorted, "отсортированный")) + + save_results_to_csv(all_results) + +if __name__== "__main__": + main() + + + diff --git a/zverevem/lab1/docs/data/graph_delete.png b/zverevem/lab1/docs/data/graph_delete.png new file mode 100644 index 0000000..2a52cac Binary files /dev/null and b/zverevem/lab1/docs/data/graph_delete.png differ diff --git a/zverevem/lab1/docs/data/graph_insert.png b/zverevem/lab1/docs/data/graph_insert.png new file mode 100644 index 0000000..7565e30 Binary files /dev/null and b/zverevem/lab1/docs/data/graph_insert.png differ diff --git a/zverevem/lab1/docs/data/graph_search.png b/zverevem/lab1/docs/data/graph_search.png new file mode 100644 index 0000000..e6eb899 Binary files /dev/null and b/zverevem/lab1/docs/data/graph_search.png differ diff --git a/zverevem/lab1/docs/data/make_graphs.py b/zverevem/lab1/docs/data/make_graphs.py new file mode 100644 index 0000000..e23e7b6 --- /dev/null +++ b/zverevem/lab1/docs/data/make_graphs.py @@ -0,0 +1,123 @@ + +import matplotlib.pyplot as plt +import numpy as np +import os + + +os.makedirs('docs/data', exist_ok=True) + + +structures = ['LinkedList', 'HashTable', 'BST'] + +random_insert = [0.0037545, 0.015088, 0.026280] +sorted_insert = [0.0017544, 0.011369, 4.930788] + +random_search = [0.00000962, 0.0001646, 0.0002592] +sorted_search = [0.00000858, 0.00014016, 0.047126] + +random_delete = [0.0000079, 0.00009824, 0.00016984] +sorted_delete = [0.00000294, 0.00005878, 0.023013] + +x = np.arange(len(structures)) +width = 0.35 + +#график вставка +fig, ax = plt.subplots(figsize=(12, 7)) + +bars1 = ax.bar(x - width/2, random_insert, width, label='Случайный порядок', color='#3498db') +bars2 = ax.bar(x + width/2, sorted_insert, width, label='Отсортированный порядок', color='#e74c3c') + + +for bar in bars1: + height = bar.get_height() + ax.annotate(f'{height:.4f}', xy=(bar.get_x() + bar.get_width()/2, height), + xytext=(0, 3), textcoords="offset points", ha='center', va='bottom', fontsize=9) + +for bar in bars2: + height = bar.get_height() + if height < 1: + ax.annotate(f'{height:.4f}', xy=(bar.get_x() + bar.get_width()/2, height), + xytext=(0, 3), textcoords="offset points", ha='center', va='bottom', fontsize=9) + else: + ax.annotate(f'{height:.1f} сек', xy=(bar.get_x() + bar.get_width()/2, height), + xytext=(0, 5), textcoords="offset points", ha='center', va='bottom', fontsize=10, fontweight='bold') + +ax.set_ylabel('Время (сек)', fontsize=12) +ax.set_title('Время вставки 10000 записей', fontsize=14, fontweight='bold') +ax.set_xticks(x) +ax.set_xticklabels(structures, fontsize=11) +ax.legend(fontsize=11) +ax.set_yscale('log') +ax.grid(True, alpha=0.3, axis='y') + +plt.tight_layout() +plt.savefig('docs/data/graph_insert.png', dpi=150, bbox_inches='tight') +plt.close() + + +# график поиск +fig, ax = plt.subplots(figsize=(12, 7)) + +bars1 = ax.bar(x - width/2, random_search, width, label='Случайный порядок', color='#3498db') +bars2 = ax.bar(x + width/2, sorted_search, width, label='Отсортированный порядок', color='#e74c3c') + +for bar in bars1: + height = bar.get_height() + ax.annotate(f'{height:.6f}', xy=(bar.get_x() + bar.get_width()/2, height), + xytext=(0, 3), textcoords="offset points", ha='center', va='bottom', fontsize=9) + +for bar in bars2: + height = bar.get_height() + if height < 0.01: + ax.annotate(f'{height:.6f}', xy=(bar.get_x() + bar.get_width()/2, height), + xytext=(0, 3), textcoords="offset points", ha='center', va='bottom', fontsize=9) + else: + ax.annotate(f'{height:.4f}', xy=(bar.get_x() + bar.get_width()/2, height), + xytext=(0, 3), textcoords="offset points", ha='center', va='bottom', fontsize=9) + +ax.set_ylabel('Время (сек)', fontsize=12) +ax.set_title('Время поиска 110 записей', fontsize=14, fontweight='bold') +ax.set_xticks(x) +ax.set_xticklabels(structures, fontsize=11) +ax.legend(fontsize=11) +ax.set_yscale('log') +ax.grid(True, alpha=0.3, axis='y') + +plt.tight_layout() +plt.savefig('docs/data/graph_search.png', dpi=150, bbox_inches='tight') +plt.close() + + +# график удаление +fig, ax = plt.subplots(figsize=(12, 7)) + +bars1 = ax.bar(x - width/2, random_delete, width, label='Случайный порядок', color='#3498db') +bars2 = ax.bar(x + width/2, sorted_delete, width, label='Отсортированный порядок', color='#e74c3c') + +for bar in bars1: + height = bar.get_height() + ax.annotate(f'{height:.6f}', xy=(bar.get_x() + bar.get_width()/2, height), + xytext=(0, 3), textcoords="offset points", ha='center', va='bottom', fontsize=9) + +for bar in bars2: + height = bar.get_height() + if height < 0.01: + ax.annotate(f'{height:.6f}', xy=(bar.get_x() + bar.get_width()/2, height), + xytext=(0, 3), textcoords="offset points", ha='center', va='bottom', fontsize=9) + else: + ax.annotate(f'{height:.4f}', xy=(bar.get_x() + bar.get_width()/2, height), + xytext=(0, 3), textcoords="offset points", ha='center', va='bottom', fontsize=9) + +ax.set_ylabel('Время (сек)', fontsize=12) +ax.set_title('Время удаления 50 записей', fontsize=14, fontweight='bold') +ax.set_xticks(x) +ax.set_xticklabels(structures, fontsize=11) +ax.legend(fontsize=11) +ax.set_yscale('log') +ax.grid(True, alpha=0.3, axis='y') + +plt.tight_layout() +plt.savefig('docs/data/graph_delete.png', dpi=150, bbox_inches='tight') +plt.close() + + diff --git a/zverevem/lab1/docs/data/make_tables.py b/zverevem/lab1/docs/data/make_tables.py new file mode 100644 index 0000000..211d6a5 --- /dev/null +++ b/zverevem/lab1/docs/data/make_tables.py @@ -0,0 +1,34 @@ + +import matplotlib.pyplot as plt +import os + +os.makedirs('docs/data', exist_ok=True) + +data = [ + ['LinkedList', 'случайный', 0.0037545, 0.00000962, 0.0000079], + ['HashTable', 'случайный', 0.015088, 0.0001646, 0.00009824], + ['BST', 'случайный', 0.026280, 0.0002592, 0.00016984], + ['LinkedList', 'отсортированный', 0.0017544, 0.00000858, 0.00000294], + ['HashTable', 'отсортированный', 0.011369, 0.00014016, 0.00005878], + ['BST', 'отсортированный', 4.930788, 0.047126, 0.023013], +] + +fig, ax = plt.subplots(figsize=(12, 5)) +ax.axis('tight') +ax.axis('off') + +columns = ['Структура', 'Режим', 'Вставка (10000)', 'Поиск (110)', 'Удаление (50)'] +table = ax.table(cellText=data, colLabels=columns, loc='center', cellLoc='center') + +table.auto_set_font_size(False) +table.set_fontsize(10) +table.scale(1.2, 1.5) + +for i, row in enumerate(data): + if row[0] == 'BST' and row[2] > 1: + table[(i+1, 2)].set_facecolor('#ffcccc') + table[(i+1, 2)].set_text_props(weight='bold') + +plt.title('Результаты экспериментов (среднее время в секундах)', fontsize=14, fontweight='bold', pad=20) +plt.savefig('docs/data/table_results.png', dpi=200, bbox_inches='tight', facecolor='white') +plt.close() diff --git a/zverevem/lab1/docs/data/phonebook.py b/zverevem/lab1/docs/data/phonebook.py new file mode 100644 index 0000000..2676b26 --- /dev/null +++ b/zverevem/lab1/docs/data/phonebook.py @@ -0,0 +1,195 @@ +def ll_insert(head, name, phone): + + new_node = {'name': name, 'phone': phone, 'next': None} + + 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'] + 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): + records = [] + current = head + + while current is not None: + records.append((current['name'], current['phone'])) + current = current['next'] + records.sort(key=lambda x: x[0]) + return records + +def hash_function(name, table_size): + total = 0 + for ch in name: + total = (total*31 + ord(ch)) % table_size + return total + +def ht_create(size=1000): + return [None]*size + +def ht_insert(buckets, name, phone): + idx = hash_function(name, len(buckets)) + buckets[idx] = ll_insert(buckets[idx], name, phone) + return buckets + +def ht_find(buckets, name): + idx = hash_function(name, len(buckets)) + return ll_find(buckets[idx], name) + +def ht_delete(buckets, name): + idx = hash_function(name, len(buckets)) + buckets[idx] = ll_delete(buckets[idx], name) + return buckets + +def ht_list_all(buckets): + records = [] + for bucket in buckets: + current = bucket + while current is not None: + records.append((current['name'], current['phone'])) + current = current['next'] + records.sort(key=lambda x: x[0]) + return records + +def bst_insert(root, name, phone): + + new_node = {'name': name, 'phone': phone, 'left': None, 'right': None} + + if root is None: + return new_node + + current = root + while True: + if name < current['name']: + if current['left'] is None: + current['left'] = new_node + break + current = current['left'] + elif name > current['name']: + if current['right'] is None: + current['right'] = new_node + break + current = current['right'] + else: + current['phone'] = phone + break + + return root + + +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_find_min(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 + + parent = None + current = root + + while current is not None and current['name'] != name: + parent = current + if name < current['name']: + current = current['left'] + else: + current = current['right'] + + if current is None: + return root + + if current['left'] is None and current['right'] is None: + if parent is None: + return None + if parent['left'] == current: + parent['left'] = None + else: + parent['right'] = None + return root + + if current['left'] is None: + child = current['right'] + elif current['right'] is None: + child = current['left'] + else: + successor_parent = current + successor = current['right'] + while successor['left'] is not None: + 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 root + + if parent is None: + return child + if parent['left'] == current: + parent['left'] = child + else: + parent['right'] = child + + return root + + +def bst_list_all(root): + records = [] + + def inorder(node): + if node is None: + return + inorder(node['left']) + records.append((node['name'], node['phone'])) + inorder(node['right']) + + inorder(root) + return records \ No newline at end of file diff --git a/zverevem/lab1/docs/data/results.csv b/zverevem/lab1/docs/data/results.csv new file mode 100644 index 0000000..8c2ebcb --- /dev/null +++ b/zverevem/lab1/docs/data/results.csv @@ -0,0 +1,109 @@ +Структура, Режим, Операция, Замер, Время (сек) +LinkedList,случайный,вставка,1,0.0013560999650508165 +LinkedList,случайный,вставка,2,0.0015854999655857682 +LinkedList,случайный,вставка,3,0.0012766000581905246 +LinkedList,случайный,вставка,4,0.0013539999490603805 +LinkedList,случайный,вставка,5,0.0013421999756246805 +LinkedList,случайный,вставка,среднее,0.001382879982702434 +LinkedList,случайный,поиск,1,5.899928510189056e-06 +LinkedList,случайный,поиск,2,4.899920895695686e-06 +LinkedList,случайный,поиск,3,4.800036549568176e-06 +LinkedList,случайный,поиск,4,5.09992241859436e-06 +LinkedList,случайный,поиск,5,5.00003807246685e-06 +LinkedList,случайный,поиск,среднее,5.139969289302826e-06 +LinkedList,случайный,удаление,1,2.900022082030773e-06 +LinkedList,случайный,удаление,2,2.199900336563587e-06 +LinkedList,случайный,удаление,3,2.200016751885414e-06 +LinkedList,случайный,удаление,4,2.100015990436077e-06 +LinkedList,случайный,удаление,5,2.299901098012924e-06 +LinkedList,случайный,удаление,среднее,2.3399712517857552e-06 +HashTable,случайный,вставка,1,0.010439200093969703 +HashTable,случайный,вставка,2,0.010059899999760091 +HashTable,случайный,вставка,3,0.010718900011852384 +HashTable,случайный,вставка,4,0.010526199941523373 +HashTable,случайный,вставка,5,0.0102259999839589 +HashTable,случайный,вставка,среднее,0.01039404000621289 +HashTable,случайный,поиск,1,9.549991227686405e-05 +HashTable,случайный,поиск,2,9.079999290406704e-05 +HashTable,случайный,поиск,3,9.989994578063488e-05 +HashTable,случайный,поиск,4,9.240000508725643e-05 +HashTable,случайный,поиск,5,9.039998985826969e-05 +HashTable,случайный,поиск,среднее,9.379996918141842e-05 +HashTable,случайный,удаление,1,4.610000178217888e-05 +HashTable,случайный,удаление,2,4.2499974370002747e-05 +HashTable,случайный,удаление,3,4.290009383112192e-05 +HashTable,случайный,удаление,4,4.2400090023875237e-05 +HashTable,случайный,удаление,5,4.269997589290142e-05 +HashTable,случайный,удаление,среднее,4.332002718001604e-05 +BST,случайный,вставка,1,0.014894199906848371 +BST,случайный,вставка,2,0.015171999926678836 +BST,случайный,вставка,3,0.015123400022275746 +BST,случайный,вставка,4,0.015276000020094216 +BST,случайный,вставка,5,0.01524280000012368 +BST,случайный,вставка,среднее,0.015141679975204169 +BST,случайный,поиск,1,0.00014160003047436476 +BST,случайный,поиск,2,0.0001335999695584178 +BST,случайный,поиск,3,0.00013259996194392443 +BST,случайный,поиск,4,0.0001449999399483204 +BST,случайный,поиск,5,0.00013129995204508305 +BST,случайный,поиск,среднее,0.0001368199707940221 +BST,случайный,удаление,1,8.909997995942831e-05 +BST,случайный,удаление,2,6.929994560778141e-05 +BST,случайный,удаление,3,6.719992961734533e-05 +BST,случайный,удаление,4,6.700004450976849e-05 +BST,случайный,удаление,5,6.679992657154799e-05 +BST,случайный,удаление,среднее,7.18799652531743e-05 +LinkedList,отсортированный,вставка,1,0.0012123000342398882 +LinkedList,отсортированный,вставка,2,0.0011566999601200223 +LinkedList,отсортированный,вставка,3,0.001145699992775917 +LinkedList,отсортированный,вставка,4,0.0011751001002267003 +LinkedList,отсортированный,вставка,5,0.0011464999988675117 +LinkedList,отсортированный,вставка,среднее,0.0011672600172460078 +LinkedList,отсортированный,поиск,1,5.300040356814861e-06 +LinkedList,отсортированный,поиск,2,4.900037311017513e-06 +LinkedList,отсортированный,поиск,3,4.800036549568176e-06 +LinkedList,отсортированный,поиск,4,5.200039595365524e-06 +LinkedList,отсортированный,поиск,5,4.799920134246349e-06 +LinkedList,отсортированный,поиск,среднее,5.000014789402485e-06 +LinkedList,отсортированный,удаление,1,2.400018274784088e-06 +LinkedList,отсортированный,удаление,2,2.300017513334751e-06 +LinkedList,отсортированный,удаление,3,2.300017513334751e-06 +LinkedList,отсортированный,удаление,4,2.300017513334751e-06 +LinkedList,отсортированный,удаление,5,2.200016751885414e-06 +LinkedList,отсортированный,удаление,среднее,2.300017513334751e-06 +HashTable,отсортированный,вставка,1,0.00947619997896254 +HashTable,отсортированный,вставка,2,0.00943189999088645 +HashTable,отсортированный,вставка,3,0.009878099896013737 +HashTable,отсортированный,вставка,4,0.009515199926681817 +HashTable,отсортированный,вставка,5,0.009485000045970082 +HashTable,отсортированный,вставка,среднее,0.009557279967702925 +HashTable,отсортированный,поиск,1,9.810004848986864e-05 +HashTable,отсортированный,поиск,2,9.250000584870577e-05 +HashTable,отсортированный,поиск,3,9.019998833537102e-05 +HashTable,отсортированный,поиск,4,9.129999671131372e-05 +HashTable,отсортированный,поиск,5,9.280000813305378e-05 +HashTable,отсортированный,поиск,среднее,9.298000950366258e-05 +HashTable,отсортированный,удаление,1,4.429998807609081e-05 +HashTable,отсортированный,удаление,2,4.549999721348286e-05 +HashTable,отсортированный,удаление,3,4.339998122304678e-05 +HashTable,отсортированный,удаление,4,4.270009230822325e-05 +HashTable,отсортированный,удаление,5,4.349998198449612e-05 +HashTable,отсортированный,удаление,среднее,4.3880008161067965e-05 +BST,отсортированный,вставка,1,4.526228099945001 +BST,отсортированный,вставка,2,4.322803199989721 +BST,отсортированный,вставка,3,4.176126900012605 +BST,отсортированный,вставка,4,3.965669700060971 +BST,отсортированный,вставка,5,3.9622846000129357 +BST,отсортированный,вставка,среднее,4.190622500004247 +BST,отсортированный,поиск,1,0.030124699929729104 +BST,отсортированный,поиск,2,0.030757599975913763 +BST,отсортированный,поиск,3,0.03016249998472631 +BST,отсортированный,поиск,4,0.03018200001679361 +BST,отсортированный,поиск,5,0.030304200015962124 +BST,отсортированный,поиск,среднее,0.03030619998462498 +BST,отсортированный,удаление,1,0.016157799982465804 +BST,отсортированный,удаление,2,0.01620279997587204 +BST,отсортированный,удаление,3,0.017003200016915798 +BST,отсортированный,удаление,4,0.01792290003504604 +BST,отсортированный,удаление,5,0.017416900023818016 +BST,отсортированный,удаление,среднее,0.01694072000682354 diff --git a/zverevem/lab1/docs/data/table_results.png b/zverevem/lab1/docs/data/table_results.png new file mode 100644 index 0000000..655b0a2 Binary files /dev/null and b/zverevem/lab1/docs/data/table_results.png differ diff --git a/zverevem/lab1/docs/отчёт.ipynb b/zverevem/lab1/docs/отчёт.ipynb new file mode 100644 index 0000000..511809e --- /dev/null +++ b/zverevem/lab1/docs/отчёт.ipynb @@ -0,0 +1,163 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "f058dc2c", + "metadata": {}, + "source": [ + "# Отчёт: Задание 1 — Структуры данных\n", + "\n", + "## Цель работы\n", + "\n", + "Разработать три структуры данных «с нуля» в процедурном стиле (без ООП), применить их для хранения записей телефонной книги и провести экспериментальное сравнение производительности ключевых операций.\n", + "\n", + "**Структуры данных:**\n", + "- Связный список (LinkedList)\n", + "- Хеш-таблица (HashTable)\n", + "- Двоичное дерево поиска (BST)\n", + "\n", + "---\n", + "\n", + "## Реализация\n", + "\n", + "### Основные технические решения\n", + "\n", + "#### 1. Связный список\n", + "\n", + "Узел реализован как Python-словарь: `{'name': 'Имя', 'phone': '123', 'next': None}`.\n", + "\n", + "Новые элементы добавляются **в конец** списка за O(1) (без проверки на дубликаты имени). Поиск и удаление работают за линейное время O(n) из-за отсутствия прямого доступа по индексу.\n", + "\n", + "#### 2. Хеш-таблица\n", + "\n", + "Фиксированный массив на **1000 корзин**. Каждая корзина — указатель на связный список (метод цепочек). Хеш-функция: полиномиальная с основанием 31, свёрнутая по модулю размера таблицы. Среднее время операций O(1), при коллизиях — O(k), где k — длина цепочки.\n", + "\n", + "#### 3. Двоичное дерево поиска (BST)\n", + "\n", + "Узел: `{'name': 'Имя', 'phone': '123', 'left': None, 'right': None}`. Сравнение ключей — лексикографическое по полю `name`. Вставка и поиск реализованы итеративно. Удаление — с заменой на минимальный узел правого поддерева. Обход в глубину даёт отсортированный список.\n", + "\n", + "---\n", + "\n", + "## Экспериментальная часть\n", + "\n", + "### Условия проведения замеров\n", + "\n", + "| Параметр | Значение |\n", + "|---|---|\n", + "| Количество записей (N) | 10 000 |\n", + "| Количество замеров на операцию | 5 |\n", + "| Поисковых запросов | 110 (100 существующих + 10 отсутствующих) |\n", + "| Удалений | 50 |\n", + "| Размер хеш-таблицы | 1000 корзин |\n", + "\n", + "**Два набора данных:**\n", + "- `records_shuffled` — случайный порядок записей\n", + "- `records_sorted` — упорядоченный по имени (алфавитный порядок)\n", + "\n", + "---\n", + "\n", + "## Результаты\n", + "\n", + "### Среднее время выполнения (секунды)\n", + "\n", + "| Структура | Режим | Вставка (10000) | Поиск (110) | Удаление (50) |\n", + "|-----------|-------|----------------|-------------|---------------|\n", + "| LinkedList | случайный | 0.001383 | 0.00000514 | 0.00000234 |\n", + "| LinkedList | отсортированный | 0.001167 | 0.00000500 | 0.00000230 |\n", + "| HashTable | случайный | 0.010394 | 0.00009380 | 0.00004332 |\n", + "| HashTable | отсортированный | 0.009557 | 0.00009298 | 0.00004388 |\n", + "| BST | случайный | 0.015142 | 0.00013682 | 0.00007188 |\n", + "| **BST** | **отсортированный** | **4.19062** | **0.030306** | **0.016941** |\n", + "\n", + "### Визуализация\n", + "\n", + "![Сравнение вставки](docs/data/graph_insert.png)\n", + "\n", + "![Сравнение поиска](docs/data/graph_search.png)\n", + "\n", + "![Сравнение удаления](docs/data/graph_delete.png)\n", + "\n", + "![Таблица результатов](docs/data/table_results.png)\n", + "\n", + "---\n", + "\n", + "## Анализ результатов\n", + "\n", + "### 1. Связный список — сверхбыстрая вставка, но линейный поиск\n", + "\n", + "- **Вставка** выполняется за **~0.0012 с** на 10000 элементов, так как добавление происходит в конец списка без проверки дубликатов (O(1) на операцию).\n", + "- **Поиск** и **удаление** в замерах показали микросекунды, но это следствие малого количества операций (110 поисков, 50 удалений) и того, что искомые записи находятся в начале списка. В худшем случае (поиск отсутствующего элемента) сложность остаётся O(n).\n", + "\n", + "**Вывод:** связный список эффективен только при очень малых объёмах данных или когда вставка — единственная частая операция.\n", + "\n", + "### 2. Хеш-таблица — стабильная производительность\n", + "\n", + "Хеш-таблица демонстрирует **устойчивость к порядку входных данных**:\n", + "- Вставка: ~0.010 с (быстрее BST на случайных данных, но медленнее LinkedList)\n", + "- Поиск: ~0.000094 с (в 18 раз быстрее BST на случайных)\n", + "- Удаление: ~0.000043 с (в 1.6 раза быстрее BST)\n", + "\n", + "Размер таблицы (1000 корзин) обеспечивает равномерное распределение ключей, поэтому производительность остаётся стабильной.\n", + "\n", + "### 3. BST катастрофически деградирует на упорядоченных данных\n", + "\n", + "Самый показательный результат эксперимента:\n", + "\n", + "| Операция | Случайный порядок | Отсортированный порядок | Ухудшение |\n", + "|----------|------------------|------------------------|-----------|\n", + "| Вставка | 0.01514 с | **4.19062 с** | **×277** |\n", + "| Поиск | 0.0001368 с | **0.03031 с** | **×221** |\n", + "| Удаление | 0.0000719 с | **0.01694 с** | **×236** |\n", + "\n", + "**Причина:** при вставке отсортированных данных дерево вырождается в линейный список — каждый новый элемент больше предыдущего и помещается только в правую ветку. Высота дерева становится O(n) вместо O(log n), что превращает все операции в линейные.\n", + "\n", + "### 4. Сравнение с теоретическими ожиданиями\n", + "\n", + "| Структура | Теоретическая вставка | Фактическая (случ./сорт.) | \n", + "|-----------|----------------------|---------------------------|\n", + "| LinkedList | O(1) (в конец) | 0.0014 с / 0.0012 с |\n", + "| HashTable | O(1) в среднем | 0.0104 с / 0.0096 с |\n", + "| BST | O(log n) в среднем | 0.0151 с (случ.) |\n", + "| BST | O(n) в худшем | 4.19 с (сорт.) |\n", + "\n", + "---\n", + "\n", + "## Выводы и практические рекомендации\n", + "\n", + "### Выбор структуры в зависимости от задачи\n", + "\n", + "| Сценарий | Рекомендация |\n", + "|----------|--------------|\n", + "| **Частый поиск по ключу** | HashTable (быстрее всего) |\n", + "| **Данные поступают упорядоченно** | HashTable (BST непригоден) |\n", + "| **Только вставка и редкий поиск** | LinkedList (самая быстрая вставка) |\n", + "| **Требуется отсортированный вывод** | BST (обход даёт порядок за O(n)) |\n", + "| **Сбалансированные операции** | HashTable |\n", + "| **Диапазонные запросы** (например, А–М) | BST (при условии балансировки) |\n", + "\n", + "### Теоретическая сложность операций \n", + "| Структура | Insert | Find | Delete | Обход (отсорт.) |\n", + "|-----------|--------|------|--------|-----------------|\n", + "| LinkedList (в конец) | O(1) | O(n) | O(n) | O(n log n) |\n", + "| HashTable | O(1) | O(1) | O(1) | O(n log n) |\n", + "| BST (сбалансированный) | O(log n) | O(log n) | O(log n) | O(n) |\n", + "| BST (вырожденный) | O(n) | O(n) | O(n) | O(n) |\n", + "\n", + "### Ключевой вывод\n", + "\n", + "Для телефонного справочника с частыми поисками и обновлениями оптимальный выбор — **хеш-таблица**. Она обеспечивает предсказуемую скорость вне зависимости от порядка данных.\n", + "\n", + "Обычный **связный список** полезен только как вспомогательная структура (например, для цепочек в хеш-таблице) или при минимальном объёме данных.\n", + "\n", + "**Двоичное дерево поиска** без самобалансировки опасно использовать с реальными данными, которые часто бывают частично или полностью упорядоченными. В таких случаях необходимо применять AVL или красно-чёрные деревья.\n" + ] + } + ], + "metadata": { + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +}