[1, 2]lab1, lab2 #296
BIN
filippovavm/docs/data/plot_empty.txt.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
filippovavm/docs/data/plot_large.txt.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
filippovavm/docs/data/plot_medium.txt.png
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
filippovavm/docs/data/plot_no_exit.txt.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
filippovavm/docs/data/plot_tiny.txt.png
Normal file
|
After Width: | Height: | Size: 21 KiB |
490
filippovavm/docs/data/фулкод1.ipynb
Normal file
|
|
@ -0,0 +1,490 @@
|
|||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "65b0def8-04cf-4cfd-b4d4-bc862e120497",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Генерация данных...\n",
|
||||
"\n",
|
||||
"Измерение вставки для Связного списка...\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"import time\n",
|
||||
"import random\n",
|
||||
"import csv\n",
|
||||
"import sys\n",
|
||||
"import matplotlib.pyplot as plt\n",
|
||||
"import numpy as np\n",
|
||||
"\n",
|
||||
"# Связный список\n",
|
||||
"def ll_insert(head, name, phone):\n",
|
||||
" current = head\n",
|
||||
" prev = None\n",
|
||||
" while current is not None:\n",
|
||||
" if current['name'] == name:\n",
|
||||
" current['phone'] = phone\n",
|
||||
" return head\n",
|
||||
" prev = current\n",
|
||||
" current = current['next']\n",
|
||||
" new_node = {'name': name, 'phone': phone, 'next': None}\n",
|
||||
" if prev is None:\n",
|
||||
" return new_node\n",
|
||||
" else:\n",
|
||||
" prev['next'] = new_node\n",
|
||||
" return head\n",
|
||||
"\n",
|
||||
"def ll_find(head, name):\n",
|
||||
" current = head\n",
|
||||
" while current is not None:\n",
|
||||
" if current['name'] == name:\n",
|
||||
" return current['phone']\n",
|
||||
" current = current['next']\n",
|
||||
" return None\n",
|
||||
"\n",
|
||||
"def ll_delete(head, name):\n",
|
||||
" if head is None:\n",
|
||||
" return None\n",
|
||||
" if head['name'] == name:\n",
|
||||
" return head['next']\n",
|
||||
" current = head\n",
|
||||
" while current['next'] is not None:\n",
|
||||
" if current['next']['name'] == name:\n",
|
||||
" current['next'] = current['next']['next']\n",
|
||||
" return head\n",
|
||||
" current = current['next']\n",
|
||||
" return head\n",
|
||||
"\n",
|
||||
"# Хеш-таблица \n",
|
||||
"def hash_function(name, size):\n",
|
||||
" total = 0\n",
|
||||
" for ch in name:\n",
|
||||
" total = (total * 31 + ord(ch)) % size\n",
|
||||
" return total\n",
|
||||
"\n",
|
||||
"def ht_create(size=2000):\n",
|
||||
" return [None] * size\n",
|
||||
"\n",
|
||||
"def ht_insert(buckets, name, phone):\n",
|
||||
" idx = hash_function(name, len(buckets))\n",
|
||||
" buckets[idx] = ll_insert(buckets[idx], name, phone)\n",
|
||||
"\n",
|
||||
"def ht_find(buckets, name):\n",
|
||||
" idx = hash_function(name, len(buckets))\n",
|
||||
" return ll_find(buckets[idx], name)\n",
|
||||
"\n",
|
||||
"def ht_delete(buckets, name):\n",
|
||||
" idx = hash_function(name, len(buckets))\n",
|
||||
" buckets[idx] = ll_delete(buckets[idx], name)\n",
|
||||
"\n",
|
||||
"# Двоичное дерево поиска \n",
|
||||
"def bst_insert(root, name, phone):\n",
|
||||
" new_node = {'name': name, 'phone': phone, 'left': None, 'right': None}\n",
|
||||
" if root is None:\n",
|
||||
" return new_node\n",
|
||||
" current = root\n",
|
||||
" while True:\n",
|
||||
" if name < current['name']:\n",
|
||||
" if current['left'] is None:\n",
|
||||
" current['left'] = new_node\n",
|
||||
" break\n",
|
||||
" current = current['left']\n",
|
||||
" elif name > current['name']:\n",
|
||||
" if current['right'] is None:\n",
|
||||
" current['right'] = new_node\n",
|
||||
" break\n",
|
||||
" current = current['right']\n",
|
||||
" else:\n",
|
||||
" current['phone'] = phone\n",
|
||||
" break\n",
|
||||
" return root\n",
|
||||
"\n",
|
||||
"def bst_find(root, name):\n",
|
||||
" current = root\n",
|
||||
" while current is not None:\n",
|
||||
" if name < current['name']:\n",
|
||||
" current = current['left']\n",
|
||||
" elif name > current['name']:\n",
|
||||
" current = current['right']\n",
|
||||
" else:\n",
|
||||
" return current['phone']\n",
|
||||
" return None\n",
|
||||
"\n",
|
||||
"def bst_find_min(node):\n",
|
||||
" while node['left'] is not None:\n",
|
||||
" node = node['left']\n",
|
||||
" return node\n",
|
||||
"\n",
|
||||
"def bst_delete(root, name):\n",
|
||||
" parent = None\n",
|
||||
" current = root\n",
|
||||
" while current is not None and current['name'] != name:\n",
|
||||
" parent = current\n",
|
||||
" if name < current['name']:\n",
|
||||
" current = current['left']\n",
|
||||
" else:\n",
|
||||
" current = current['right']\n",
|
||||
" if current is None:\n",
|
||||
" return root\n",
|
||||
" \n",
|
||||
" if current['left'] is None and current['right'] is None:\n",
|
||||
" if parent is None:\n",
|
||||
" return None\n",
|
||||
" if parent['left'] is current:\n",
|
||||
" parent['left'] = None\n",
|
||||
" else:\n",
|
||||
" parent['right'] = None\n",
|
||||
" return root\n",
|
||||
" \n",
|
||||
" if current['left'] is None:\n",
|
||||
" if parent is None:\n",
|
||||
" return current['right']\n",
|
||||
" if parent['left'] is current:\n",
|
||||
" parent['left'] = current['right']\n",
|
||||
" else:\n",
|
||||
" parent['right'] = current['right']\n",
|
||||
" return root\n",
|
||||
" if current['right'] is None:\n",
|
||||
" if parent is None:\n",
|
||||
" return current['left']\n",
|
||||
" if parent['left'] is current:\n",
|
||||
" parent['left'] = current['left']\n",
|
||||
" else:\n",
|
||||
" parent['right'] = current['left']\n",
|
||||
" return root\n",
|
||||
" \n",
|
||||
" succ_parent = current\n",
|
||||
" succ = current['right']\n",
|
||||
" while succ['left'] is not None:\n",
|
||||
" succ_parent = succ\n",
|
||||
" succ = succ['left']\n",
|
||||
" current['name'] = succ['name']\n",
|
||||
" current['phone'] = succ['phone']\n",
|
||||
" if succ_parent['left'] is succ:\n",
|
||||
" succ_parent['left'] = succ['right']\n",
|
||||
" else:\n",
|
||||
" succ_parent['right'] = succ['right']\n",
|
||||
" return root\n",
|
||||
"\n",
|
||||
"# Генерация данных \n",
|
||||
"def generate_records(N):\n",
|
||||
" records = []\n",
|
||||
" for i in range(N):\n",
|
||||
" name = f\"User_{i:05d}\"\n",
|
||||
" phone = f\"+7-999-{random.randint(1000000, 9999999)}\"\n",
|
||||
" records.append((name, phone))\n",
|
||||
" return records\n",
|
||||
"\n",
|
||||
"# Замеры\n",
|
||||
"REPEATS = 5\n",
|
||||
"N = 10000\n",
|
||||
"\n",
|
||||
"def measure_insert(struct, records, repeats=REPEATS):\n",
|
||||
" times = []\n",
|
||||
" for _ in range(repeats):\n",
|
||||
" if struct == 'll':\n",
|
||||
" head = None\n",
|
||||
" start = time.perf_counter()\n",
|
||||
" for name, phone in records:\n",
|
||||
" head = ll_insert(head, name, phone)\n",
|
||||
" end = time.perf_counter()\n",
|
||||
" elif struct == 'ht':\n",
|
||||
" buckets = ht_create(2000)\n",
|
||||
" start = time.perf_counter()\n",
|
||||
" for name, phone in records:\n",
|
||||
" ht_insert(buckets, name, phone)\n",
|
||||
" end = time.perf_counter()\n",
|
||||
" elif struct == 'bst':\n",
|
||||
" root = None\n",
|
||||
" start = time.perf_counter()\n",
|
||||
" for name, phone in records:\n",
|
||||
" root = bst_insert(root, name, phone)\n",
|
||||
" end = time.perf_counter()\n",
|
||||
" times.append(end - start)\n",
|
||||
" return times\n",
|
||||
"\n",
|
||||
"def build_structure(struct, records):\n",
|
||||
" if struct == 'll':\n",
|
||||
" head = None\n",
|
||||
" for name, phone in records:\n",
|
||||
" head = ll_insert(head, name, phone)\n",
|
||||
" return head\n",
|
||||
" elif struct == 'ht':\n",
|
||||
" buckets = ht_create(2000)\n",
|
||||
" for name, phone in records:\n",
|
||||
" ht_insert(buckets, name, phone)\n",
|
||||
" return buckets\n",
|
||||
" elif struct == 'bst':\n",
|
||||
" root = None\n",
|
||||
" for name, phone in records:\n",
|
||||
" root = bst_insert(root, name, phone)\n",
|
||||
" return root\n",
|
||||
"\n",
|
||||
"def measure_find_on_structure(struct, structure, records, repeats=REPEATS):\n",
|
||||
" times = []\n",
|
||||
" N = len(records)\n",
|
||||
" for _ in range(repeats):\n",
|
||||
" indices = random.sample(range(N), 100)\n",
|
||||
" exist = [records[i][0] for i in indices]\n",
|
||||
" missing = [f\"None_{i}\" for i in range(10)]\n",
|
||||
" search = exist + missing\n",
|
||||
" start = time.perf_counter()\n",
|
||||
" if struct == 'll':\n",
|
||||
" for name in search:\n",
|
||||
" ll_find(structure, name)\n",
|
||||
" elif struct == 'ht':\n",
|
||||
" for name in search:\n",
|
||||
" ht_find(structure, name)\n",
|
||||
" elif struct == 'bst':\n",
|
||||
" for name in search:\n",
|
||||
" bst_find(structure, name)\n",
|
||||
" times.append(time.perf_counter() - start)\n",
|
||||
" return times\n",
|
||||
"\n",
|
||||
"def measure_delete_on_structure(struct, records, repeats=REPEATS):\n",
|
||||
" times = []\n",
|
||||
" N = len(records)\n",
|
||||
" for _ in range(repeats):\n",
|
||||
" indices = random.sample(range(N), 50)\n",
|
||||
" del_names = [records[i][0] for i in indices]\n",
|
||||
" if struct == 'll':\n",
|
||||
" head = None\n",
|
||||
" for name, phone in records:\n",
|
||||
" head = ll_insert(head, name, phone)\n",
|
||||
" start = time.perf_counter()\n",
|
||||
" for name in del_names:\n",
|
||||
" head = ll_delete(head, name)\n",
|
||||
" end = time.perf_counter()\n",
|
||||
" elif struct == 'ht':\n",
|
||||
" buckets = ht_create(2000)\n",
|
||||
" for name, phone in records:\n",
|
||||
" ht_insert(buckets, name, phone)\n",
|
||||
" start = time.perf_counter()\n",
|
||||
" for name in del_names:\n",
|
||||
" ht_delete(buckets, name)\n",
|
||||
" end = time.perf_counter()\n",
|
||||
" elif struct == 'bst':\n",
|
||||
" root = None\n",
|
||||
" for name, phone in records:\n",
|
||||
" root = bst_insert(root, name, phone)\n",
|
||||
" start = time.perf_counter()\n",
|
||||
" for name in del_names:\n",
|
||||
" root = bst_delete(root, name)\n",
|
||||
" end = time.perf_counter()\n",
|
||||
" times.append(end - start)\n",
|
||||
" return times\n",
|
||||
"\n",
|
||||
"# Основная функция \n",
|
||||
"def main():\n",
|
||||
" print(\"Генерация данных...\")\n",
|
||||
" records = generate_records(N)\n",
|
||||
" random.shuffle(records) # случайный порядок\n",
|
||||
" records_sorted = sorted(records, key=lambda x: x[0]) # отсортированный\n",
|
||||
"\n",
|
||||
" results = [] # для CSV\n",
|
||||
" struct_names = {'ll': 'Связного списка', 'ht': 'Хеш-таблицы', 'bst': 'Двоичного дерева поиска'}\n",
|
||||
" mode_names = {'shuffled': 'случайный', 'sorted': 'отсортированный'}\n",
|
||||
" op_names = {'insert': 'Вставка всех записей', 'find': 'Поиск записей', 'delete': 'Удаление записей'}\n",
|
||||
"\n",
|
||||
" # для графиков\n",
|
||||
" insert_sh = {} # {struct: [times]}\n",
|
||||
" insert_so = {}\n",
|
||||
" find_sh = {}\n",
|
||||
" find_so = {}\n",
|
||||
" delete_sh = {}\n",
|
||||
" delete_so = {}\n",
|
||||
"\n",
|
||||
" # Вставка \n",
|
||||
" for struct in ['ll', 'ht', 'bst']:\n",
|
||||
" print(f\"\\nИзмерение вставки для {struct_names[struct]}...\")\n",
|
||||
" times_sh = measure_insert(struct, records)\n",
|
||||
" times_so = measure_insert(struct, records_sorted)\n",
|
||||
" insert_sh[struct] = times_sh\n",
|
||||
" insert_so[struct] = times_so\n",
|
||||
" print(f\" случайный: {[round(t,6) for t in times_sh]}, среднее = {sum(times_sh)/len(times_sh):.6f}\")\n",
|
||||
" print(f\" отсортированный: {[round(t,6) for t in times_so]}, среднее = {sum(times_so)/len(times_so):.6f}\")\n",
|
||||
" results.append([struct_names[struct], mode_names['shuffled'], op_names['insert'], sum(times_sh)/len(times_sh)] + times_sh)\n",
|
||||
" results.append([struct_names[struct], mode_names['sorted'], op_names['insert'], sum(times_so)/len(times_so)] + times_so)\n",
|
||||
"\n",
|
||||
" # Поиск \n",
|
||||
" for struct in ['ll', 'ht', 'bst']:\n",
|
||||
" print(f\"\\nПоиск для {struct_names[struct]} на случайных данных...\")\n",
|
||||
" structure_sh = build_structure(struct, records)\n",
|
||||
" times_find_sh = measure_find_on_structure(struct, structure_sh, records)\n",
|
||||
" find_sh[struct] = times_find_sh\n",
|
||||
" print(f\" случайный: {[round(t,6) for t in times_find_sh]}, среднее = {sum(times_find_sh)/len(times_find_sh):.6f}\")\n",
|
||||
" results.append([struct_names[struct], mode_names['shuffled'], op_names['find'], sum(times_find_sh)/len(times_find_sh)] + times_find_sh)\n",
|
||||
"\n",
|
||||
" print(f\"Поиск для {struct_names[struct]} на отсортированных данных...\")\n",
|
||||
" structure_so = build_structure(struct, records_sorted)\n",
|
||||
" times_find_so = measure_find_on_structure(struct, structure_so, records_sorted)\n",
|
||||
" find_so[struct] = times_find_so\n",
|
||||
" print(f\" отсортированный: {[round(t,6) for t in times_find_so]}, среднее = {sum(times_find_so)/len(times_find_so):.6f}\")\n",
|
||||
" results.append([struct_names[struct], mode_names['sorted'], op_names['find'], sum(times_find_so)/len(times_find_so)] + times_find_so)\n",
|
||||
"\n",
|
||||
" # Удаление \n",
|
||||
" for struct in ['ll', 'ht', 'bst']:\n",
|
||||
" print(f\"\\nУдаление для {struct_names[struct]} на случайных данных...\")\n",
|
||||
" times_del_sh = measure_delete_on_structure(struct, records)\n",
|
||||
" delete_sh[struct] = times_del_sh\n",
|
||||
" print(f\" случайный: {[round(t,6) for t in times_del_sh]}, среднее = {sum(times_del_sh)/len(times_del_sh):.6f}\")\n",
|
||||
" results.append([struct_names[struct], mode_names['shuffled'], op_names['delete'], sum(times_del_sh)/len(times_del_sh)] + times_del_sh)\n",
|
||||
"\n",
|
||||
" print(f\"Удаление для {struct_names[struct]} на отсортированных данных...\")\n",
|
||||
" times_del_so = measure_delete_on_structure(struct, records_sorted)\n",
|
||||
" delete_so[struct] = times_del_so\n",
|
||||
" print(f\" отсортированный: {[round(t,6) for t in times_del_so]}, среднее = {sum(times_del_so)/len(times_del_so):.6f}\")\n",
|
||||
" results.append([struct_names[struct], mode_names['sorted'], op_names['delete'], sum(times_del_so)/len(times_del_so)] + times_del_so)\n",
|
||||
"\n",
|
||||
" # Сохраняем CSV\n",
|
||||
" with open(\"phonebook_results.csv\", \"w\", newline=\"\", encoding=\"utf-8\") as f:\n",
|
||||
" writer = csv.writer(f)\n",
|
||||
" writer.writerow(['Структура', 'Режим', 'Операция', 'Среднее', 'Замер1', 'Замер2', 'Замер3', 'Замер4', 'Замер5'])\n",
|
||||
" writer.writerows(results)\n",
|
||||
"\n",
|
||||
" # Построение графиков \n",
|
||||
" try:\n",
|
||||
" # График вставки\n",
|
||||
" fig1, ax1 = plt.subplots(figsize=(10,6))\n",
|
||||
" x = np.arange(3)\n",
|
||||
" width = 0.35\n",
|
||||
" means_sh = [sum(insert_sh[s])/len(insert_sh[s]) for s in ['ll','ht','bst']]\n",
|
||||
" means_so = [sum(insert_so[s])/len(insert_so[s]) for s in ['ll','ht','bst']]\n",
|
||||
" rects1 = ax1.bar(x - width/2, means_sh, width, label='Случайный порядок', color='skyblue')\n",
|
||||
" rects2 = ax1.bar(x + width/2, means_so, width, label='Отсортированный порядок', color='salmon')\n",
|
||||
" ax1.set_ylabel('Время (сек)')\n",
|
||||
" ax1.set_title('Вставка всех записей')\n",
|
||||
" ax1.set_xticks(x)\n",
|
||||
" ax1.set_xticklabels(['Связный список', 'Хеш-таблица', 'Двоичное дерево'])\n",
|
||||
" ax1.legend()\n",
|
||||
" ax1.set_yscale('log')\n",
|
||||
" for rect in rects1 + rects2:\n",
|
||||
" h = rect.get_height()\n",
|
||||
" ax1.annotate(f'{h:.3f}', xy=(rect.get_x()+rect.get_width()/2, h),\n",
|
||||
" xytext=(0,3), textcoords=\"offset points\", ha='center', va='bottom', fontsize=8)\n",
|
||||
" plt.tight_layout()\n",
|
||||
" plt.savefig('insert_comparison.png')\n",
|
||||
" plt.show()\n",
|
||||
"\n",
|
||||
" # График поиска\n",
|
||||
" fig2, ax2 = plt.subplots(figsize=(10,6))\n",
|
||||
" means_find_sh = [sum(find_sh[s])/len(find_sh[s]) for s in ['ll','ht','bst']]\n",
|
||||
" means_find_so = [sum(find_so[s])/len(find_so[s]) for s in ['ll','ht','bst']]\n",
|
||||
" rects1 = ax2.bar(x - width/2, means_find_sh, width, label='Случайный порядок', color='skyblue')\n",
|
||||
" rects2 = ax2.bar(x + width/2, means_find_so, width, label='Отсортированный порядок', color='salmon')\n",
|
||||
" ax2.set_ylabel('Время (сек)')\n",
|
||||
" ax2.set_title('Поиск (100 существующих + 10 отсутствующих)')\n",
|
||||
" ax2.set_xticks(x)\n",
|
||||
" ax2.set_xticklabels(['Связный список', 'Хеш-таблица', 'Двоичное дерево'])\n",
|
||||
" ax2.legend()\n",
|
||||
" for rect in rects1 + rects2:\n",
|
||||
" h = rect.get_height()\n",
|
||||
" ax2.annotate(f'{h:.5f}', xy=(rect.get_x()+rect.get_width()/2, h),\n",
|
||||
" xytext=(0,3), textcoords=\"offset points\", ha='center', va='bottom', fontsize=8)\n",
|
||||
" plt.tight_layout()\n",
|
||||
" plt.savefig('find_comparison.png')\n",
|
||||
" plt.show()\n",
|
||||
"\n",
|
||||
" # График удаления \n",
|
||||
" fig3, ax3 = plt.subplots(figsize=(10,6))\n",
|
||||
" means_del_sh = [sum(delete_sh[s])/len(delete_sh[s]) for s in ['ll','ht','bst']]\n",
|
||||
" means_del_so = [sum(delete_so[s])/len(delete_so[s]) for s in ['ll','ht','bst']]\n",
|
||||
" rects1 = ax3.bar(x - width/2, means_del_sh, width, label='Случайный порядок', color='skyblue')\n",
|
||||
" rects2 = ax3.bar(x + width/2, means_del_so, width, label='Отсортированный порядок', color='salmon')\n",
|
||||
" ax3.set_ylabel('Время (сек)')\n",
|
||||
" ax3.set_title('Удаление 50 случайных записей')\n",
|
||||
" ax3.set_xticks(x)\n",
|
||||
" ax3.set_xticklabels(['Связный список', 'Хеш-таблица', 'Двоичное дерево'])\n",
|
||||
" ax3.legend()\n",
|
||||
" for rect in rects1 + rects2:\n",
|
||||
" h = rect.get_height()\n",
|
||||
" ax3.annotate(f'{h:.5f}', xy=(rect.get_x()+rect.get_width()/2, h),\n",
|
||||
" xytext=(0,3), textcoords=\"offset points\", ha='center', va='bottom', fontsize=8)\n",
|
||||
" plt.tight_layout()\n",
|
||||
" plt.savefig('delete_comparison.png')\n",
|
||||
" plt.show()\n",
|
||||
"\n",
|
||||
" print(\"Графики сохранены: insert_comparison.png, find_comparison.png, delete_comparison.png\")\n",
|
||||
" except Exception as e:\n",
|
||||
" print(f\"Не удалось построить графики: {e}\")\n",
|
||||
" # Графики замеров\n",
|
||||
" try:\n",
|
||||
" def plot_attempts(data_sh, data_so, op_name):\n",
|
||||
" fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))\n",
|
||||
" # случайный порядок\n",
|
||||
" for struct, label, color, marker in [('ll','LinkedList','red','o'), ('ht','HashTable','green','s'), ('bst','BST','blue','^')]:\n",
|
||||
" times = data_sh[struct]\n",
|
||||
" x = range(1, len(times)+1)\n",
|
||||
" ax1.plot(x, times, marker=marker, color=color, label=label, linestyle='--', linewidth=1)\n",
|
||||
" ax1.scatter(x, times, color=color, s=60, zorder=5)\n",
|
||||
" ax1.set_xlabel('Номер попытки')\n",
|
||||
" ax1.set_ylabel('Время (сек)')\n",
|
||||
" ax1.set_title(f'{op_name} – случайный порядок')\n",
|
||||
" ax1.legend()\n",
|
||||
" ax1.grid(True, linestyle=':', alpha=0.7)\n",
|
||||
" # отсортированный порядок\n",
|
||||
" for struct, label, color, marker in [('ll','LinkedList','red','o'), ('ht','HashTable','green','s'), ('bst','BST','blue','^')]:\n",
|
||||
" times = data_so[struct]\n",
|
||||
" x = range(1, len(times)+1)\n",
|
||||
" ax2.plot(x, times, marker=marker, color=color, label=label, linestyle='--', linewidth=1)\n",
|
||||
" ax2.scatter(x, times, color=color, s=60, zorder=5)\n",
|
||||
" ax2.set_xlabel('Номер попытки')\n",
|
||||
" ax2.set_ylabel('Время (сек)')\n",
|
||||
" ax2.set_title(f'{op_name} – отсортированный порядок')\n",
|
||||
" ax2.legend()\n",
|
||||
" ax2.grid(True, linestyle=':', alpha=0.7)\n",
|
||||
" plt.tight_layout()\n",
|
||||
" plt.savefig(f'{op_name}_5attempts.png')\n",
|
||||
" plt.show()\n",
|
||||
" \n",
|
||||
" plot_attempts(insert_sh, insert_so, 'insert')\n",
|
||||
" plot_attempts(find_sh, find_so, 'find')\n",
|
||||
" plot_attempts(delete_sh, delete_so, 'delete')\n",
|
||||
" print(\"Дополнительные графики сохранены: insert_5attempts.png, find_5attempts.png, delete_5attempts.png\")\n",
|
||||
" except Exception as e:\n",
|
||||
" print(f\"Не удалось построить дополнительные графики: {e}\")\n",
|
||||
"\n",
|
||||
"if __name__ == \"__main__\":\n",
|
||||
" sys.setrecursionlimit(20000)\n",
|
||||
" main()\n",
|
||||
"\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "cead201d-1150-463f-9ff3-a4bed6f7fc03",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.13.5"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
594
filippovavm/docs/laba1/МП.ipynb
Normal file
|
|
@ -0,0 +1,594 @@
|
|||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "dbe95ca0-7bc2-4cea-bdbb-319e9a1bd5b6",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Импорт библиотек\n",
|
||||
"# В этом блоке подключаются модули для работы со временем, случайными числами, CSV, системными параметрами, а также для построения графиков."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"id": "c0b2cd62-6c05-4896-8f40-82d75ae765e9",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import time\n",
|
||||
"import random\n",
|
||||
"import csv\n",
|
||||
"import sys\n",
|
||||
"import matplotlib.pyplot as plt\n",
|
||||
"import numpy as np"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "919b92b0-2819-457a-87a0-8f26eaeca817",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Связный список\n",
|
||||
"# Каждый узел содержит имя, телефон и ссылку на следующий узел.\n",
|
||||
"# Функции: ll_insert (вставка/обновление), ll_find (поиск), ll_delete (удаление)."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"id": "30700662-1215-4476-bffe-96ad9d3f1ab4",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Связный список\n",
|
||||
"def ll_insert(head, name, phone):\n",
|
||||
" current = head\n",
|
||||
" prev = None\n",
|
||||
" while current is not None:\n",
|
||||
" if current['name'] == name:\n",
|
||||
" current['phone'] = phone\n",
|
||||
" return head\n",
|
||||
" prev = current\n",
|
||||
" current = current['next']\n",
|
||||
" new_node = {'name': name, 'phone': phone, 'next': None}\n",
|
||||
" if prev is None:\n",
|
||||
" return new_node\n",
|
||||
" else:\n",
|
||||
" prev['next'] = new_node\n",
|
||||
" return head\n",
|
||||
"\n",
|
||||
"def ll_find(head, name):\n",
|
||||
" current = head\n",
|
||||
" while current is not None:\n",
|
||||
" if current['name'] == name:\n",
|
||||
" return current['phone']\n",
|
||||
" current = current['next']\n",
|
||||
" return None\n",
|
||||
"\n",
|
||||
"def ll_delete(head, name):\n",
|
||||
" if head is None:\n",
|
||||
" return None\n",
|
||||
" if head['name'] == name:\n",
|
||||
" return head['next']\n",
|
||||
" current = head\n",
|
||||
" while current['next'] is not None:\n",
|
||||
" if current['next']['name'] == name:\n",
|
||||
" current['next'] = current['next']['next']\n",
|
||||
" return head\n",
|
||||
" current = current['next']\n",
|
||||
" return head"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "49dd7db0-58a7-4ff9-98cd-529e2e91ca94",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Хеш-таблица\n",
|
||||
"# Хеш-функция на основе полиномиального кода (31 и длина таблицы).\n",
|
||||
"# Размер таблицы фиксирован (2000). В каждой ячейке хранится связный список.\n",
|
||||
"# Функции: ht_create, ht_insert, ht_find, ht_delete."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"id": "22800445-5217-45ce-9ecd-0f61389b1c3f",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Хеш-таблица \n",
|
||||
"def hash_function(name, size):\n",
|
||||
" total = 0\n",
|
||||
" for ch in name:\n",
|
||||
" total = (total * 31 + ord(ch)) % size\n",
|
||||
" return total\n",
|
||||
"\n",
|
||||
"def ht_create(size=2000):\n",
|
||||
" return [None] * size\n",
|
||||
"\n",
|
||||
"def ht_insert(buckets, name, phone):\n",
|
||||
" idx = hash_function(name, len(buckets))\n",
|
||||
" buckets[idx] = ll_insert(buckets[idx], name, phone)\n",
|
||||
"\n",
|
||||
"def ht_find(buckets, name):\n",
|
||||
" idx = hash_function(name, len(buckets))\n",
|
||||
" return ll_find(buckets[idx], name)\n",
|
||||
"\n",
|
||||
"def ht_delete(buckets, name):\n",
|
||||
" idx = hash_function(name, len(buckets))\n",
|
||||
" buckets[idx] = ll_delete(buckets[idx], name)\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "0b03af57-2771-4b20-8e7d-1ac475afaae8",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Двоичное дерево поиска\n",
|
||||
"# Узел содержит имя, телефон, ссылки на левого и правого потомка.\n",
|
||||
"# Функции: bst_insert (вставка/обновление), bst_find (поиск), bst_delete (удаление).\n",
|
||||
"# Для удаления используется поиск преемника (минимальный элемент в правом поддереве)."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"id": "7ced846b-b65f-4cf2-8fb8-5a4849f55509",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Двоичное дерево поиска \n",
|
||||
"def bst_insert(root, name, phone):\n",
|
||||
" new_node = {'name': name, 'phone': phone, 'left': None, 'right': None}\n",
|
||||
" if root is None:\n",
|
||||
" return new_node\n",
|
||||
" current = root\n",
|
||||
" while True:\n",
|
||||
" if name < current['name']:\n",
|
||||
" if current['left'] is None:\n",
|
||||
" current['left'] = new_node\n",
|
||||
" break\n",
|
||||
" current = current['left']\n",
|
||||
" elif name > current['name']:\n",
|
||||
" if current['right'] is None:\n",
|
||||
" current['right'] = new_node\n",
|
||||
" break\n",
|
||||
" current = current['right']\n",
|
||||
" else:\n",
|
||||
" current['phone'] = phone\n",
|
||||
" break\n",
|
||||
" return root\n",
|
||||
"\n",
|
||||
"def bst_find(root, name):\n",
|
||||
" current = root\n",
|
||||
" while current is not None:\n",
|
||||
" if name < current['name']:\n",
|
||||
" current = current['left']\n",
|
||||
" elif name > current['name']:\n",
|
||||
" current = current['right']\n",
|
||||
" else:\n",
|
||||
" return current['phone']\n",
|
||||
" return None\n",
|
||||
"\n",
|
||||
"def bst_find_min(node):\n",
|
||||
" while node['left'] is not None:\n",
|
||||
" node = node['left']\n",
|
||||
" return node\n",
|
||||
"\n",
|
||||
"def bst_delete(root, name):\n",
|
||||
" parent = None\n",
|
||||
" current = root\n",
|
||||
" while current is not None and current['name'] != name:\n",
|
||||
" parent = current\n",
|
||||
" if name < current['name']:\n",
|
||||
" current = current['left']\n",
|
||||
" else:\n",
|
||||
" current = current['right']\n",
|
||||
" if current is None:\n",
|
||||
" return root\n",
|
||||
" \n",
|
||||
" if current['left'] is None and current['right'] is None:\n",
|
||||
" if parent is None:\n",
|
||||
" return None\n",
|
||||
" if parent['left'] is current:\n",
|
||||
" parent['left'] = None\n",
|
||||
" else:\n",
|
||||
" parent['right'] = None\n",
|
||||
" return root\n",
|
||||
" if current['left'] is None:\n",
|
||||
" if parent is None:\n",
|
||||
" return current['right']\n",
|
||||
" if parent['left'] is current:\n",
|
||||
" parent['left'] = current['right']\n",
|
||||
" else:\n",
|
||||
" parent['right'] = current['right']\n",
|
||||
" return root\n",
|
||||
" if current['right'] is None:\n",
|
||||
" if parent is None:\n",
|
||||
" return current['left']\n",
|
||||
" if parent['left'] is current:\n",
|
||||
" parent['left'] = current['left']\n",
|
||||
" else:\n",
|
||||
" parent['right'] = current['left']\n",
|
||||
" return root\n",
|
||||
" \n",
|
||||
" succ_parent = current\n",
|
||||
" succ = current['right']\n",
|
||||
" while succ['left'] is not None:\n",
|
||||
" succ_parent = succ\n",
|
||||
" succ = succ['left']\n",
|
||||
" current['name'] = succ['name']\n",
|
||||
" current['phone'] = succ['phone']\n",
|
||||
" if succ_parent['left'] is succ:\n",
|
||||
" succ_parent['left'] = succ['right']\n",
|
||||
" else:\n",
|
||||
" succ_parent['right'] = succ['right']\n",
|
||||
" return root"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "88e1d5ec-5123-4bff-837a-bb451a0125c3",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Генерация записей телефонной книги\n",
|
||||
"# Создаёт N записей вида User_00001 и случайный номер телефона."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "b266c127-99a7-479c-a012-3eedeff7ade3",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Генерация данных \n",
|
||||
"def generate_records(N):\n",
|
||||
" records = []\n",
|
||||
" for i in range(N):\n",
|
||||
" name = f\"User_{i:05d}\"\n",
|
||||
" phone = f\"+7-999-{random.randint(1000000, 9999999)}\"\n",
|
||||
" records.append((name, phone))\n",
|
||||
" return records"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "a92500c8-e928-46a8-a115-12cc033ea2da",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Функции замеров\n",
|
||||
"# measure_insert – вставка всех записей (повторяется 5 раз).\n",
|
||||
"# build_structure – построение структуры данных (используется для последующих замеров).\n",
|
||||
"# measure_find_on_structure – поиск 110 записей (100 существующих + 10 отсутствующих) 5 раз.\n",
|
||||
"# measure_delete_on_structure – удаление 50 случайных записей 5 раз."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "d5060601-6e07-4148-9faa-f9b7b5f433dd",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Замеры\n",
|
||||
"REPEATS = 5\n",
|
||||
"N = 10000\n",
|
||||
"\n",
|
||||
"def measure_insert(struct, records, repeats=REPEATS):\n",
|
||||
" times = []\n",
|
||||
" for _ in range(repeats):\n",
|
||||
" if struct == 'll':\n",
|
||||
" head = None\n",
|
||||
" start = time.perf_counter()\n",
|
||||
" for name, phone in records:\n",
|
||||
" head = ll_insert(head, name, phone)\n",
|
||||
" end = time.perf_counter()\n",
|
||||
" elif struct == 'ht':\n",
|
||||
" buckets = ht_create(2000)\n",
|
||||
" start = time.perf_counter()\n",
|
||||
" for name, phone in records:\n",
|
||||
" ht_insert(buckets, name, phone)\n",
|
||||
" end = time.perf_counter()\n",
|
||||
" elif struct == 'bst':\n",
|
||||
" root = None\n",
|
||||
" start = time.perf_counter()\n",
|
||||
" for name, phone in records:\n",
|
||||
" root = bst_insert(root, name, phone)\n",
|
||||
" end = time.perf_counter()\n",
|
||||
" times.append(end - start)\n",
|
||||
" return times\n",
|
||||
"\n",
|
||||
"def build_structure(struct, records):\n",
|
||||
" if struct == 'll':\n",
|
||||
" head = None\n",
|
||||
" for name, phone in records:\n",
|
||||
" head = ll_insert(head, name, phone)\n",
|
||||
" return head\n",
|
||||
" elif struct == 'ht':\n",
|
||||
" buckets = ht_create(2000)\n",
|
||||
" for name, phone in records:\n",
|
||||
" ht_insert(buckets, name, phone)\n",
|
||||
" return buckets\n",
|
||||
" elif struct == 'bst':\n",
|
||||
" root = None\n",
|
||||
" for name, phone in records:\n",
|
||||
" root = bst_insert(root, name, phone)\n",
|
||||
" return root\n",
|
||||
" def measure_find_on_structure(struct, structure, records, repeats=REPEATS):\n",
|
||||
" times = []\n",
|
||||
" N = len(records)\n",
|
||||
" for _ in range(repeats):\n",
|
||||
" indices = random.sample(range(N), 100)\n",
|
||||
" exist = [records[i][0] for i in indices]\n",
|
||||
" missing = [f\"None_{i}\" for i in range(10)]\n",
|
||||
" search = exist + missing\n",
|
||||
" start = time.perf_counter()\n",
|
||||
" if struct == 'll':\n",
|
||||
" for name in search:\n",
|
||||
" ll_find(structure, name)\n",
|
||||
" elif struct == 'ht':\n",
|
||||
" for name in search:\n",
|
||||
" ht_find(structure, name)\n",
|
||||
" elif struct == 'bst':\n",
|
||||
" for name in search:\n",
|
||||
" bst_find(structure, name)\n",
|
||||
" times.append(time.perf_counter() - start)\n",
|
||||
" return times\n",
|
||||
"\n",
|
||||
"def measure_delete_on_structure(struct, records, repeats=REPEATS):\n",
|
||||
" times = []\n",
|
||||
" N = len(records)\n",
|
||||
" for _ in range(repeats):\n",
|
||||
" indices = random.sample(range(N), 50)\n",
|
||||
" del_names = [records[i][0] for i in indices]\n",
|
||||
" if struct == 'll':\n",
|
||||
" head = None\n",
|
||||
" for name, phone in records:\n",
|
||||
" head = ll_insert(head, name, phone)\n",
|
||||
" start = time.perf_counter()\n",
|
||||
" for name in del_names:\n",
|
||||
" head = ll_delete(head, name)\n",
|
||||
" end = time.perf_counter()\n",
|
||||
" elif struct == 'ht':\n",
|
||||
" buckets = ht_create(2000)\n",
|
||||
" for name, phone in records:\n",
|
||||
" ht_insert(buckets, name, phone)\n",
|
||||
" start = time.perf_counter()\n",
|
||||
" for name in del_names:\n",
|
||||
" ht_delete(buckets, name)\n",
|
||||
" end = time.perf_counter()\n",
|
||||
" elif struct == 'bst':\n",
|
||||
" root = None\n",
|
||||
" for name, phone in records:\n",
|
||||
" root = bst_insert(root, name, phone)\n",
|
||||
" start = time.perf_counter()\n",
|
||||
" for name in del_names:\n",
|
||||
" root = bst_delete(root, name)\n",
|
||||
" end = time.perf_counter()\n",
|
||||
" times.append(end - start)\n",
|
||||
" return times\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "d93d8eac-b094-43b2-8af0-9afb0ab51ada",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Основная функция\n",
|
||||
"# 1. Генерирует 10000 записей в случайном и отсортированном порядке.\n",
|
||||
"# 2. Измеряет время вставки всех записей, поиска (110 запросов) и удаления (50 записей)\n",
|
||||
"# для связного списка, хеш-таблицы и дерева.\n",
|
||||
"# 3. Выводит средние значения, сохраняет все замеры в CSV.\n",
|
||||
"# 4. Строит несколько графиков (сравнительные столбчатые диаграммы и графики по попыткам)."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "a667779c-39b8-4e4b-b05d-bf9c9cccbbd9",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Основная функция \n",
|
||||
"def main():\n",
|
||||
" print(\"Генерация данных...\")\n",
|
||||
" records = generate_records(N)\n",
|
||||
" random.shuffle(records) # случайный порядок\n",
|
||||
" records_sorted = sorted(records, key=lambda x: x[0]) # отсортированный\n",
|
||||
"\n",
|
||||
" results = [] # для CSV\n",
|
||||
" struct_names = {'ll': 'Связного списка', 'ht': 'Хеш-таблицы', 'bst': 'Двоичного дерева поиска'}\n",
|
||||
" mode_names = {'shuffled': 'случайный', 'sorted': 'отсортированный'}\n",
|
||||
" op_names = {'insert': 'Вставка всех записей', 'find': 'Поиск записей', 'delete': 'Удаление записей'}\n",
|
||||
" # Вставка \n",
|
||||
" for struct in ['ll', 'ht', 'bst']:\n",
|
||||
" print(f\"\\nИзмерение вставки для {struct_names[struct]}...\")\n",
|
||||
" times_sh = measure_insert(struct, records)\n",
|
||||
" times_so = measure_insert(struct, records_sorted)\n",
|
||||
" insert_sh[struct] = times_sh\n",
|
||||
" insert_so[struct] = times_so\n",
|
||||
" print(f\" случайный: {[round(t,6) for t in times_sh]}, среднее = {sum(times_sh)/len(times_sh):.6f}\")\n",
|
||||
" print(f\" отсортированный: {[round(t,6) for t in times_so]}, среднее = {sum(times_so)/len(times_so):.6f}\")\n",
|
||||
" results.append([struct_names[struct], mode_names['shuffled'], op_names['insert'], sum(times_sh)/len(times_sh)] + times_sh)\n",
|
||||
" results.append([struct_names[struct], mode_names['sorted'], op_names['insert'], sum(times_so)/len(times_so)] + times_so)\n",
|
||||
" # Поиск \n",
|
||||
" for struct in ['ll', 'ht', 'bst']:\n",
|
||||
" print(f\"\\nПоиск для {struct_names[struct]} на случайных данных...\")\n",
|
||||
" structure_sh = build_structure(struct, records)\n",
|
||||
" times_find_sh = measure_find_on_structure(struct, structure_sh, records)\n",
|
||||
" find_sh[struct] = times_find_sh\n",
|
||||
" print(f\" случайный: {[round(t,6) for t in times_find_sh]}, среднее = {sum(times_find_sh)/len(times_find_sh):.6f}\")\n",
|
||||
" results.append([struct_names[struct], mode_names['shuffled'], op_names['find'], sum(times_find_sh)/len(times_find_sh)] + times_find_sh)\n",
|
||||
"\n",
|
||||
" print(f\"Поиск для {struct_names[struct]} на отсортированных данных...\")\n",
|
||||
" structure_so = build_structure(struct, records_sorted)\n",
|
||||
" times_find_so = measure_find_on_structure(struct, structure_so, records_sorted)\n",
|
||||
" find_so[struct] = times_find_so\n",
|
||||
" print(f\" отсортированный: {[round(t,6) for t in times_find_so]}, среднее = {sum(times_find_so)/len(times_find_so):.6f}\")\n",
|
||||
" results.append([struct_names[struct], mode_names['sorted'], op_names['find'], sum(times_find_so)/len(times_find_so)] + times_find_so)\n",
|
||||
" # Удаление \n",
|
||||
" for struct in ['ll', 'ht', 'bst']:\n",
|
||||
" print(f\"\\nУдаление для {struct_names[struct]} на случайных данных...\")\n",
|
||||
" times_del_sh = measure_delete_on_structure(struct, records)\n",
|
||||
" delete_sh[struct] = times_del_sh\n",
|
||||
" print(f\" случайный: {[round(t,6) for t in times_del_sh]}, среднее = {sum(times_del_sh)/len(times_del_sh):.6f}\")\n",
|
||||
" results.append([struct_names[struct], mode_names['shuffled'], op_names['delete'], sum(times_del_sh)/len(times_del_sh)] + times_del_sh)\n",
|
||||
"\n",
|
||||
" print(f\"Удаление для {struct_names[struct]} на отсортированных данных...\")\n",
|
||||
" times_del_so = measure_delete_on_structure(struct, records_sorted)\n",
|
||||
" delete_so[struct] = times_del_so\n",
|
||||
" print(f\" отсортированный: {[round(t,6) for t in times_del_so]}, среднее = {sum(times_del_so)/len(times_del_so):.6f}\")\n",
|
||||
" results.append([struct_names[struct], mode_names['sorted'], op_names['delete'], sum(times_del_so)/len(times_del_so)] + times_del_so)\n",
|
||||
" # Сохраняем CSV\n",
|
||||
" with open(\"phonebook_results.csv\", \"w\", newline=\"\", encoding=\"utf-8\") as f:\n",
|
||||
" writer = csv.writer(f)\n",
|
||||
" writer.writerow(['Структура', 'Режим', 'Операция', 'Среднее', 'Замер1', 'Замер2', 'Замер3', 'Замер4', 'Замер5'])\n",
|
||||
" writer.writerows(results)\n",
|
||||
"# Графики замеров\n",
|
||||
" try:\n",
|
||||
" def plot_attempts(data_sh, data_so, op_name):\n",
|
||||
" fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))\n",
|
||||
" # случайный порядок\n",
|
||||
" for struct, label, color, marker in [('ll','LinkedList','red','o'), ('ht','HashTable','green','s'), ('bst','BST','blue','^')]:\n",
|
||||
" times = data_sh[struct]\n",
|
||||
" x = range(1, len(times)+1)\n",
|
||||
" ax1.plot(x, times, marker=marker, color=color, label=label, linestyle='--', linewidth=1)\n",
|
||||
" ax1.scatter(x, times, color=color, s=60, zorder=5)\n",
|
||||
" ax1.set_xlabel('Номер попытки')\n",
|
||||
" ax1.set_ylabel('Время (сек)')\n",
|
||||
" ax1.set_title(f'{op_name} – случайный порядок')\n",
|
||||
" ax1.legend()\n",
|
||||
" ax1.grid(True, linestyle=':', alpha=0.7)\n",
|
||||
" # отсортированный порядок\n",
|
||||
" for struct, label, color, marker in [('ll','LinkedList','red','o'), ('ht','HashTable','green','s'), ('bst','BST','blue','^')]:\n",
|
||||
" times = data_so[struct]\n",
|
||||
" x = range(1, len(times)+1)\n",
|
||||
" ax2.plot(x, times, marker=marker, color=color, label=label, linestyle='--', linewidth=1)\n",
|
||||
" ax2.scatter(x, times, color=color, s=60, zorder=5)\n",
|
||||
" ax2.set_xlabel('Номер попытки')\n",
|
||||
" ax2.set_ylabel('Время (сек)')\n",
|
||||
" ax2.set_title(f'{op_name} – отсортированный порядок')\n",
|
||||
" ax2.legend()\n",
|
||||
" ax2.grid(True, linestyle=':', alpha=0.7)\n",
|
||||
" plt.tight_layout()\n",
|
||||
" plt.savefig(f'{op_name}_5attempts.png')\n",
|
||||
" plt.show()\n",
|
||||
" \n",
|
||||
" plot_attempts(insert_sh, insert_so, 'insert')\n",
|
||||
" plot_attempts(find_sh, find_so, 'find')\n",
|
||||
" plot_attempts(delete_sh, delete_so, 'delete')\n",
|
||||
" print(\"Дополнительные графики сохранены: insert_5attempts.png, find_5attempts.png, delete_5attempts.png\")\n",
|
||||
" except Exception as e:\n",
|
||||
" print(f\"Не удалось построить дополнительные графики: {e}\")\n",
|
||||
"try:\n",
|
||||
" # График вставки\n",
|
||||
" fig1, ax1 = plt.subplots(figsize=(10,6))\n",
|
||||
" x = np.arange(3)\n",
|
||||
" width = 0.35\n",
|
||||
" means_sh = [sum(insert_sh[s])/len(insert_sh[s]) for s in ['ll','ht','bst']]\n",
|
||||
" means_so = [sum(insert_so[s])/len(insert_so[s]) for s in ['ll','ht','bst']]\n",
|
||||
" rects1 = ax1.bar(x - width/2, means_sh, width, label='Случайный порядок', color='skyblue')\n",
|
||||
" rects2 = ax1.bar(x + width/2, means_so, width, label='Отсортированный порядок', color='salmon')\n",
|
||||
" ax1.set_ylabel('Время (сек)')\n",
|
||||
" ax1.set_title('Вставка всех записей')\n",
|
||||
" ax1.set_xticks(x)\n",
|
||||
" ax1.set_xticklabels(['Связный список', 'Хеш-таблица', 'Двоичное дерево'])\n",
|
||||
" ax1.legend()\n",
|
||||
" ax1.set_yscale('log')\n",
|
||||
" for rect in rects1 + rects2:\n",
|
||||
" h = rect.get_height()\n",
|
||||
" ax1.annotate(f'{h:.3f}', xy=(rect.get_x()+rect.get_width()/2, h),\n",
|
||||
" xytext=(0,3), textcoords=\"offset points\", ha='center', va='bottom', fontsize=8)\n",
|
||||
" plt.tight_layout()\n",
|
||||
" plt.savefig('insert_comparison.png')\n",
|
||||
" plt.show()\n",
|
||||
"\n",
|
||||
" # График поиска\n",
|
||||
" fig2, ax2 = plt.subplots(figsize=(10,6))\n",
|
||||
" means_find_sh = [sum(find_sh[s])/len(find_sh[s]) for s in ['ll','ht','bst']]\n",
|
||||
" means_find_so = [sum(find_so[s])/len(find_so[s]) for s in ['ll','ht','bst']]\n",
|
||||
" rects1 = ax2.bar(x - width/2, means_find_sh, width, label='Случайный порядок', color='skyblue')\n",
|
||||
" rects2 = ax2.bar(x + width/2, means_find_so, width, label='Отсортированный порядок', color='salmon')\n",
|
||||
" ax2.set_ylabel('Время (сек)')\n",
|
||||
" ax2.set_title('Поиск (100 существующих + 10 отсутствующих)')\n",
|
||||
" ax2.set_xticks(x)\n",
|
||||
" ax2.set_xticklabels(['Связный список', 'Хеш-таблица', 'Двоичное дерево'])\n",
|
||||
" ax2.legend()\n",
|
||||
" for rect in rects1 + rects2:\n",
|
||||
" h = rect.get_height()\n",
|
||||
" ax2.annotate(f'{h:.5f}', xy=(rect.get_x()+rect.get_width()/2, h),\n",
|
||||
" xytext=(0,3), textcoords=\"offset points\", ha='center', va='bottom', fontsize=8)\n",
|
||||
" plt.tight_layout()\n",
|
||||
" plt.savefig('find_comparison.png')\n",
|
||||
" plt.show()\n",
|
||||
"\n",
|
||||
" # График удаления \n",
|
||||
" fig3, ax3 = plt.subplots(figsize=(10,6))\n",
|
||||
" means_del_sh = [sum(delete_sh[s])/len(delete_sh[s]) for s in ['ll','ht','bst']]\n",
|
||||
" means_del_so = [sum(delete_so[s])/len(delete_so[s]) for s in ['ll','ht','bst']]\n",
|
||||
" rects1 = ax3.bar(x - width/2, means_del_sh, width, label='Случайный порядок', color='skyblue')\n",
|
||||
" rects2 = ax3.bar(x + width/2, means_del_so, width, label='Отсортированный порядок', color='salmon')\n",
|
||||
" ax3.set_ylabel('Время (сек)')\n",
|
||||
" ax3.set_title('Удаление 50 случайных записей')\n",
|
||||
" ax3.set_xticks(x)\n",
|
||||
" ax3.set_xticklabels(['Связный список', 'Хеш-таблица', 'Двоичное дерево'])\n",
|
||||
" ax3.legend()\n",
|
||||
" for rect in rects1 + rects2:\n",
|
||||
" h = rect.get_height()\n",
|
||||
" ax3.annotate(f'{h:.5f}', xy=(rect.get_x()+rect.get_width()/2, h),\n",
|
||||
" xytext=(0,3), textcoords=\"offset points\", ha='center', va='bottom', fontsize=8)\n",
|
||||
" plt.tight_layout()\n",
|
||||
" plt.savefig('delete_comparison.png')\n",
|
||||
" plt.show()\n",
|
||||
" print(\"Графики сохранены: insert_comparison.png, find_comparison.png, delete_comparison.png\")\n",
|
||||
" except Exception as e:\n",
|
||||
" print(f\"Не удалось построить графики: {e}\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "dd2b18cd-f37c-4f9f-a92a-325be857deb0",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Запуск эксперимента\n",
|
||||
"# Устанавливается увеличенная глубина рекурсии (на случай глубоких деревьев) и вызывается main()."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "04462e9d-aecc-44b3-b98b-0d06f856f38a",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"if __name__ == \"__main__\":\n",
|
||||
" sys.setrecursionlimit(20000)\n",
|
||||
" main()"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.13.5"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
149
filippovavm/docs/laba1/отчёт1.ipynb
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "0c973489-075d-42ac-a28f-f8d8d954a0da",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Анализ результатов\n",
|
||||
"\n",
|
||||
"## Предложенные вопросы\n",
|
||||
"\n",
|
||||
"- Как порядок входных данных влияет на скорость вставки в BST (деградация до O(n) на отсортированных данных)?\n",
|
||||
"- Почему хеш-таблица почти не чувствительна к порядку?\n",
|
||||
"- Почему связный список всегда медленен при поиске?\n",
|
||||
"- Как удаление работает в каждой структуре?\n",
|
||||
"- Вывод: какую структуру и для каких задач (частые вставки, частый поиск, необходимость получать данные в порядке) стоит выбирать в реальной жизни?\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "a265cc14-95ff-47ae-a346-ac38cb2a323e",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Выводы\n",
|
||||
"\n",
|
||||
"### 1) Как порядок входных данных влияет на скорость вставки в BST?\n",
|
||||
"\n",
|
||||
"Порядок отличается очень сильно. В обычном случае сложность равна **O(log n)**, а в худшем случае (как раз на отсортированных данных) – **O(n)**. \n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "950c5e97-12e9-4225-a91e-b8289fdfb5e6",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### 2) Почему хеш-таблица почти не чувствительна к порядку?\n",
|
||||
"\n",
|
||||
"Это происходит из‑за особенностей записи данных в память. Хеш-таблица вычисляет номер строки (корзины) по математической формуле, поэтому любой элемент можно найти за **O(1)** в среднем. Порядок поступления записей не влияет на расчёт индекса.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "5f6059bf-e99a-4b14-869c-32fb44b092fa",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### 3) Почему связный список всегда медленен при поиске?\n",
|
||||
"\n",
|
||||
"Это происходит из‑за способа записи. Доступ к следующему элементу возможен только последовательным перебором, равным номеру искомой позиции. Сложность поиска – **O(n)**.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "77ddd385-a50d-4ab6-b761-477460529e9d",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### 4) Как удаление работает в каждой структуре?\n",
|
||||
"\n",
|
||||
"- **Связный список** \n",
|
||||
" - Если список пустой → возвращаем `None`. \n",
|
||||
" - Если удаляем голову → новой головой становится следующий элемент. \n",
|
||||
" - Если удаляем промежуточный элемент – ищем нужный узел, затем у предыдущего узла меняем ссылку на следующий после удаляемого.\n",
|
||||
"\n",
|
||||
"- **Хеш-таблица** \n",
|
||||
" Реализация вычисляет номер корзины через хеш-ключ, затем использует функции связного списка для работы внутри корзины. Таким образом, удаление в хеш-таблице наследует логику удаления из связного списка, но применяется только к элементам одной корзины.\n",
|
||||
"\n",
|
||||
"- **Бинарное дерево (BST)** \n",
|
||||
" Рассматриваются 4 случая (логика похожа на связный список, но с учётом двух потомков): \n",
|
||||
" - Если дерево пустое → вернуть `None`. \n",
|
||||
" - Если удаляемый элемент меньше корня → спуститься в левую ветвь. \n",
|
||||
" - Если больше корня → спуститься в правую ветвь. \n",
|
||||
" - Если у удаляемого узла два потомка – находим преемника (самый левый узел в правом поддереве), копируем его данные в удаляемый узел и удаляем преемника. \n",
|
||||
" При нахождении элемента ссылка от родителя к удаляемому узлу заменяется на ссылку на соответствующего потомка (левый или правый).\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "97b09bb6-e8ef-486e-9cdd-fc37b19bfeb2",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### 5) Какую структуру и для каких задач стоит выбирать в реальной жизни?\n",
|
||||
"\n",
|
||||
"- **Для частых вставок и поиска элементов** – лучше всего использовать **хеш-таблицу**, так как добавление происходит за счёт математического вычисления индекса, а не последовательного перебора. \n",
|
||||
"- **Если нужны упорядоченные данные** (например, вывод записей в алфавитном порядке) – подходит **двоичное дерево поиска** (BST) благодаря свойству in‑order обхода. \n",
|
||||
"- **Связный список** неэффективен для больших объёмов данных.\n",
|
||||
"\n",
|
||||
"---"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "3b242f54-fd27-4f6e-8a31-9b3a6bee5f59",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Дополнительные числовые результаты\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"1. **Связный список:** \n",
|
||||
" - Вставка: O(n) → ~0.25 с, порядок данных не влияет. \n",
|
||||
" - Поиск: O(n) → очень медленно (~0.5 с), порядок не влияет. \n",
|
||||
" - Удаление: O(n) → медленно.\n",
|
||||
"\n",
|
||||
"2. **Хеш-таблица:** \n",
|
||||
" - Вставка: O(1) в среднем → ~0.008 с, порядок данных не влияет. \n",
|
||||
" - Поиск: O(1) → ~0.002 с, самый быстрый. \n",
|
||||
" - Удаление: O(1) → ~0.002 с.\n",
|
||||
"\n",
|
||||
"3. **Двоичное дерево поиска:** \n",
|
||||
" - На случайных данных: O(log n) → вставка ~0.018 с, поиск ~0.0015 с, удаление ~0.0016 с. \n",
|
||||
" - На отсортированных данных: дерево вырождается в линейный список → вставка ~2.3 с, поиск и удаление также становятся O(n) (на графиках виден рост времени).\n",
|
||||
"\n",
|
||||
"**ИТОГОВЫЙ ВЫВОД:** \n",
|
||||
"- Для частого поиска, вставки и удаления – лучший выбор **хеш-таблица**. \n",
|
||||
"- Если данные поступают в отсортированном порядке – BST не подходит из‑за деградации. \n",
|
||||
"- Если нужна частая выдача записей в алфавитном порядке и данные случайны – BST хорош. \n",
|
||||
"- Связный список неэффективен для больших объёмов."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "1b39f136-0c95-46f0-b794-7136232bcd3c",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.13.5"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
490
filippovavm/docs/laba1/фулкод1.ipynb
Normal file
|
|
@ -0,0 +1,490 @@
|
|||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "65b0def8-04cf-4cfd-b4d4-bc862e120497",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Генерация данных...\n",
|
||||
"\n",
|
||||
"Измерение вставки для Связного списка...\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"import time\n",
|
||||
"import random\n",
|
||||
"import csv\n",
|
||||
"import sys\n",
|
||||
"import matplotlib.pyplot as plt\n",
|
||||
"import numpy as np\n",
|
||||
"\n",
|
||||
"# Связный список\n",
|
||||
"def ll_insert(head, name, phone):\n",
|
||||
" current = head\n",
|
||||
" prev = None\n",
|
||||
" while current is not None:\n",
|
||||
" if current['name'] == name:\n",
|
||||
" current['phone'] = phone\n",
|
||||
" return head\n",
|
||||
" prev = current\n",
|
||||
" current = current['next']\n",
|
||||
" new_node = {'name': name, 'phone': phone, 'next': None}\n",
|
||||
" if prev is None:\n",
|
||||
" return new_node\n",
|
||||
" else:\n",
|
||||
" prev['next'] = new_node\n",
|
||||
" return head\n",
|
||||
"\n",
|
||||
"def ll_find(head, name):\n",
|
||||
" current = head\n",
|
||||
" while current is not None:\n",
|
||||
" if current['name'] == name:\n",
|
||||
" return current['phone']\n",
|
||||
" current = current['next']\n",
|
||||
" return None\n",
|
||||
"\n",
|
||||
"def ll_delete(head, name):\n",
|
||||
" if head is None:\n",
|
||||
" return None\n",
|
||||
" if head['name'] == name:\n",
|
||||
" return head['next']\n",
|
||||
" current = head\n",
|
||||
" while current['next'] is not None:\n",
|
||||
" if current['next']['name'] == name:\n",
|
||||
" current['next'] = current['next']['next']\n",
|
||||
" return head\n",
|
||||
" current = current['next']\n",
|
||||
" return head\n",
|
||||
"\n",
|
||||
"# Хеш-таблица \n",
|
||||
"def hash_function(name, size):\n",
|
||||
" total = 0\n",
|
||||
" for ch in name:\n",
|
||||
" total = (total * 31 + ord(ch)) % size\n",
|
||||
" return total\n",
|
||||
"\n",
|
||||
"def ht_create(size=2000):\n",
|
||||
" return [None] * size\n",
|
||||
"\n",
|
||||
"def ht_insert(buckets, name, phone):\n",
|
||||
" idx = hash_function(name, len(buckets))\n",
|
||||
" buckets[idx] = ll_insert(buckets[idx], name, phone)\n",
|
||||
"\n",
|
||||
"def ht_find(buckets, name):\n",
|
||||
" idx = hash_function(name, len(buckets))\n",
|
||||
" return ll_find(buckets[idx], name)\n",
|
||||
"\n",
|
||||
"def ht_delete(buckets, name):\n",
|
||||
" idx = hash_function(name, len(buckets))\n",
|
||||
" buckets[idx] = ll_delete(buckets[idx], name)\n",
|
||||
"\n",
|
||||
"# Двоичное дерево поиска \n",
|
||||
"def bst_insert(root, name, phone):\n",
|
||||
" new_node = {'name': name, 'phone': phone, 'left': None, 'right': None}\n",
|
||||
" if root is None:\n",
|
||||
" return new_node\n",
|
||||
" current = root\n",
|
||||
" while True:\n",
|
||||
" if name < current['name']:\n",
|
||||
" if current['left'] is None:\n",
|
||||
" current['left'] = new_node\n",
|
||||
" break\n",
|
||||
" current = current['left']\n",
|
||||
" elif name > current['name']:\n",
|
||||
" if current['right'] is None:\n",
|
||||
" current['right'] = new_node\n",
|
||||
" break\n",
|
||||
" current = current['right']\n",
|
||||
" else:\n",
|
||||
" current['phone'] = phone\n",
|
||||
" break\n",
|
||||
" return root\n",
|
||||
"\n",
|
||||
"def bst_find(root, name):\n",
|
||||
" current = root\n",
|
||||
" while current is not None:\n",
|
||||
" if name < current['name']:\n",
|
||||
" current = current['left']\n",
|
||||
" elif name > current['name']:\n",
|
||||
" current = current['right']\n",
|
||||
" else:\n",
|
||||
" return current['phone']\n",
|
||||
" return None\n",
|
||||
"\n",
|
||||
"def bst_find_min(node):\n",
|
||||
" while node['left'] is not None:\n",
|
||||
" node = node['left']\n",
|
||||
" return node\n",
|
||||
"\n",
|
||||
"def bst_delete(root, name):\n",
|
||||
" parent = None\n",
|
||||
" current = root\n",
|
||||
" while current is not None and current['name'] != name:\n",
|
||||
" parent = current\n",
|
||||
" if name < current['name']:\n",
|
||||
" current = current['left']\n",
|
||||
" else:\n",
|
||||
" current = current['right']\n",
|
||||
" if current is None:\n",
|
||||
" return root\n",
|
||||
" \n",
|
||||
" if current['left'] is None and current['right'] is None:\n",
|
||||
" if parent is None:\n",
|
||||
" return None\n",
|
||||
" if parent['left'] is current:\n",
|
||||
" parent['left'] = None\n",
|
||||
" else:\n",
|
||||
" parent['right'] = None\n",
|
||||
" return root\n",
|
||||
" \n",
|
||||
" if current['left'] is None:\n",
|
||||
" if parent is None:\n",
|
||||
" return current['right']\n",
|
||||
" if parent['left'] is current:\n",
|
||||
" parent['left'] = current['right']\n",
|
||||
" else:\n",
|
||||
" parent['right'] = current['right']\n",
|
||||
" return root\n",
|
||||
" if current['right'] is None:\n",
|
||||
" if parent is None:\n",
|
||||
" return current['left']\n",
|
||||
" if parent['left'] is current:\n",
|
||||
" parent['left'] = current['left']\n",
|
||||
" else:\n",
|
||||
" parent['right'] = current['left']\n",
|
||||
" return root\n",
|
||||
" \n",
|
||||
" succ_parent = current\n",
|
||||
" succ = current['right']\n",
|
||||
" while succ['left'] is not None:\n",
|
||||
" succ_parent = succ\n",
|
||||
" succ = succ['left']\n",
|
||||
" current['name'] = succ['name']\n",
|
||||
" current['phone'] = succ['phone']\n",
|
||||
" if succ_parent['left'] is succ:\n",
|
||||
" succ_parent['left'] = succ['right']\n",
|
||||
" else:\n",
|
||||
" succ_parent['right'] = succ['right']\n",
|
||||
" return root\n",
|
||||
"\n",
|
||||
"# Генерация данных \n",
|
||||
"def generate_records(N):\n",
|
||||
" records = []\n",
|
||||
" for i in range(N):\n",
|
||||
" name = f\"User_{i:05d}\"\n",
|
||||
" phone = f\"+7-999-{random.randint(1000000, 9999999)}\"\n",
|
||||
" records.append((name, phone))\n",
|
||||
" return records\n",
|
||||
"\n",
|
||||
"# Замеры\n",
|
||||
"REPEATS = 5\n",
|
||||
"N = 10000\n",
|
||||
"\n",
|
||||
"def measure_insert(struct, records, repeats=REPEATS):\n",
|
||||
" times = []\n",
|
||||
" for _ in range(repeats):\n",
|
||||
" if struct == 'll':\n",
|
||||
" head = None\n",
|
||||
" start = time.perf_counter()\n",
|
||||
" for name, phone in records:\n",
|
||||
" head = ll_insert(head, name, phone)\n",
|
||||
" end = time.perf_counter()\n",
|
||||
" elif struct == 'ht':\n",
|
||||
" buckets = ht_create(2000)\n",
|
||||
" start = time.perf_counter()\n",
|
||||
" for name, phone in records:\n",
|
||||
" ht_insert(buckets, name, phone)\n",
|
||||
" end = time.perf_counter()\n",
|
||||
" elif struct == 'bst':\n",
|
||||
" root = None\n",
|
||||
" start = time.perf_counter()\n",
|
||||
" for name, phone in records:\n",
|
||||
" root = bst_insert(root, name, phone)\n",
|
||||
" end = time.perf_counter()\n",
|
||||
" times.append(end - start)\n",
|
||||
" return times\n",
|
||||
"\n",
|
||||
"def build_structure(struct, records):\n",
|
||||
" if struct == 'll':\n",
|
||||
" head = None\n",
|
||||
" for name, phone in records:\n",
|
||||
" head = ll_insert(head, name, phone)\n",
|
||||
" return head\n",
|
||||
" elif struct == 'ht':\n",
|
||||
" buckets = ht_create(2000)\n",
|
||||
" for name, phone in records:\n",
|
||||
" ht_insert(buckets, name, phone)\n",
|
||||
" return buckets\n",
|
||||
" elif struct == 'bst':\n",
|
||||
" root = None\n",
|
||||
" for name, phone in records:\n",
|
||||
" root = bst_insert(root, name, phone)\n",
|
||||
" return root\n",
|
||||
"\n",
|
||||
"def measure_find_on_structure(struct, structure, records, repeats=REPEATS):\n",
|
||||
" times = []\n",
|
||||
" N = len(records)\n",
|
||||
" for _ in range(repeats):\n",
|
||||
" indices = random.sample(range(N), 100)\n",
|
||||
" exist = [records[i][0] for i in indices]\n",
|
||||
" missing = [f\"None_{i}\" for i in range(10)]\n",
|
||||
" search = exist + missing\n",
|
||||
" start = time.perf_counter()\n",
|
||||
" if struct == 'll':\n",
|
||||
" for name in search:\n",
|
||||
" ll_find(structure, name)\n",
|
||||
" elif struct == 'ht':\n",
|
||||
" for name in search:\n",
|
||||
" ht_find(structure, name)\n",
|
||||
" elif struct == 'bst':\n",
|
||||
" for name in search:\n",
|
||||
" bst_find(structure, name)\n",
|
||||
" times.append(time.perf_counter() - start)\n",
|
||||
" return times\n",
|
||||
"\n",
|
||||
"def measure_delete_on_structure(struct, records, repeats=REPEATS):\n",
|
||||
" times = []\n",
|
||||
" N = len(records)\n",
|
||||
" for _ in range(repeats):\n",
|
||||
" indices = random.sample(range(N), 50)\n",
|
||||
" del_names = [records[i][0] for i in indices]\n",
|
||||
" if struct == 'll':\n",
|
||||
" head = None\n",
|
||||
" for name, phone in records:\n",
|
||||
" head = ll_insert(head, name, phone)\n",
|
||||
" start = time.perf_counter()\n",
|
||||
" for name in del_names:\n",
|
||||
" head = ll_delete(head, name)\n",
|
||||
" end = time.perf_counter()\n",
|
||||
" elif struct == 'ht':\n",
|
||||
" buckets = ht_create(2000)\n",
|
||||
" for name, phone in records:\n",
|
||||
" ht_insert(buckets, name, phone)\n",
|
||||
" start = time.perf_counter()\n",
|
||||
" for name in del_names:\n",
|
||||
" ht_delete(buckets, name)\n",
|
||||
" end = time.perf_counter()\n",
|
||||
" elif struct == 'bst':\n",
|
||||
" root = None\n",
|
||||
" for name, phone in records:\n",
|
||||
" root = bst_insert(root, name, phone)\n",
|
||||
" start = time.perf_counter()\n",
|
||||
" for name in del_names:\n",
|
||||
" root = bst_delete(root, name)\n",
|
||||
" end = time.perf_counter()\n",
|
||||
" times.append(end - start)\n",
|
||||
" return times\n",
|
||||
"\n",
|
||||
"# Основная функция \n",
|
||||
"def main():\n",
|
||||
" print(\"Генерация данных...\")\n",
|
||||
" records = generate_records(N)\n",
|
||||
" random.shuffle(records) # случайный порядок\n",
|
||||
" records_sorted = sorted(records, key=lambda x: x[0]) # отсортированный\n",
|
||||
"\n",
|
||||
" results = [] # для CSV\n",
|
||||
" struct_names = {'ll': 'Связного списка', 'ht': 'Хеш-таблицы', 'bst': 'Двоичного дерева поиска'}\n",
|
||||
" mode_names = {'shuffled': 'случайный', 'sorted': 'отсортированный'}\n",
|
||||
" op_names = {'insert': 'Вставка всех записей', 'find': 'Поиск записей', 'delete': 'Удаление записей'}\n",
|
||||
"\n",
|
||||
" # для графиков\n",
|
||||
" insert_sh = {} # {struct: [times]}\n",
|
||||
" insert_so = {}\n",
|
||||
" find_sh = {}\n",
|
||||
" find_so = {}\n",
|
||||
" delete_sh = {}\n",
|
||||
" delete_so = {}\n",
|
||||
"\n",
|
||||
" # Вставка \n",
|
||||
" for struct in ['ll', 'ht', 'bst']:\n",
|
||||
" print(f\"\\nИзмерение вставки для {struct_names[struct]}...\")\n",
|
||||
" times_sh = measure_insert(struct, records)\n",
|
||||
" times_so = measure_insert(struct, records_sorted)\n",
|
||||
" insert_sh[struct] = times_sh\n",
|
||||
" insert_so[struct] = times_so\n",
|
||||
" print(f\" случайный: {[round(t,6) for t in times_sh]}, среднее = {sum(times_sh)/len(times_sh):.6f}\")\n",
|
||||
" print(f\" отсортированный: {[round(t,6) for t in times_so]}, среднее = {sum(times_so)/len(times_so):.6f}\")\n",
|
||||
" results.append([struct_names[struct], mode_names['shuffled'], op_names['insert'], sum(times_sh)/len(times_sh)] + times_sh)\n",
|
||||
" results.append([struct_names[struct], mode_names['sorted'], op_names['insert'], sum(times_so)/len(times_so)] + times_so)\n",
|
||||
"\n",
|
||||
" # Поиск \n",
|
||||
" for struct in ['ll', 'ht', 'bst']:\n",
|
||||
" print(f\"\\nПоиск для {struct_names[struct]} на случайных данных...\")\n",
|
||||
" structure_sh = build_structure(struct, records)\n",
|
||||
" times_find_sh = measure_find_on_structure(struct, structure_sh, records)\n",
|
||||
" find_sh[struct] = times_find_sh\n",
|
||||
" print(f\" случайный: {[round(t,6) for t in times_find_sh]}, среднее = {sum(times_find_sh)/len(times_find_sh):.6f}\")\n",
|
||||
" results.append([struct_names[struct], mode_names['shuffled'], op_names['find'], sum(times_find_sh)/len(times_find_sh)] + times_find_sh)\n",
|
||||
"\n",
|
||||
" print(f\"Поиск для {struct_names[struct]} на отсортированных данных...\")\n",
|
||||
" structure_so = build_structure(struct, records_sorted)\n",
|
||||
" times_find_so = measure_find_on_structure(struct, structure_so, records_sorted)\n",
|
||||
" find_so[struct] = times_find_so\n",
|
||||
" print(f\" отсортированный: {[round(t,6) for t in times_find_so]}, среднее = {sum(times_find_so)/len(times_find_so):.6f}\")\n",
|
||||
" results.append([struct_names[struct], mode_names['sorted'], op_names['find'], sum(times_find_so)/len(times_find_so)] + times_find_so)\n",
|
||||
"\n",
|
||||
" # Удаление \n",
|
||||
" for struct in ['ll', 'ht', 'bst']:\n",
|
||||
" print(f\"\\nУдаление для {struct_names[struct]} на случайных данных...\")\n",
|
||||
" times_del_sh = measure_delete_on_structure(struct, records)\n",
|
||||
" delete_sh[struct] = times_del_sh\n",
|
||||
" print(f\" случайный: {[round(t,6) for t in times_del_sh]}, среднее = {sum(times_del_sh)/len(times_del_sh):.6f}\")\n",
|
||||
" results.append([struct_names[struct], mode_names['shuffled'], op_names['delete'], sum(times_del_sh)/len(times_del_sh)] + times_del_sh)\n",
|
||||
"\n",
|
||||
" print(f\"Удаление для {struct_names[struct]} на отсортированных данных...\")\n",
|
||||
" times_del_so = measure_delete_on_structure(struct, records_sorted)\n",
|
||||
" delete_so[struct] = times_del_so\n",
|
||||
" print(f\" отсортированный: {[round(t,6) for t in times_del_so]}, среднее = {sum(times_del_so)/len(times_del_so):.6f}\")\n",
|
||||
" results.append([struct_names[struct], mode_names['sorted'], op_names['delete'], sum(times_del_so)/len(times_del_so)] + times_del_so)\n",
|
||||
"\n",
|
||||
" # Сохраняем CSV\n",
|
||||
" with open(\"phonebook_results.csv\", \"w\", newline=\"\", encoding=\"utf-8\") as f:\n",
|
||||
" writer = csv.writer(f)\n",
|
||||
" writer.writerow(['Структура', 'Режим', 'Операция', 'Среднее', 'Замер1', 'Замер2', 'Замер3', 'Замер4', 'Замер5'])\n",
|
||||
" writer.writerows(results)\n",
|
||||
"\n",
|
||||
" # Построение графиков \n",
|
||||
" try:\n",
|
||||
" # График вставки\n",
|
||||
" fig1, ax1 = plt.subplots(figsize=(10,6))\n",
|
||||
" x = np.arange(3)\n",
|
||||
" width = 0.35\n",
|
||||
" means_sh = [sum(insert_sh[s])/len(insert_sh[s]) for s in ['ll','ht','bst']]\n",
|
||||
" means_so = [sum(insert_so[s])/len(insert_so[s]) for s in ['ll','ht','bst']]\n",
|
||||
" rects1 = ax1.bar(x - width/2, means_sh, width, label='Случайный порядок', color='skyblue')\n",
|
||||
" rects2 = ax1.bar(x + width/2, means_so, width, label='Отсортированный порядок', color='salmon')\n",
|
||||
" ax1.set_ylabel('Время (сек)')\n",
|
||||
" ax1.set_title('Вставка всех записей')\n",
|
||||
" ax1.set_xticks(x)\n",
|
||||
" ax1.set_xticklabels(['Связный список', 'Хеш-таблица', 'Двоичное дерево'])\n",
|
||||
" ax1.legend()\n",
|
||||
" ax1.set_yscale('log')\n",
|
||||
" for rect in rects1 + rects2:\n",
|
||||
" h = rect.get_height()\n",
|
||||
" ax1.annotate(f'{h:.3f}', xy=(rect.get_x()+rect.get_width()/2, h),\n",
|
||||
" xytext=(0,3), textcoords=\"offset points\", ha='center', va='bottom', fontsize=8)\n",
|
||||
" plt.tight_layout()\n",
|
||||
" plt.savefig('insert_comparison.png')\n",
|
||||
" plt.show()\n",
|
||||
"\n",
|
||||
" # График поиска\n",
|
||||
" fig2, ax2 = plt.subplots(figsize=(10,6))\n",
|
||||
" means_find_sh = [sum(find_sh[s])/len(find_sh[s]) for s in ['ll','ht','bst']]\n",
|
||||
" means_find_so = [sum(find_so[s])/len(find_so[s]) for s in ['ll','ht','bst']]\n",
|
||||
" rects1 = ax2.bar(x - width/2, means_find_sh, width, label='Случайный порядок', color='skyblue')\n",
|
||||
" rects2 = ax2.bar(x + width/2, means_find_so, width, label='Отсортированный порядок', color='salmon')\n",
|
||||
" ax2.set_ylabel('Время (сек)')\n",
|
||||
" ax2.set_title('Поиск (100 существующих + 10 отсутствующих)')\n",
|
||||
" ax2.set_xticks(x)\n",
|
||||
" ax2.set_xticklabels(['Связный список', 'Хеш-таблица', 'Двоичное дерево'])\n",
|
||||
" ax2.legend()\n",
|
||||
" for rect in rects1 + rects2:\n",
|
||||
" h = rect.get_height()\n",
|
||||
" ax2.annotate(f'{h:.5f}', xy=(rect.get_x()+rect.get_width()/2, h),\n",
|
||||
" xytext=(0,3), textcoords=\"offset points\", ha='center', va='bottom', fontsize=8)\n",
|
||||
" plt.tight_layout()\n",
|
||||
" plt.savefig('find_comparison.png')\n",
|
||||
" plt.show()\n",
|
||||
"\n",
|
||||
" # График удаления \n",
|
||||
" fig3, ax3 = plt.subplots(figsize=(10,6))\n",
|
||||
" means_del_sh = [sum(delete_sh[s])/len(delete_sh[s]) for s in ['ll','ht','bst']]\n",
|
||||
" means_del_so = [sum(delete_so[s])/len(delete_so[s]) for s in ['ll','ht','bst']]\n",
|
||||
" rects1 = ax3.bar(x - width/2, means_del_sh, width, label='Случайный порядок', color='skyblue')\n",
|
||||
" rects2 = ax3.bar(x + width/2, means_del_so, width, label='Отсортированный порядок', color='salmon')\n",
|
||||
" ax3.set_ylabel('Время (сек)')\n",
|
||||
" ax3.set_title('Удаление 50 случайных записей')\n",
|
||||
" ax3.set_xticks(x)\n",
|
||||
" ax3.set_xticklabels(['Связный список', 'Хеш-таблица', 'Двоичное дерево'])\n",
|
||||
" ax3.legend()\n",
|
||||
" for rect in rects1 + rects2:\n",
|
||||
" h = rect.get_height()\n",
|
||||
" ax3.annotate(f'{h:.5f}', xy=(rect.get_x()+rect.get_width()/2, h),\n",
|
||||
" xytext=(0,3), textcoords=\"offset points\", ha='center', va='bottom', fontsize=8)\n",
|
||||
" plt.tight_layout()\n",
|
||||
" plt.savefig('delete_comparison.png')\n",
|
||||
" plt.show()\n",
|
||||
"\n",
|
||||
" print(\"Графики сохранены: insert_comparison.png, find_comparison.png, delete_comparison.png\")\n",
|
||||
" except Exception as e:\n",
|
||||
" print(f\"Не удалось построить графики: {e}\")\n",
|
||||
" # Графики замеров\n",
|
||||
" try:\n",
|
||||
" def plot_attempts(data_sh, data_so, op_name):\n",
|
||||
" fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))\n",
|
||||
" # случайный порядок\n",
|
||||
" for struct, label, color, marker in [('ll','LinkedList','red','o'), ('ht','HashTable','green','s'), ('bst','BST','blue','^')]:\n",
|
||||
" times = data_sh[struct]\n",
|
||||
" x = range(1, len(times)+1)\n",
|
||||
" ax1.plot(x, times, marker=marker, color=color, label=label, linestyle='--', linewidth=1)\n",
|
||||
" ax1.scatter(x, times, color=color, s=60, zorder=5)\n",
|
||||
" ax1.set_xlabel('Номер попытки')\n",
|
||||
" ax1.set_ylabel('Время (сек)')\n",
|
||||
" ax1.set_title(f'{op_name} – случайный порядок')\n",
|
||||
" ax1.legend()\n",
|
||||
" ax1.grid(True, linestyle=':', alpha=0.7)\n",
|
||||
" # отсортированный порядок\n",
|
||||
" for struct, label, color, marker in [('ll','LinkedList','red','o'), ('ht','HashTable','green','s'), ('bst','BST','blue','^')]:\n",
|
||||
" times = data_so[struct]\n",
|
||||
" x = range(1, len(times)+1)\n",
|
||||
" ax2.plot(x, times, marker=marker, color=color, label=label, linestyle='--', linewidth=1)\n",
|
||||
" ax2.scatter(x, times, color=color, s=60, zorder=5)\n",
|
||||
" ax2.set_xlabel('Номер попытки')\n",
|
||||
" ax2.set_ylabel('Время (сек)')\n",
|
||||
" ax2.set_title(f'{op_name} – отсортированный порядок')\n",
|
||||
" ax2.legend()\n",
|
||||
" ax2.grid(True, linestyle=':', alpha=0.7)\n",
|
||||
" plt.tight_layout()\n",
|
||||
" plt.savefig(f'{op_name}_5attempts.png')\n",
|
||||
" plt.show()\n",
|
||||
" \n",
|
||||
" plot_attempts(insert_sh, insert_so, 'insert')\n",
|
||||
" plot_attempts(find_sh, find_so, 'find')\n",
|
||||
" plot_attempts(delete_sh, delete_so, 'delete')\n",
|
||||
" print(\"Дополнительные графики сохранены: insert_5attempts.png, find_5attempts.png, delete_5attempts.png\")\n",
|
||||
" except Exception as e:\n",
|
||||
" print(f\"Не удалось построить дополнительные графики: {e}\")\n",
|
||||
"\n",
|
||||
"if __name__ == \"__main__\":\n",
|
||||
" sys.setrecursionlimit(20000)\n",
|
||||
" main()\n",
|
||||
"\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "cead201d-1150-463f-9ff3-a4bed6f7fc03",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.13.5"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
13
filippovavm/docs/laba2/all_results.csv
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
maze,algorithm,avg_time_ms,avg_visited,avg_path_len
|
||||
tiny.txt,BFSStrategy,0.03461998421698809,6.0,4.0
|
||||
tiny.txt,DFSStrategy,0.021119997836649418,5.0,6.0
|
||||
tiny.txt,AStarStrategy,0.05429997108876705,6.0,4.0
|
||||
medium.txt,BFSStrategy,0.1677999971434474,39.0,16.0
|
||||
medium.txt,DFSStrategy,0.18643999937921762,44.0,18.0
|
||||
medium.txt,AStarStrategy,0.2677599899470806,39.0,16.0
|
||||
large.txt,BFSStrategy,12.41911998949945,2500.0,99.0
|
||||
large.txt,DFSStrategy,127.24694001954049,2450.0,2451.0
|
||||
large.txt,AStarStrategy,17.408600030466914,2500.0,99.0
|
||||
empty.txt,BFSStrategy,0.33364000264555216,100.0,100.0
|
||||
empty.txt,DFSStrategy,0.37696000654250383,99.0,100.0
|
||||
empty.txt,AStarStrategy,0.4786999896168709,100.0,100.0
|
||||
|
99
filippovavm/docs/laba2/empty.txt
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
S E
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
50
filippovavm/docs/laba2/large.txt
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
S E
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
48
filippovavm/docs/laba2/medium.txt
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
##################################################
|
||||
#S #
|
||||
# ############## ####################### #
|
||||
# # # # # #
|
||||
# # ####### # # ################ # # #
|
||||
# # # # # # # # # # #
|
||||
# # # ### # # # # ########### # # # #
|
||||
# # # # # # # # # # # # # # #
|
||||
# # # # # # # # # # ###### # # # # #
|
||||
# # # # # # # # # # # # # # # # #
|
||||
# # # # # # # # # # # ## # # # # # #
|
||||
# # # # # # # # # # # # # # # # #
|
||||
# # # # # # # # # # ###### # # # # #
|
||||
# # # # # # # # # # # # # # #
|
||||
# # # # # # # # # ########### # # # #
|
||||
# # # # # # # # # # # # #
|
||||
# # # # # # # # ################ # # #
|
||||
# # # # # # # # # #
|
||||
# # # # # # # ####################### #
|
||||
# # # # # # # #
|
||||
# # # # # # ###################################
|
||||
# # # # # # #
|
||||
# # # # # #######################################
|
||||
# # # # # #
|
||||
# # # # #########################################
|
||||
# # # # #
|
||||
# # # ###########################################
|
||||
# # # #
|
||||
# # #############################################
|
||||
# # #
|
||||
# ################################################
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
##################################################
|
||||
20
filippovavm/docs/laba2/no_exit.txt
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
####################
|
||||
#S #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
###################E
|
||||
BIN
filippovavm/docs/laba2/plot_empty.txt.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
filippovavm/docs/laba2/plot_large.txt.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
filippovavm/docs/laba2/plot_no_exit.txt.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
filippovavm/docs/laba2/plot_tiny.txt.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
filippovavm/docs/laba2/summary_comparison.png
Normal file
|
After Width: | Height: | Size: 53 KiB |
11
filippovavm/docs/laba2/tiny.txt
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
##########
|
||||
#S #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# E#
|
||||
##########
|
||||
631
filippovavm/docs/laba2/мп2_1.ipynb
Normal file
|
|
@ -0,0 +1,631 @@
|
|||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "332cd3ba-eb85-47e3-85cc-736843c10214",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Полная реализация поиска выхода из лабиринта (ООП + паттерны)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "3d027c2d-7827-4b2f-8c52-0632b97fb462",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Этап 1. Модель лабиринта (без паттернов)\n",
|
||||
"\n",
|
||||
"**Описание:** \n",
|
||||
"Создаются два класса: `Cell` (клетка) и `Maze` (лабиринт). \n",
|
||||
"`Cell` хранит координаты `(x, y)`, флаг `is_wall`, флаги `is_start`, `is_exit`, метод `is_passable()`. \n",
|
||||
"`Maze` содержит двумерный массив клеток, размеры, ссылки на старт и выход. \n",
|
||||
"Метод `get_neighbors(cell)` возвращает список проходимых соседей (вверх, вниз, влево, вправо). \n",
|
||||
"\n",
|
||||
"Этот этап — основа для всех последующих алгоритмов."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"id": "c35ca325-3402-4c1b-91d4-32f734d6d599",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import time\n",
|
||||
"import csv\n",
|
||||
"import heapq\n",
|
||||
"from collections import deque\n",
|
||||
"from abc import ABC, abstractmethod\n",
|
||||
"import matplotlib.pyplot as plt\n",
|
||||
"import pandas as pd\n",
|
||||
"from dataclasses import dataclass\n",
|
||||
"\n",
|
||||
"class Cell:\n",
|
||||
" def __init__(self, x, y, is_wall=False):\n",
|
||||
" self.x = x\n",
|
||||
" self.y = y\n",
|
||||
" self.is_wall = is_wall\n",
|
||||
" self.is_start = False\n",
|
||||
" self.is_exit = False\n",
|
||||
"\n",
|
||||
" def is_passable(self):\n",
|
||||
" return not self.is_wall\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class Maze:\n",
|
||||
" def __init__(self, width, height):\n",
|
||||
" self.width = width\n",
|
||||
" self.height = height\n",
|
||||
" self.cells = [[Cell(x, y) for x in range(width)] for y in range(height)]\n",
|
||||
" self.start = None\n",
|
||||
" self.exit = None\n",
|
||||
"\n",
|
||||
" def get_cell(self, x, y):\n",
|
||||
" if 0 <= x < self.width and 0 <= y < self.height:\n",
|
||||
" return self.cells[y][x]\n",
|
||||
" return None\n",
|
||||
"\n",
|
||||
" def get_neighbors(self, cell):\n",
|
||||
" neighbors = []\n",
|
||||
" for dx, dy in [(0, 1), (0, -1), (1, 0), (-1, 0)]:\n",
|
||||
" nx, ny = cell.x + dx, cell.y + dy\n",
|
||||
" nb = self.get_cell(nx, ny)\n",
|
||||
" if nb and nb.is_passable():\n",
|
||||
" neighbors.append(nb)\n",
|
||||
" return neighbors"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "9e10908b-e541-46e4-ad15-99555c9c5de3",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Этап 2. Загрузка лабиринта из файла – паттерн Builder\n",
|
||||
"\n",
|
||||
"**Описание:** \n",
|
||||
"Паттерн **Builder** отделяет конструирование сложного объекта (лабиринта) от его представления. \n",
|
||||
"Интерфейс `MazeBuilder` объявляет метод `build_from_file(filename)`. \n",
|
||||
"`TextFileMazeBuilder` реализует загрузку из текстового файла, где:\n",
|
||||
"- `#` – стена\n",
|
||||
"- пробел (или любой другой символ, кроме `#`, `S`, `E`) – проход\n",
|
||||
"- `S` – старт\n",
|
||||
"- `E` – выход\n",
|
||||
"\n",
|
||||
"Процесс: чтение строк, определение размеров, создание клеток, установка флагов. \n",
|
||||
"Builder скрывает детали парсинга и валидации."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"id": "eab1c38a-aef1-4de7-96b3-24df9b6becb7",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"class MazeBuilder(ABC):\n",
|
||||
" @abstractmethod\n",
|
||||
" def build_from_file(self, filename):\n",
|
||||
" pass\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class TextFileMazeBuilder(MazeBuilder):\n",
|
||||
" def build_from_file(self, filename):\n",
|
||||
" with open(filename, 'r', encoding='utf-8') as f:\n",
|
||||
" lines = [line.rstrip('\\n') for line in f.readlines()]\n",
|
||||
" height = len(lines)\n",
|
||||
" width = max(len(line) for line in lines)\n",
|
||||
" maze = Maze(width, height)\n",
|
||||
"\n",
|
||||
" for y, line in enumerate(lines):\n",
|
||||
" for x, ch in enumerate(line):\n",
|
||||
" cell = maze.get_cell(x, y)\n",
|
||||
" if ch == '#':\n",
|
||||
" cell.is_wall = True\n",
|
||||
" elif ch == 'S':\n",
|
||||
" cell.is_start = True\n",
|
||||
" maze.start = cell\n",
|
||||
" elif ch == 'E':\n",
|
||||
" cell.is_exit = True\n",
|
||||
" maze.exit = cell\n",
|
||||
" else:\n",
|
||||
" cell.is_wall = False\n",
|
||||
" return maze"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "791f75f0-ea40-496d-ad38-c827e444e6ae",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Этап 3. Стратегии поиска пути – паттерн Strategy\n",
|
||||
"\n",
|
||||
"**Описание:** \n",
|
||||
"Паттерн **Strategy** определяет семейство алгоритмов, инкапсулирует каждый и делает их взаимозаменяемыми. \n",
|
||||
"Интерфейс `PathFindingStrategy` объявляет метод `find_path(maze, start, exit)`, возвращающий `(path, visited_count)`. \n",
|
||||
"\n",
|
||||
"Реализованы три стратегии:\n",
|
||||
"\n",
|
||||
"1. **BFS (поиск в ширину)** \n",
|
||||
" - Использует очередь `deque`. \n",
|
||||
" - Гарантирует нахождение кратчайшего пути по числу шагов. \n",
|
||||
" - Сложность O(V+E). \n",
|
||||
" - Подходит для небольших и средних лабиринтов, где важна оптимальность.\n",
|
||||
"\n",
|
||||
"2. **DFS (поиск в глубину)** \n",
|
||||
" - Использует стек (список). \n",
|
||||
" - Не гарантирует кратчайший путь, но может быть быстрее на определённых конфигурациях. \n",
|
||||
" - Сложность O(V+E). \n",
|
||||
" - Полезен, когда нужно быстро найти любой путь.\n",
|
||||
"\n",
|
||||
"3. **A\\*** (A-star) \n",
|
||||
" - Использует приоритетную очередь (heapq) и эвристику. \n",
|
||||
" - Эвристика – манхэттенское расстояние: \n",
|
||||
" $[\n",
|
||||
" h(n) = |x_n - x_{exit}| + |y_n - y_{exit}|\n",
|
||||
" $] \n",
|
||||
" - Оценка стоимости пути: \\( f(n) = g(n) + h(n) \\), где \\( g(n) \\) – реальная стоимость от старта. \n",
|
||||
" - Гарантирует оптимальность при допустимой эвристике. \n",
|
||||
" - На практике быстрее BFS за счёт целенаправленного поиска."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"id": "fe37e65c-7f33-458f-9ead-37838b60316a",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"class PathFindingStrategy(ABC):\n",
|
||||
" @abstractmethod\n",
|
||||
" def find_path(self, maze, start, exit):\n",
|
||||
" pass\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class BFSStrategy(PathFindingStrategy):\n",
|
||||
" def find_path(self, maze, start, exit):\n",
|
||||
" visited = set()\n",
|
||||
" if start == exit:\n",
|
||||
" return [start], 1\n",
|
||||
" queue = deque([start])\n",
|
||||
" visited.add(start)\n",
|
||||
" parent = {start: None}\n",
|
||||
" while queue:\n",
|
||||
" current = queue.popleft()\n",
|
||||
" for nb in maze.get_neighbors(current):\n",
|
||||
" if nb not in visited:\n",
|
||||
" visited.add(nb)\n",
|
||||
" parent[nb] = current\n",
|
||||
" if nb == exit:\n",
|
||||
" path = []\n",
|
||||
" node = nb\n",
|
||||
" while node is not None:\n",
|
||||
" path.append(node)\n",
|
||||
" node = parent[node]\n",
|
||||
" path.reverse()\n",
|
||||
" return path, len(visited)\n",
|
||||
" queue.append(nb)\n",
|
||||
" return [], len(visited)\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class DFSStrategy(PathFindingStrategy):\n",
|
||||
" def find_path(self, maze, start, exit):\n",
|
||||
" visited = set()\n",
|
||||
" stack = [(start, [start])]\n",
|
||||
" while stack:\n",
|
||||
" current, path = stack.pop()\n",
|
||||
" if current == exit:\n",
|
||||
" return path, len(visited)\n",
|
||||
" visited.add(current)\n",
|
||||
" for nb in maze.get_neighbors(current):\n",
|
||||
" if nb not in visited:\n",
|
||||
" stack.append((nb, path + [nb]))\n",
|
||||
" return [], len(visited)\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class AStarStrategy(PathFindingStrategy):\n",
|
||||
" def heuristic(self, cell, exit):\n",
|
||||
" return abs(cell.x - exit.x) + abs(cell.y - exit.y)\n",
|
||||
"\n",
|
||||
" def find_path(self, maze, start, exit):\n",
|
||||
" open_set = []\n",
|
||||
" counter = 0\n",
|
||||
" heapq.heappush(open_set, (0, counter, start))\n",
|
||||
" counter += 1\n",
|
||||
" came_from = {}\n",
|
||||
" g_score = {start: 0}\n",
|
||||
" f_score = {start: self.heuristic(start, exit)}\n",
|
||||
" visited = set()\n",
|
||||
" while open_set:\n",
|
||||
" _, _, current = heapq.heappop(open_set)\n",
|
||||
" visited.add(current)\n",
|
||||
" if current == exit:\n",
|
||||
" path = []\n",
|
||||
" node = current\n",
|
||||
" while node in came_from:\n",
|
||||
" path.append(node)\n",
|
||||
" node = came_from[node]\n",
|
||||
" path.append(start)\n",
|
||||
" path.reverse()\n",
|
||||
" return path, len(visited)\n",
|
||||
" for nb in maze.get_neighbors(current):\n",
|
||||
" tentative_g = g_score[current] + 1\n",
|
||||
" if tentative_g < g_score.get(nb, float('inf')):\n",
|
||||
" came_from[nb] = current\n",
|
||||
" g_score[nb] = tentative_g\n",
|
||||
" f = tentative_g + self.heuristic(nb, exit)\n",
|
||||
" heapq.heappush(open_set, (f, counter, nb))\n",
|
||||
" counter += 1\n",
|
||||
" return [], len(visited)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "6eeb7cd4-dade-40a0-b607-c08198b602ea",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Этап 4. Класс-оркестратор MazeSolver и статистика\n",
|
||||
"\n",
|
||||
"**Описание:** \n",
|
||||
"`MazeSolver` принимает лабиринт и стратегию. \n",
|
||||
"Метод `solve()` замеряет время выполнения (`time.perf_counter()`), вызывает стратегию и возвращает статистику `SearchStats`: \n",
|
||||
"- `time_ms` – время в миллисекундах \n",
|
||||
"- `visited_cells` – количество посещённых клеток \n",
|
||||
"- `path_length` – длина пути \n",
|
||||
"- `algorithm` – имя алгоритма\n",
|
||||
"\n",
|
||||
"Класс также поддерживает паттерн **Observer** (будет добавлен в следующем этапе)."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"id": "5177c17f-6dd2-42d7-92b0-87471b5c22c1",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"@dataclass\n",
|
||||
"class SearchStats:\n",
|
||||
" time_ms: float\n",
|
||||
" visited_cells: int\n",
|
||||
" path_length: int\n",
|
||||
" algorithm: str\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class MazeSolver:\n",
|
||||
" def __init__(self, maze, strategy, observers=None):\n",
|
||||
" self.maze = maze\n",
|
||||
" self.strategy = strategy\n",
|
||||
" self.observers = observers if observers else []\n",
|
||||
"\n",
|
||||
" def attach(self, observer):\n",
|
||||
" self.observers.append(observer)\n",
|
||||
"\n",
|
||||
" def detach(self, observer):\n",
|
||||
" self.observers.remove(observer)\n",
|
||||
"\n",
|
||||
" def notify(self, event_type, data=None):\n",
|
||||
" for obs in self.observers:\n",
|
||||
" obs.update(event_type, data)\n",
|
||||
"\n",
|
||||
" def set_strategy(self, strategy):\n",
|
||||
" self.strategy = strategy\n",
|
||||
"\n",
|
||||
" def solve(self):\n",
|
||||
" if self.maze.start is None or self.maze.exit is None:\n",
|
||||
" raise ValueError(\"Лабиринт не имеет старта или выхода\")\n",
|
||||
" self.notify(\"search_start\")\n",
|
||||
" start_time = time.perf_counter()\n",
|
||||
" path, visited = self.strategy.find_path(self.maze, self.maze.start, self.maze.exit)\n",
|
||||
" end_time = time.perf_counter()\n",
|
||||
" if path:\n",
|
||||
" self.notify(\"path_found\", len(path))\n",
|
||||
" else:\n",
|
||||
" self.notify(\"no_path\")\n",
|
||||
" stats = SearchStats(\n",
|
||||
" time_ms=(end_time - start_time) * 1000,\n",
|
||||
" visited_cells=visited,\n",
|
||||
" path_length=len(path),\n",
|
||||
" algorithm=self.strategy.__class__.__name__\n",
|
||||
" )\n",
|
||||
" return path, stats"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "87114e56-ac0f-4227-b081-b7f84ebecaa3",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Этап 5. Визуализация и пошаговое управление – паттерны Observer и Command\n",
|
||||
"\n",
|
||||
"**Описание:** \n",
|
||||
"- **Observer** (`ConsoleLogger`) подписывается на события `MazeSolver` и выводит сообщения о начале поиска, нахождении пути или его отсутствии. \n",
|
||||
"- **Command** – интерфейс с методами `execute()` и `undo()`. \n",
|
||||
" `MoveCommand` реализует перемещение игрока на одну клетку и сохраняет предыдущую позицию для отмены. \n",
|
||||
" `Player` хранит текущую клетку. \n",
|
||||
"- Демонстрация: после нахождения пути для `tiny.txt` алгоритм BFS с наблюдателем выводит логи, затем выполняется последовательное перемещение по найденному пути с возможностью отмены последнего шага (undo)."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"id": "171e638f-f3ce-4278-902f-37375c33a94b",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"class Observer(ABC):\n",
|
||||
" @abstractmethod\n",
|
||||
" def update(self, event_type, data=None):\n",
|
||||
" pass\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class ConsoleLogger(Observer):\n",
|
||||
" def update(self, event_type, data=None):\n",
|
||||
" if event_type == \"search_start\":\n",
|
||||
" print(f\"[LOG] Поиск пути начат\")\n",
|
||||
" elif event_type == \"path_found\":\n",
|
||||
" print(f\"[LOG] Путь найден! Длина: {data}\")\n",
|
||||
" elif event_type == \"no_path\":\n",
|
||||
" print(\"[LOG] Путь не найден\")\n",
|
||||
" elif event_type == \"step\":\n",
|
||||
" print(f\"[LOG] Шаг: {data}\")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class Command(ABC):\n",
|
||||
" @abstractmethod\n",
|
||||
" def execute(self):\n",
|
||||
" pass\n",
|
||||
"\n",
|
||||
" @abstractmethod\n",
|
||||
" def undo(self):\n",
|
||||
" pass\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class MoveCommand(Command):\n",
|
||||
" def __init__(self, player, direction, maze):\n",
|
||||
" self.player = player\n",
|
||||
" self.direction = direction\n",
|
||||
" self.maze = maze\n",
|
||||
" self.prev_pos = None\n",
|
||||
"\n",
|
||||
" def execute(self):\n",
|
||||
" self.prev_pos = self.player.current_cell\n",
|
||||
" dx, dy = self.direction\n",
|
||||
" nx, ny = self.player.current_cell.x + dx, self.player.current_cell.y + dy\n",
|
||||
" new_cell = self.maze.get_cell(nx, ny)\n",
|
||||
" if new_cell and new_cell.is_passable():\n",
|
||||
" self.player.current_cell = new_cell\n",
|
||||
" return True\n",
|
||||
" return False\n",
|
||||
"\n",
|
||||
" def undo(self):\n",
|
||||
" if self.prev_pos:\n",
|
||||
" self.player.current_cell = self.prev_pos\n",
|
||||
" return True\n",
|
||||
" return False\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class Player:\n",
|
||||
" def __init__(self, start_cell):\n",
|
||||
" self.current_cell = start_cell\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def interactive_move_demo(maze, path):\n",
|
||||
" if not path:\n",
|
||||
" print(\"Путь не найден, демонстрация движения невозможна.\")\n",
|
||||
" return\n",
|
||||
" player = Player(maze.start)\n",
|
||||
" command_history = []\n",
|
||||
" print(\"\\n=== Интерактивное движение по найденному пути ===\")\n",
|
||||
" print(\"Текущая позиция: старт\")\n",
|
||||
" for step, cell in enumerate(path):\n",
|
||||
" if cell == maze.start:\n",
|
||||
" continue\n",
|
||||
" prev = path[step-1]\n",
|
||||
" dx = cell.x - prev.x\n",
|
||||
" dy = cell.y - prev.y\n",
|
||||
" cmd = MoveCommand(player, (dx, dy), maze)\n",
|
||||
" cmd.execute()\n",
|
||||
" command_history.append(cmd)\n",
|
||||
" print(f\"Шаг {step}: перемещение на ({dx},{dy}), позиция ({player.current_cell.x},{player.current_cell.y})\")\n",
|
||||
" if cell == maze.exit:\n",
|
||||
" print(\"Достигнут выход!\")\n",
|
||||
" break\n",
|
||||
" if command_history:\n",
|
||||
" print(\"\\n=== Демонстрация отмены последнего шага (undo) ===\")\n",
|
||||
" cmd = command_history[-1]\n",
|
||||
" cmd.undo()\n",
|
||||
" print(f\"Отменён последний шаг, позиция: ({player.current_cell.x},{player.current_cell.y})\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "1d67bc06-60f8-4b1d-9018-0b6cb8a8df74",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Этап 6. Экспериментальная часть\n",
|
||||
"\n",
|
||||
"**Описание:** \n",
|
||||
"Подготавливаются 5 лабиринтов разной сложности (файлы `tiny.txt`, `medium.txt`, `large.txt`, `empty.txt`, `no_exit.txt`). \n",
|
||||
"Для каждого лабиринта и каждой стратегии выполняется 5 запусков `solve()`, усредняются: \n",
|
||||
"- время выполнения (мс) \n",
|
||||
"- количество посещённых клеток \n",
|
||||
"- длина найденного пути \n",
|
||||
"\n",
|
||||
"Результаты сохраняются в `all_results.csv`. \n",
|
||||
"Строятся столбчатые диаграммы для каждого лабиринта и общий график сравнения алгоритмов. \n",
|
||||
"\n",
|
||||
"Код также демонстрирует паттерны Observer (логирование) и Command (движение) на лабиринте `tiny.txt`."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"id": "996a2948-28ca-4f04-8571-b8ce694fe2a4",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"ename": "NameError",
|
||||
"evalue": "name 'BFSStrategy' is not defined",
|
||||
"output_type": "error",
|
||||
"traceback": [
|
||||
"\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
|
||||
"\u001b[1;31mNameError\u001b[0m Traceback (most recent call last)",
|
||||
"Cell \u001b[1;32mIn[1], line 32\u001b[0m\n\u001b[0;32m 24\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;18m__name__\u001b[39m \u001b[38;5;241m==\u001b[39m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m__main__\u001b[39m\u001b[38;5;124m\"\u001b[39m:\n\u001b[0;32m 25\u001b[0m maze_files \u001b[38;5;241m=\u001b[39m [\n\u001b[0;32m 26\u001b[0m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mtiny.txt\u001b[39m\u001b[38;5;124m\"\u001b[39m,\n\u001b[0;32m 27\u001b[0m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mmedium.txt\u001b[39m\u001b[38;5;124m\"\u001b[39m,\n\u001b[1;32m (...)\u001b[0m\n\u001b[0;32m 30\u001b[0m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mno_exit.txt\u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[0;32m 31\u001b[0m ]\n\u001b[1;32m---> 32\u001b[0m strategies \u001b[38;5;241m=\u001b[39m [BFSStrategy(), DFSStrategy(), AStarStrategy()]\n\u001b[0;32m 33\u001b[0m all_results \u001b[38;5;241m=\u001b[39m []\n\u001b[0;32m 34\u001b[0m logger \u001b[38;5;241m=\u001b[39m ConsoleLogger()\n",
|
||||
"\u001b[1;31mNameError\u001b[0m: name 'BFSStrategy' is not defined"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"def test_single_maze(filename, strategies, repeats=5):\n",
|
||||
" builder = TextFileMazeBuilder()\n",
|
||||
" maze = builder.build_from_file(filename)\n",
|
||||
" results = []\n",
|
||||
" for strategy in strategies:\n",
|
||||
" solver = MazeSolver(maze, strategy)\n",
|
||||
" times = []\n",
|
||||
" visits = []\n",
|
||||
" lengths = []\n",
|
||||
" for _ in range(repeats):\n",
|
||||
" _, stats = solver.solve()\n",
|
||||
" times.append(stats.time_ms)\n",
|
||||
" visits.append(stats.visited_cells)\n",
|
||||
" lengths.append(stats.path_length)\n",
|
||||
" results.append({\n",
|
||||
" 'algorithm': strategy.__class__.__name__,\n",
|
||||
" 'avg_time_ms': sum(times) / repeats,\n",
|
||||
" 'avg_visited': sum(visits) / repeats,\n",
|
||||
" 'avg_path_len': sum(lengths) / repeats\n",
|
||||
" })\n",
|
||||
" return results\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"if __name__ == \"__main__\":\n",
|
||||
" maze_files = [\n",
|
||||
" \"tiny.txt\",\n",
|
||||
" \"medium.txt\",\n",
|
||||
" \"large.txt\",\n",
|
||||
" \"empty.txt\",\n",
|
||||
" \"no_exit.txt\"\n",
|
||||
" ]\n",
|
||||
" strategies = [BFSStrategy(), DFSStrategy(), AStarStrategy()]\n",
|
||||
" all_results = []\n",
|
||||
" logger = ConsoleLogger()\n",
|
||||
"\n",
|
||||
" for maze_file in maze_files:\n",
|
||||
" print(f\"Загрузка лабиринта из {maze_file}...\")\n",
|
||||
" try:\n",
|
||||
" builder = TextFileMazeBuilder()\n",
|
||||
" maze = builder.build_from_file(maze_file)\n",
|
||||
" # Демонстрация Observer и Command для tiny.txt\n",
|
||||
" if maze_file == \"tiny.txt\":\n",
|
||||
" solver_with_observer = MazeSolver(maze, strategies[0], observers=[logger])\n",
|
||||
" path, _ = solver_with_observer.solve()\n",
|
||||
" interactive_move_demo(maze, path)\n",
|
||||
" results = test_single_maze(maze_file, strategies)\n",
|
||||
" for r in results:\n",
|
||||
" r['maze'] = maze_file\n",
|
||||
" all_results.append(r)\n",
|
||||
" print(f\"Результаты для {maze_file}:\")\n",
|
||||
" for r in results:\n",
|
||||
" print(f\" {r['algorithm']}: время = {r['avg_time_ms']:.3f} мс, \"\n",
|
||||
" f\"посещено = {r['avg_visited']:.1f}, длина пути = {r['avg_path_len']:.1f}\")\n",
|
||||
" except Exception as e:\n",
|
||||
" print(f\"Ошибка при обработке {maze_file}: {e}\")\n",
|
||||
"\n",
|
||||
" if all_results:\n",
|
||||
" with open('all_results.csv', 'w', newline='', encoding='utf-8') as f:\n",
|
||||
" writer = csv.DictWriter(f, fieldnames=['maze', 'algorithm', 'avg_time_ms', 'avg_visited', 'avg_path_len'])\n",
|
||||
" writer.writeheader()\n",
|
||||
" writer.writerows(all_results)\n",
|
||||
"\n",
|
||||
" df = pd.DataFrame(all_results)\n",
|
||||
" for maze in df['maze'].unique():\n",
|
||||
" subset = df[df['maze'] == maze]\n",
|
||||
" plt.figure()\n",
|
||||
" plt.bar(subset['algorithm'], subset['avg_time_ms'])\n",
|
||||
" plt.title(f'Сравнение алгоритмов на лабиринте {maze}')\n",
|
||||
" plt.ylabel('Среднее время (мс)')\n",
|
||||
" plt.savefig(f'plot_{maze}.png')\n",
|
||||
" plt.close()\n",
|
||||
"\n",
|
||||
" plt.figure(figsize=(10, 6))\n",
|
||||
" for alg in df['algorithm'].unique():\n",
|
||||
" subset = df[df['algorithm'] == alg]\n",
|
||||
" plt.plot(subset['maze'], subset['avg_time_ms'], marker='o', label=alg)\n",
|
||||
" plt.xlabel('Лабиринт')\n",
|
||||
" plt.ylabel('Среднее время (мс)')\n",
|
||||
" plt.title('Сравнение эффективности алгоритмов на разных лабиринтах')\n",
|
||||
" plt.legend()\n",
|
||||
" plt.grid(True)\n",
|
||||
" plt.savefig('summary_comparison.png')\n",
|
||||
" plt.show()\n",
|
||||
" else:\n",
|
||||
" print(\"Нет данных для построения графиков. Проверьте файлы лабиринтов.\")\n",
|
||||
"\n",
|
||||
" print(\"\\nЭксперимент завершён. Результаты сохранены в all_results.csv и графиках.\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "937ab5f6-e884-46f6-8d45-b152ca61e7b7",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Заключение\n",
|
||||
"\n",
|
||||
"В работе реализованы:\n",
|
||||
"- Классы `Cell` и `Maze` для моделирования лабиринта.\n",
|
||||
"- Паттерн **Builder** для загрузки лабиринтов из текстовых файлов.\n",
|
||||
"- Паттерн **Strategy** для трёх алгоритмов поиска: BFS, DFS, A*.\n",
|
||||
"- Паттерны **Observer** (логирование) и **Command** (управление с отменой) для визуализации и интерактивности.\n",
|
||||
"- Экспериментальная часть с замером времени, посещённых клеток, длины пути, сохранением результатов в CSV и построением графиков.\n",
|
||||
"\n",
|
||||
"Код полностью соответствует заданию и готов к использованию."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "1ddcb647-eb50-40aa-bc9a-cb76f8a14f23",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "a560e7bf-6b18-4018-9912-ea8da341e8a7",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "25793d80-f546-4270-ae7c-86c4898d6c32",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.13.5"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
375
filippovavm/docs/laba2/отчет2.ipynb
Normal file
|
|
@ -0,0 +1,375 @@
|
|||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "204f9862-9a51-47be-bb5b-fcb099f9f707",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Отчёт по лабораторной работе: Поиск выхода из лабиринта (ООП + паттерны)\n",
|
||||
"\n",
|
||||
"## 1. Описание задачи\n",
|
||||
"\n",
|
||||
"Разработать программу для загрузки лабиринта из файла, поиска пути от старта до выхода с возможностью выбора алгоритма и экспериментального сравнения алгоритмов. Требовалось применить минимум 3 паттерна проектирования GoF.\n",
|
||||
"\n",
|
||||
"**Исходные данные:** \n",
|
||||
"- Лабиринты разной сложности (маленький, средний, большой, пустой, без выхода). \n",
|
||||
"- Формат файла: `#` – стена, ` ` – проход, `S` – старт, `E` – выход. \n",
|
||||
"- Алгоритмы поиска: BFS, DFS, A* (с манхэттенской эвристикой).\n",
|
||||
"\n",
|
||||
"**Цель эксперимента:** сравнить эффективность алгоритмов по времени, количеству посещённых клеток и длине найденного пути.\n",
|
||||
"\n",
|
||||
"---\n",
|
||||
"\n",
|
||||
"## 2. Выбранные паттерны проектирования\n",
|
||||
"\n",
|
||||
"### 2.1. Builder (строитель) – для загрузки лабиринта\n",
|
||||
"**Проблема:** создание лабиринта из файла требует нескольких шагов (чтение, парсинг, установка флагов). \n",
|
||||
"**Решение:** интерфейс `MazeBuilder` и конкретная реализация `TextFileMazeBuilder` скрывают детали построения. \n",
|
||||
"**Преимущество:** легко добавить новый формат (JSON, XML) без изменения остального кода.\n",
|
||||
"\n",
|
||||
"### 2.2. Strategy (стратегия) – для алгоритмов поиска\n",
|
||||
"**Проблема:** алгоритмы поиска (BFS, DFS, A*) взаимозаменяемы, но их реализация отличается. \n",
|
||||
"**Решение:** интерфейс `PathFindingStrategy` и три класса-стратегии. \n",
|
||||
"**Преимущество:** можно динамически менять алгоритм во время выполнения (например, через `MazeSolver.setStrategy()`).\n",
|
||||
"\n",
|
||||
"### 2.3. Observer (наблюдатель) – для логирования (опционально, но реализован)\n",
|
||||
"**Проблема:** нужно оповещать внешние компоненты о событиях поиска (начало, найден путь, ошибка). \n",
|
||||
"**Решение:** интерфейс `Observer` и класс `ConsoleLogger`. \n",
|
||||
"**Преимущество:** слабая связность – легко добавить другие наблюдатели (GUI, файл лога).\n",
|
||||
"\n",
|
||||
"### 2.4. Command (команда) – для пошагового движения (опционально, в демо)\n",
|
||||
"**Проблема:** требуется поддержка отмены ходов (undo). \n",
|
||||
"**Решение:** интерфейс `Command`, класс `MoveCommand`, класс `Player`. \n",
|
||||
"**Преимущество:** инкапсуляция запроса, возможность отмены, ведения истории.\n",
|
||||
"\n",
|
||||
"---\n",
|
||||
"\n",
|
||||
"## 3. Диаграмма классов (Mermaid)\n",
|
||||
"\n",
|
||||
"```mermaid\n",
|
||||
"classDiagram\n",
|
||||
" class Cell {\n",
|
||||
" -int x\n",
|
||||
" -int y\n",
|
||||
" -bool isWall\n",
|
||||
" -bool isStart\n",
|
||||
" -bool isExit\n",
|
||||
" +isPassable()\n",
|
||||
" }\n",
|
||||
" class Maze {\n",
|
||||
" -int width\n",
|
||||
" -int height\n",
|
||||
" -List[List[Cell]] cells\n",
|
||||
" -Cell start\n",
|
||||
" -Cell exit\n",
|
||||
" +getCell(x,y)\n",
|
||||
" +getNeighbors(cell)\n",
|
||||
" }\n",
|
||||
" class MazeBuilder {\n",
|
||||
" <<interface>>\n",
|
||||
" +buildFromFile(filename)\n",
|
||||
" }\n",
|
||||
" class TextFileMazeBuilder {\n",
|
||||
" +buildFromFile(filename)\n",
|
||||
" }\n",
|
||||
" class PathFindingStrategy {\n",
|
||||
" <<interface>>\n",
|
||||
" +findPath(maze, start, exit)\n",
|
||||
" }\n",
|
||||
" class BFSStrategy\n",
|
||||
" class DFSStrategy\n",
|
||||
" class AStarStrategy\n",
|
||||
" class SearchStats {\n",
|
||||
" +float time_ms\n",
|
||||
" +int visited_cells\n",
|
||||
" +int path_length\n",
|
||||
" +string algorithm\n",
|
||||
" }\n",
|
||||
" class MazeSolver {\n",
|
||||
" -Maze maze\n",
|
||||
" -PathFindingStrategy strategy\n",
|
||||
" +setStrategy(strategy)\n",
|
||||
" +solve() (path, stats)\n",
|
||||
" }\n",
|
||||
" class Observer {\n",
|
||||
" <<interface>>\n",
|
||||
" +update(event_type, data)\n",
|
||||
" }\n",
|
||||
" class ConsoleLogger {\n",
|
||||
" +update(event_type, data)\n",
|
||||
" }\n",
|
||||
" class Command {\n",
|
||||
" <<interface>>\n",
|
||||
" +execute()\n",
|
||||
" +undo()\n",
|
||||
" }\n",
|
||||
" class MoveCommand {\n",
|
||||
" -Player player\n",
|
||||
" -tuple direction\n",
|
||||
" -Maze maze\n",
|
||||
" -Cell prev_pos\n",
|
||||
" +execute()\n",
|
||||
" +undo()\n",
|
||||
" }\n",
|
||||
" class Player {\n",
|
||||
" -Cell current_cell\n",
|
||||
" }\n",
|
||||
"\n",
|
||||
" MazeBuilder <|.. TextFileMazeBuilder\n",
|
||||
" PathFindingStrategy <|.. BFSStrategy\n",
|
||||
" PathFindingStrategy <|.. DFSStrategy\n",
|
||||
" PathFindingStrategy <|.. AStarStrategy\n",
|
||||
" MazeSolver --> PathFindingStrategy\n",
|
||||
" MazeSolver --> Maze\n",
|
||||
" MazeSolver --> SearchStats\n",
|
||||
" Observer <|.. ConsoleLogger\n",
|
||||
" MazeSolver --> Observer\n",
|
||||
" Command <|.. MoveCommand\n",
|
||||
" MoveCommand --> Player\n",
|
||||
" MoveCommand --> Maze"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "0e671083-627f-4940-970f-80f8668388fb",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 4.1 Builder (загрузка лабиринта)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"id": "bb983238-274f-4f19-b784-73be954a3aae",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"ename": "NameError",
|
||||
"evalue": "name 'ABC' is not defined",
|
||||
"output_type": "error",
|
||||
"traceback": [
|
||||
"\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
|
||||
"\u001b[1;31mNameError\u001b[0m Traceback (most recent call last)",
|
||||
"Cell \u001b[1;32mIn[1], line 1\u001b[0m\n\u001b[1;32m----> 1\u001b[0m \u001b[38;5;28;01mclass\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;21;01mMazeBuilder\u001b[39;00m(ABC):\n\u001b[0;32m 2\u001b[0m \u001b[38;5;129m@abstractmethod\u001b[39m\n\u001b[0;32m 3\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;21mbuild_from_file\u001b[39m(\u001b[38;5;28mself\u001b[39m, filename):\n\u001b[0;32m 4\u001b[0m \u001b[38;5;28;01mpass\u001b[39;00m\n",
|
||||
"\u001b[1;31mNameError\u001b[0m: name 'ABC' is not defined"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"class MazeBuilder(ABC):\n",
|
||||
" @abstractmethod\n",
|
||||
" def build_from_file(self, filename):\n",
|
||||
" pass\n",
|
||||
"\n",
|
||||
"class TextFileMazeBuilder(MazeBuilder):\n",
|
||||
" def build_from_file(self, filename):\n",
|
||||
" with open(filename, 'r') as f:\n",
|
||||
" lines = [line.rstrip('\\n') for line in f]\n",
|
||||
" height = len(lines)\n",
|
||||
" width = max(len(line) for line in lines)\n",
|
||||
" maze = Maze(width, height)\n",
|
||||
" for y, line in enumerate(lines):\n",
|
||||
" for x, ch in enumerate(line):\n",
|
||||
" cell = maze.get_cell(x, y)\n",
|
||||
" if ch == '#': cell.is_wall = True\n",
|
||||
" elif ch == 'S': cell.is_start = True; maze.start = cell\n",
|
||||
" elif ch == 'E': cell.is_exit = True; maze.exit = cell\n",
|
||||
" else: cell.is_wall = False\n",
|
||||
" return maze"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "f20c327f-b8f7-40f4-a71f-eede64d5dedf",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 4.2. Стратегии поиска (BFS, DFS, A*)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "b5fb0715-9749-404d-870d-0a83c0025eac",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"class BFSStrategy(PathFindingStrategy):\n",
|
||||
" def find_path(self, maze, start, exit):\n",
|
||||
" queue = deque([start])\n",
|
||||
" visited = {start}\n",
|
||||
" parent = {start: None}\n",
|
||||
" while queue:\n",
|
||||
" cur = queue.popleft()\n",
|
||||
" if cur == exit:\n",
|
||||
" path = []\n",
|
||||
" while cur:\n",
|
||||
" path.append(cur)\n",
|
||||
" cur = parent[cur]\n",
|
||||
" return path[::-1], len(visited)\n",
|
||||
" for nb in maze.get_neighbors(cur):\n",
|
||||
" if nb not in visited:\n",
|
||||
" visited.add(nb)\n",
|
||||
" parent[nb] = cur\n",
|
||||
" queue.append(nb)\n",
|
||||
" return [], len(visited)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "bae1222c-07f6-487d-ac05-7d09d7086e67",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 4.3. Оркестратор MazeSolver и статистика"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "247f19cf-4811-4dd8-8e35-03af6550d202",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"class MazeSolver:\n",
|
||||
" def solve(self):\n",
|
||||
" start_time = time.perf_counter()\n",
|
||||
" path, visited = self.strategy.find_path(self.maze, self.maze.start, self.maze.exit)\n",
|
||||
" end_time = time.perf_counter()\n",
|
||||
" stats = SearchStats(\n",
|
||||
" time_ms=(end_time - start_time)*1000,\n",
|
||||
" visited_cells=visited,\n",
|
||||
" path_length=len(path),\n",
|
||||
" algorithm=self.strategy.__class__.__name__\n",
|
||||
" )\n",
|
||||
" return path, stats"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "8a7167d5-efd8-4b3c-a14e-5b6c1cba83ef",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 5. Результаты экспериментов\n",
|
||||
"\n",
|
||||
"**Тестовые лабиринты:**\n",
|
||||
"- `tiny.txt` – 10×10, простой путь\n",
|
||||
"- `medium.txt` – 50×50, с тупиками\n",
|
||||
"- `large.txt` – 100×100, запутанный\n",
|
||||
"- `empty.txt` – 100×100, без стен (старт в левом верхнем углу, выход в правом нижнем)\n",
|
||||
"- `no_exit.txt` – лабиринт без выхода\n",
|
||||
"\n",
|
||||
"> **Примечание:** средний лабиринт (`medium.txt`) не был включён в замеры из-за отсутствия корректного файла. Остальные четыре лабиринта соответствуют заданию.\n",
|
||||
"\n",
|
||||
"**Методика:** каждый алгоритм запущен 5 раз на каждом лабиринте, значения усреднены. Данные получены из `all_results.csv`.\n",
|
||||
"\n",
|
||||
"### 5.1. Таблица результатов\n",
|
||||
"\n",
|
||||
"| Лабиринт | Алгоритм | Время (мс) | Посещено клеток | Длина пути |\n",
|
||||
"|----------------|----------------|------------|-----------------|------------|\n",
|
||||
"| tiny.txt | BFSStrategy | 0.2854 | 72.0 | 16.0 |\n",
|
||||
"| tiny.txt | DFSStrategy | 0.2665 | 71.0 | 72.0 |\n",
|
||||
"| tiny.txt | AStarStrategy | 0.3941 | 72.0 | 16.0 |\n",
|
||||
"| large.txt | BFSStrategy | 4.9520 | 1275.0 | 50.0 |\n",
|
||||
"| large.txt | DFSStrategy | 0.2159 | 49.0 | 50.0 |\n",
|
||||
"| large.txt | AStarStrategy | 0.3549 | 50.0 | 50.0 |\n",
|
||||
"| empty.txt | BFSStrategy | 24.3337 | 5049.0 | 100.0 |\n",
|
||||
"| empty.txt | DFSStrategy | 0.5570 | 99.0 | 100.0 |\n",
|
||||
"| empty.txt | AStarStrategy | 0.7525 | 100.0 | 100.0 |\n",
|
||||
"| no_exit.txt | BFSStrategy | 1.2649 | 324.0 | 0.0 |\n",
|
||||
"| no_exit.txt | DFSStrategy | 3.2304 | 324.0 | 0.0 |\n",
|
||||
"| no_exit.txt | AStarStrategy | 2.2239 | 324.0 | 0.0 |\n",
|
||||
"\n",
|
||||
"### 5.2. Графики\n",
|
||||
"\n",
|
||||
"Графики для каждого по отдельности сохранены в файле, тут предоставляю общее сравнение\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"---\n",
|
||||
"\n",
|
||||
"## 6. Анализ эффективности алгоритмов\n",
|
||||
"\n",
|
||||
"### 6.1. Время выполнения\n",
|
||||
"- На **tiny.txt** все алгоритмы показали близкое время (~0.2–0.4 мс); DFS незначительно быстрее.\n",
|
||||
"- На **large.txt** BFS значительно медленнее (4.95 мс) из-за равномерного обхода, а DFS и A* работают быстро (~0.2–0.35 мс).\n",
|
||||
"- На **empty.txt** BFS крайне медленен (24.3 мс), поскольку вынужден обойти почти всё поле; DFS и A* справляются за ~0.5–0.75 мс.\n",
|
||||
"- В лабиринте **no_exit.txt** BFS быстрее (1.26 мс), а DFS и A* медленнее (2.2–3.2 мс) из-за разных порядков обхода.\n",
|
||||
"\n",
|
||||
"### 6.2. Количество посещённых клеток\n",
|
||||
"- **BFS** на `empty.txt` посещает 5049 клеток (почти половину поля), тогда как DFS и A* – всего ~100 клеток.\n",
|
||||
"- В `large.txt` BFS посещает 1275 клеток, DFS – 49, A* – 50. Это показывает, что A* (как и DFS) находит путь, исследуя лишь узкую область.\n",
|
||||
"- В `tiny.txt` все алгоритмы посещают около 70 клеток (различия незначительны).\n",
|
||||
"\n",
|
||||
"### 6.3. Длина найденного пути\n",
|
||||
"- **BFS** и **A*** находят кратчайший путь (16 шагов в tiny.txt, 50 в large.txt, 100 в empty.txt).\n",
|
||||
"- **DFS** в tiny.txt даёт очень длинный путь (72 шага вместо 16), в large.txt – 50 (совпал с оптимальным), в empty.txt – 100 (оптимально).\n",
|
||||
"- Таким образом, DFS не гарантирует кратчайший путь, хотя в некоторых случаях может его найти.\n",
|
||||
"\n",
|
||||
"### 6.4. Лабиринт без выхода\n",
|
||||
"- Все алгоритмы исследуют всю достижимую область (324 клетки). \n",
|
||||
"- Время различается: BFS – 1.26 мс, DFS – 3.23 мс, A* – 2.22 мс. Это связано с тем, что DFS «закапывается» в глубину, а A* тратит время на поддержание очереди с приоритетами.\n",
|
||||
"\n",
|
||||
"---\n",
|
||||
"\n",
|
||||
"## 7. Применимость паттернов и гибкость архитектуры\n",
|
||||
"\n",
|
||||
"### 7.1. Паттерн Builder\n",
|
||||
"- **Без него:** код загрузки был бы прямо в `main` или в конструкторе `Maze`. Пришлось бы переписывать при добавлении нового формата.\n",
|
||||
"- **С ним:** легко добавить `JSONMazeBuilder`, заменив всего одну строку в клиентском коде.\n",
|
||||
"\n",
|
||||
"### 7.2. Паттерн Strategy\n",
|
||||
"- **Без него:** пришлось бы использовать громоздкие `if-elif` для выбора алгоритма, дублировать код замера времени.\n",
|
||||
"- **С ним:** алгоритмы полностью независимы, можно динамически менять стратегию (например, на основе размера лабиринта).\n",
|
||||
"\n",
|
||||
"### 7.3. Паттерн Observer (опционально)\n",
|
||||
"- **Без него:** логирование и визуализация были бы вплетены в алгоритмы поиска.\n",
|
||||
"- **С ним:** наблюдатели подписываются на события, и логирование можно отключить или заменить на другой вывод без изменения `MazeSolver`.\n",
|
||||
"\n",
|
||||
"### 7.4. Паттерн Command (для пошагового движения)\n",
|
||||
"- **Без него:** отмена хода пришлось бы реализовывать вручную, что привело бы к дублированию кода.\n",
|
||||
"- **С ним:** команды легко складывать в историю, реализовать `undo` и `redo`.\n",
|
||||
"\n",
|
||||
"---\n",
|
||||
"\n",
|
||||
"## 8. Выводы\n",
|
||||
"\n",
|
||||
"- **ООП и паттерны проектирования** позволили создать гибкую, расширяемую и легко тестируемую программу.\n",
|
||||
"- **Builder** упростил добавление новых форматов лабиринтов.\n",
|
||||
"- **Strategy** сделал алгоритмы поиска взаимозаменяемыми и позволил проводить честное сравнение.\n",
|
||||
"- **Observer** и **Command** добавили возможности логирования и отмены действий без «засорения» основной логики.\n",
|
||||
"- Экспериментально подтверждены теоретические свойства алгоритмов: A* – лучший выбор для большинства случаев (оптимальный путь и высокая скорость), BFS – оптимален по длине пути, но медленен на больших картах, DFS – прост, но не даёт гарантий кратчайшего пути.\n",
|
||||
"\n",
|
||||
"**Итог:** использование паттернов повысило качество кода, уменьшило связанность и облегчило поддержку. Без них любое изменение (добавление нового алгоритма или формата файла) потребовало бы правки многих классов.\n",
|
||||
"\n",
|
||||
"--- \n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "5e634d95-e0c0-4b8a-a330-02463cd085c8",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.13.5"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
352
filippovavm/docs/laba2/фулкод2.ipynb
Normal file
594
filippovavm/docs/МП.ipynb
Normal file
|
|
@ -0,0 +1,594 @@
|
|||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "dbe95ca0-7bc2-4cea-bdbb-319e9a1bd5b6",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Импорт библиотек\n",
|
||||
"# В этом блоке подключаются модули для работы со временем, случайными числами, CSV, системными параметрами, а также для построения графиков."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"id": "c0b2cd62-6c05-4896-8f40-82d75ae765e9",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import time\n",
|
||||
"import random\n",
|
||||
"import csv\n",
|
||||
"import sys\n",
|
||||
"import matplotlib.pyplot as plt\n",
|
||||
"import numpy as np"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "919b92b0-2819-457a-87a0-8f26eaeca817",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Связный список\n",
|
||||
"# Каждый узел содержит имя, телефон и ссылку на следующий узел.\n",
|
||||
"# Функции: ll_insert (вставка/обновление), ll_find (поиск), ll_delete (удаление)."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"id": "30700662-1215-4476-bffe-96ad9d3f1ab4",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Связный список\n",
|
||||
"def ll_insert(head, name, phone):\n",
|
||||
" current = head\n",
|
||||
" prev = None\n",
|
||||
" while current is not None:\n",
|
||||
" if current['name'] == name:\n",
|
||||
" current['phone'] = phone\n",
|
||||
" return head\n",
|
||||
" prev = current\n",
|
||||
" current = current['next']\n",
|
||||
" new_node = {'name': name, 'phone': phone, 'next': None}\n",
|
||||
" if prev is None:\n",
|
||||
" return new_node\n",
|
||||
" else:\n",
|
||||
" prev['next'] = new_node\n",
|
||||
" return head\n",
|
||||
"\n",
|
||||
"def ll_find(head, name):\n",
|
||||
" current = head\n",
|
||||
" while current is not None:\n",
|
||||
" if current['name'] == name:\n",
|
||||
" return current['phone']\n",
|
||||
" current = current['next']\n",
|
||||
" return None\n",
|
||||
"\n",
|
||||
"def ll_delete(head, name):\n",
|
||||
" if head is None:\n",
|
||||
" return None\n",
|
||||
" if head['name'] == name:\n",
|
||||
" return head['next']\n",
|
||||
" current = head\n",
|
||||
" while current['next'] is not None:\n",
|
||||
" if current['next']['name'] == name:\n",
|
||||
" current['next'] = current['next']['next']\n",
|
||||
" return head\n",
|
||||
" current = current['next']\n",
|
||||
" return head"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "49dd7db0-58a7-4ff9-98cd-529e2e91ca94",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Хеш-таблица\n",
|
||||
"# Хеш-функция на основе полиномиального кода (31 и длина таблицы).\n",
|
||||
"# Размер таблицы фиксирован (2000). В каждой ячейке хранится связный список.\n",
|
||||
"# Функции: ht_create, ht_insert, ht_find, ht_delete."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"id": "22800445-5217-45ce-9ecd-0f61389b1c3f",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Хеш-таблица \n",
|
||||
"def hash_function(name, size):\n",
|
||||
" total = 0\n",
|
||||
" for ch in name:\n",
|
||||
" total = (total * 31 + ord(ch)) % size\n",
|
||||
" return total\n",
|
||||
"\n",
|
||||
"def ht_create(size=2000):\n",
|
||||
" return [None] * size\n",
|
||||
"\n",
|
||||
"def ht_insert(buckets, name, phone):\n",
|
||||
" idx = hash_function(name, len(buckets))\n",
|
||||
" buckets[idx] = ll_insert(buckets[idx], name, phone)\n",
|
||||
"\n",
|
||||
"def ht_find(buckets, name):\n",
|
||||
" idx = hash_function(name, len(buckets))\n",
|
||||
" return ll_find(buckets[idx], name)\n",
|
||||
"\n",
|
||||
"def ht_delete(buckets, name):\n",
|
||||
" idx = hash_function(name, len(buckets))\n",
|
||||
" buckets[idx] = ll_delete(buckets[idx], name)\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "0b03af57-2771-4b20-8e7d-1ac475afaae8",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Двоичное дерево поиска\n",
|
||||
"# Узел содержит имя, телефон, ссылки на левого и правого потомка.\n",
|
||||
"# Функции: bst_insert (вставка/обновление), bst_find (поиск), bst_delete (удаление).\n",
|
||||
"# Для удаления используется поиск преемника (минимальный элемент в правом поддереве)."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"id": "7ced846b-b65f-4cf2-8fb8-5a4849f55509",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Двоичное дерево поиска \n",
|
||||
"def bst_insert(root, name, phone):\n",
|
||||
" new_node = {'name': name, 'phone': phone, 'left': None, 'right': None}\n",
|
||||
" if root is None:\n",
|
||||
" return new_node\n",
|
||||
" current = root\n",
|
||||
" while True:\n",
|
||||
" if name < current['name']:\n",
|
||||
" if current['left'] is None:\n",
|
||||
" current['left'] = new_node\n",
|
||||
" break\n",
|
||||
" current = current['left']\n",
|
||||
" elif name > current['name']:\n",
|
||||
" if current['right'] is None:\n",
|
||||
" current['right'] = new_node\n",
|
||||
" break\n",
|
||||
" current = current['right']\n",
|
||||
" else:\n",
|
||||
" current['phone'] = phone\n",
|
||||
" break\n",
|
||||
" return root\n",
|
||||
"\n",
|
||||
"def bst_find(root, name):\n",
|
||||
" current = root\n",
|
||||
" while current is not None:\n",
|
||||
" if name < current['name']:\n",
|
||||
" current = current['left']\n",
|
||||
" elif name > current['name']:\n",
|
||||
" current = current['right']\n",
|
||||
" else:\n",
|
||||
" return current['phone']\n",
|
||||
" return None\n",
|
||||
"\n",
|
||||
"def bst_find_min(node):\n",
|
||||
" while node['left'] is not None:\n",
|
||||
" node = node['left']\n",
|
||||
" return node\n",
|
||||
"\n",
|
||||
"def bst_delete(root, name):\n",
|
||||
" parent = None\n",
|
||||
" current = root\n",
|
||||
" while current is not None and current['name'] != name:\n",
|
||||
" parent = current\n",
|
||||
" if name < current['name']:\n",
|
||||
" current = current['left']\n",
|
||||
" else:\n",
|
||||
" current = current['right']\n",
|
||||
" if current is None:\n",
|
||||
" return root\n",
|
||||
" \n",
|
||||
" if current['left'] is None and current['right'] is None:\n",
|
||||
" if parent is None:\n",
|
||||
" return None\n",
|
||||
" if parent['left'] is current:\n",
|
||||
" parent['left'] = None\n",
|
||||
" else:\n",
|
||||
" parent['right'] = None\n",
|
||||
" return root\n",
|
||||
" if current['left'] is None:\n",
|
||||
" if parent is None:\n",
|
||||
" return current['right']\n",
|
||||
" if parent['left'] is current:\n",
|
||||
" parent['left'] = current['right']\n",
|
||||
" else:\n",
|
||||
" parent['right'] = current['right']\n",
|
||||
" return root\n",
|
||||
" if current['right'] is None:\n",
|
||||
" if parent is None:\n",
|
||||
" return current['left']\n",
|
||||
" if parent['left'] is current:\n",
|
||||
" parent['left'] = current['left']\n",
|
||||
" else:\n",
|
||||
" parent['right'] = current['left']\n",
|
||||
" return root\n",
|
||||
" \n",
|
||||
" succ_parent = current\n",
|
||||
" succ = current['right']\n",
|
||||
" while succ['left'] is not None:\n",
|
||||
" succ_parent = succ\n",
|
||||
" succ = succ['left']\n",
|
||||
" current['name'] = succ['name']\n",
|
||||
" current['phone'] = succ['phone']\n",
|
||||
" if succ_parent['left'] is succ:\n",
|
||||
" succ_parent['left'] = succ['right']\n",
|
||||
" else:\n",
|
||||
" succ_parent['right'] = succ['right']\n",
|
||||
" return root"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "88e1d5ec-5123-4bff-837a-bb451a0125c3",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Генерация записей телефонной книги\n",
|
||||
"# Создаёт N записей вида User_00001 и случайный номер телефона."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "b266c127-99a7-479c-a012-3eedeff7ade3",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Генерация данных \n",
|
||||
"def generate_records(N):\n",
|
||||
" records = []\n",
|
||||
" for i in range(N):\n",
|
||||
" name = f\"User_{i:05d}\"\n",
|
||||
" phone = f\"+7-999-{random.randint(1000000, 9999999)}\"\n",
|
||||
" records.append((name, phone))\n",
|
||||
" return records"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "a92500c8-e928-46a8-a115-12cc033ea2da",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Функции замеров\n",
|
||||
"# measure_insert – вставка всех записей (повторяется 5 раз).\n",
|
||||
"# build_structure – построение структуры данных (используется для последующих замеров).\n",
|
||||
"# measure_find_on_structure – поиск 110 записей (100 существующих + 10 отсутствующих) 5 раз.\n",
|
||||
"# measure_delete_on_structure – удаление 50 случайных записей 5 раз."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "d5060601-6e07-4148-9faa-f9b7b5f433dd",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Замеры\n",
|
||||
"REPEATS = 5\n",
|
||||
"N = 10000\n",
|
||||
"\n",
|
||||
"def measure_insert(struct, records, repeats=REPEATS):\n",
|
||||
" times = []\n",
|
||||
" for _ in range(repeats):\n",
|
||||
" if struct == 'll':\n",
|
||||
" head = None\n",
|
||||
" start = time.perf_counter()\n",
|
||||
" for name, phone in records:\n",
|
||||
" head = ll_insert(head, name, phone)\n",
|
||||
" end = time.perf_counter()\n",
|
||||
" elif struct == 'ht':\n",
|
||||
" buckets = ht_create(2000)\n",
|
||||
" start = time.perf_counter()\n",
|
||||
" for name, phone in records:\n",
|
||||
" ht_insert(buckets, name, phone)\n",
|
||||
" end = time.perf_counter()\n",
|
||||
" elif struct == 'bst':\n",
|
||||
" root = None\n",
|
||||
" start = time.perf_counter()\n",
|
||||
" for name, phone in records:\n",
|
||||
" root = bst_insert(root, name, phone)\n",
|
||||
" end = time.perf_counter()\n",
|
||||
" times.append(end - start)\n",
|
||||
" return times\n",
|
||||
"\n",
|
||||
"def build_structure(struct, records):\n",
|
||||
" if struct == 'll':\n",
|
||||
" head = None\n",
|
||||
" for name, phone in records:\n",
|
||||
" head = ll_insert(head, name, phone)\n",
|
||||
" return head\n",
|
||||
" elif struct == 'ht':\n",
|
||||
" buckets = ht_create(2000)\n",
|
||||
" for name, phone in records:\n",
|
||||
" ht_insert(buckets, name, phone)\n",
|
||||
" return buckets\n",
|
||||
" elif struct == 'bst':\n",
|
||||
" root = None\n",
|
||||
" for name, phone in records:\n",
|
||||
" root = bst_insert(root, name, phone)\n",
|
||||
" return root\n",
|
||||
" def measure_find_on_structure(struct, structure, records, repeats=REPEATS):\n",
|
||||
" times = []\n",
|
||||
" N = len(records)\n",
|
||||
" for _ in range(repeats):\n",
|
||||
" indices = random.sample(range(N), 100)\n",
|
||||
" exist = [records[i][0] for i in indices]\n",
|
||||
" missing = [f\"None_{i}\" for i in range(10)]\n",
|
||||
" search = exist + missing\n",
|
||||
" start = time.perf_counter()\n",
|
||||
" if struct == 'll':\n",
|
||||
" for name in search:\n",
|
||||
" ll_find(structure, name)\n",
|
||||
" elif struct == 'ht':\n",
|
||||
" for name in search:\n",
|
||||
" ht_find(structure, name)\n",
|
||||
" elif struct == 'bst':\n",
|
||||
" for name in search:\n",
|
||||
" bst_find(structure, name)\n",
|
||||
" times.append(time.perf_counter() - start)\n",
|
||||
" return times\n",
|
||||
"\n",
|
||||
"def measure_delete_on_structure(struct, records, repeats=REPEATS):\n",
|
||||
" times = []\n",
|
||||
" N = len(records)\n",
|
||||
" for _ in range(repeats):\n",
|
||||
" indices = random.sample(range(N), 50)\n",
|
||||
" del_names = [records[i][0] for i in indices]\n",
|
||||
" if struct == 'll':\n",
|
||||
" head = None\n",
|
||||
" for name, phone in records:\n",
|
||||
" head = ll_insert(head, name, phone)\n",
|
||||
" start = time.perf_counter()\n",
|
||||
" for name in del_names:\n",
|
||||
" head = ll_delete(head, name)\n",
|
||||
" end = time.perf_counter()\n",
|
||||
" elif struct == 'ht':\n",
|
||||
" buckets = ht_create(2000)\n",
|
||||
" for name, phone in records:\n",
|
||||
" ht_insert(buckets, name, phone)\n",
|
||||
" start = time.perf_counter()\n",
|
||||
" for name in del_names:\n",
|
||||
" ht_delete(buckets, name)\n",
|
||||
" end = time.perf_counter()\n",
|
||||
" elif struct == 'bst':\n",
|
||||
" root = None\n",
|
||||
" for name, phone in records:\n",
|
||||
" root = bst_insert(root, name, phone)\n",
|
||||
" start = time.perf_counter()\n",
|
||||
" for name in del_names:\n",
|
||||
" root = bst_delete(root, name)\n",
|
||||
" end = time.perf_counter()\n",
|
||||
" times.append(end - start)\n",
|
||||
" return times\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "d93d8eac-b094-43b2-8af0-9afb0ab51ada",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Основная функция\n",
|
||||
"# 1. Генерирует 10000 записей в случайном и отсортированном порядке.\n",
|
||||
"# 2. Измеряет время вставки всех записей, поиска (110 запросов) и удаления (50 записей)\n",
|
||||
"# для связного списка, хеш-таблицы и дерева.\n",
|
||||
"# 3. Выводит средние значения, сохраняет все замеры в CSV.\n",
|
||||
"# 4. Строит несколько графиков (сравнительные столбчатые диаграммы и графики по попыткам)."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "a667779c-39b8-4e4b-b05d-bf9c9cccbbd9",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Основная функция \n",
|
||||
"def main():\n",
|
||||
" print(\"Генерация данных...\")\n",
|
||||
" records = generate_records(N)\n",
|
||||
" random.shuffle(records) # случайный порядок\n",
|
||||
" records_sorted = sorted(records, key=lambda x: x[0]) # отсортированный\n",
|
||||
"\n",
|
||||
" results = [] # для CSV\n",
|
||||
" struct_names = {'ll': 'Связного списка', 'ht': 'Хеш-таблицы', 'bst': 'Двоичного дерева поиска'}\n",
|
||||
" mode_names = {'shuffled': 'случайный', 'sorted': 'отсортированный'}\n",
|
||||
" op_names = {'insert': 'Вставка всех записей', 'find': 'Поиск записей', 'delete': 'Удаление записей'}\n",
|
||||
" # Вставка \n",
|
||||
" for struct in ['ll', 'ht', 'bst']:\n",
|
||||
" print(f\"\\nИзмерение вставки для {struct_names[struct]}...\")\n",
|
||||
" times_sh = measure_insert(struct, records)\n",
|
||||
" times_so = measure_insert(struct, records_sorted)\n",
|
||||
" insert_sh[struct] = times_sh\n",
|
||||
" insert_so[struct] = times_so\n",
|
||||
" print(f\" случайный: {[round(t,6) for t in times_sh]}, среднее = {sum(times_sh)/len(times_sh):.6f}\")\n",
|
||||
" print(f\" отсортированный: {[round(t,6) for t in times_so]}, среднее = {sum(times_so)/len(times_so):.6f}\")\n",
|
||||
" results.append([struct_names[struct], mode_names['shuffled'], op_names['insert'], sum(times_sh)/len(times_sh)] + times_sh)\n",
|
||||
" results.append([struct_names[struct], mode_names['sorted'], op_names['insert'], sum(times_so)/len(times_so)] + times_so)\n",
|
||||
" # Поиск \n",
|
||||
" for struct in ['ll', 'ht', 'bst']:\n",
|
||||
" print(f\"\\nПоиск для {struct_names[struct]} на случайных данных...\")\n",
|
||||
" structure_sh = build_structure(struct, records)\n",
|
||||
" times_find_sh = measure_find_on_structure(struct, structure_sh, records)\n",
|
||||
" find_sh[struct] = times_find_sh\n",
|
||||
" print(f\" случайный: {[round(t,6) for t in times_find_sh]}, среднее = {sum(times_find_sh)/len(times_find_sh):.6f}\")\n",
|
||||
" results.append([struct_names[struct], mode_names['shuffled'], op_names['find'], sum(times_find_sh)/len(times_find_sh)] + times_find_sh)\n",
|
||||
"\n",
|
||||
" print(f\"Поиск для {struct_names[struct]} на отсортированных данных...\")\n",
|
||||
" structure_so = build_structure(struct, records_sorted)\n",
|
||||
" times_find_so = measure_find_on_structure(struct, structure_so, records_sorted)\n",
|
||||
" find_so[struct] = times_find_so\n",
|
||||
" print(f\" отсортированный: {[round(t,6) for t in times_find_so]}, среднее = {sum(times_find_so)/len(times_find_so):.6f}\")\n",
|
||||
" results.append([struct_names[struct], mode_names['sorted'], op_names['find'], sum(times_find_so)/len(times_find_so)] + times_find_so)\n",
|
||||
" # Удаление \n",
|
||||
" for struct in ['ll', 'ht', 'bst']:\n",
|
||||
" print(f\"\\nУдаление для {struct_names[struct]} на случайных данных...\")\n",
|
||||
" times_del_sh = measure_delete_on_structure(struct, records)\n",
|
||||
" delete_sh[struct] = times_del_sh\n",
|
||||
" print(f\" случайный: {[round(t,6) for t in times_del_sh]}, среднее = {sum(times_del_sh)/len(times_del_sh):.6f}\")\n",
|
||||
" results.append([struct_names[struct], mode_names['shuffled'], op_names['delete'], sum(times_del_sh)/len(times_del_sh)] + times_del_sh)\n",
|
||||
"\n",
|
||||
" print(f\"Удаление для {struct_names[struct]} на отсортированных данных...\")\n",
|
||||
" times_del_so = measure_delete_on_structure(struct, records_sorted)\n",
|
||||
" delete_so[struct] = times_del_so\n",
|
||||
" print(f\" отсортированный: {[round(t,6) for t in times_del_so]}, среднее = {sum(times_del_so)/len(times_del_so):.6f}\")\n",
|
||||
" results.append([struct_names[struct], mode_names['sorted'], op_names['delete'], sum(times_del_so)/len(times_del_so)] + times_del_so)\n",
|
||||
" # Сохраняем CSV\n",
|
||||
" with open(\"phonebook_results.csv\", \"w\", newline=\"\", encoding=\"utf-8\") as f:\n",
|
||||
" writer = csv.writer(f)\n",
|
||||
" writer.writerow(['Структура', 'Режим', 'Операция', 'Среднее', 'Замер1', 'Замер2', 'Замер3', 'Замер4', 'Замер5'])\n",
|
||||
" writer.writerows(results)\n",
|
||||
"# Графики замеров\n",
|
||||
" try:\n",
|
||||
" def plot_attempts(data_sh, data_so, op_name):\n",
|
||||
" fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))\n",
|
||||
" # случайный порядок\n",
|
||||
" for struct, label, color, marker in [('ll','LinkedList','red','o'), ('ht','HashTable','green','s'), ('bst','BST','blue','^')]:\n",
|
||||
" times = data_sh[struct]\n",
|
||||
" x = range(1, len(times)+1)\n",
|
||||
" ax1.plot(x, times, marker=marker, color=color, label=label, linestyle='--', linewidth=1)\n",
|
||||
" ax1.scatter(x, times, color=color, s=60, zorder=5)\n",
|
||||
" ax1.set_xlabel('Номер попытки')\n",
|
||||
" ax1.set_ylabel('Время (сек)')\n",
|
||||
" ax1.set_title(f'{op_name} – случайный порядок')\n",
|
||||
" ax1.legend()\n",
|
||||
" ax1.grid(True, linestyle=':', alpha=0.7)\n",
|
||||
" # отсортированный порядок\n",
|
||||
" for struct, label, color, marker in [('ll','LinkedList','red','o'), ('ht','HashTable','green','s'), ('bst','BST','blue','^')]:\n",
|
||||
" times = data_so[struct]\n",
|
||||
" x = range(1, len(times)+1)\n",
|
||||
" ax2.plot(x, times, marker=marker, color=color, label=label, linestyle='--', linewidth=1)\n",
|
||||
" ax2.scatter(x, times, color=color, s=60, zorder=5)\n",
|
||||
" ax2.set_xlabel('Номер попытки')\n",
|
||||
" ax2.set_ylabel('Время (сек)')\n",
|
||||
" ax2.set_title(f'{op_name} – отсортированный порядок')\n",
|
||||
" ax2.legend()\n",
|
||||
" ax2.grid(True, linestyle=':', alpha=0.7)\n",
|
||||
" plt.tight_layout()\n",
|
||||
" plt.savefig(f'{op_name}_5attempts.png')\n",
|
||||
" plt.show()\n",
|
||||
" \n",
|
||||
" plot_attempts(insert_sh, insert_so, 'insert')\n",
|
||||
" plot_attempts(find_sh, find_so, 'find')\n",
|
||||
" plot_attempts(delete_sh, delete_so, 'delete')\n",
|
||||
" print(\"Дополнительные графики сохранены: insert_5attempts.png, find_5attempts.png, delete_5attempts.png\")\n",
|
||||
" except Exception as e:\n",
|
||||
" print(f\"Не удалось построить дополнительные графики: {e}\")\n",
|
||||
"try:\n",
|
||||
" # График вставки\n",
|
||||
" fig1, ax1 = plt.subplots(figsize=(10,6))\n",
|
||||
" x = np.arange(3)\n",
|
||||
" width = 0.35\n",
|
||||
" means_sh = [sum(insert_sh[s])/len(insert_sh[s]) for s in ['ll','ht','bst']]\n",
|
||||
" means_so = [sum(insert_so[s])/len(insert_so[s]) for s in ['ll','ht','bst']]\n",
|
||||
" rects1 = ax1.bar(x - width/2, means_sh, width, label='Случайный порядок', color='skyblue')\n",
|
||||
" rects2 = ax1.bar(x + width/2, means_so, width, label='Отсортированный порядок', color='salmon')\n",
|
||||
" ax1.set_ylabel('Время (сек)')\n",
|
||||
" ax1.set_title('Вставка всех записей')\n",
|
||||
" ax1.set_xticks(x)\n",
|
||||
" ax1.set_xticklabels(['Связный список', 'Хеш-таблица', 'Двоичное дерево'])\n",
|
||||
" ax1.legend()\n",
|
||||
" ax1.set_yscale('log')\n",
|
||||
" for rect in rects1 + rects2:\n",
|
||||
" h = rect.get_height()\n",
|
||||
" ax1.annotate(f'{h:.3f}', xy=(rect.get_x()+rect.get_width()/2, h),\n",
|
||||
" xytext=(0,3), textcoords=\"offset points\", ha='center', va='bottom', fontsize=8)\n",
|
||||
" plt.tight_layout()\n",
|
||||
" plt.savefig('insert_comparison.png')\n",
|
||||
" plt.show()\n",
|
||||
"\n",
|
||||
" # График поиска\n",
|
||||
" fig2, ax2 = plt.subplots(figsize=(10,6))\n",
|
||||
" means_find_sh = [sum(find_sh[s])/len(find_sh[s]) for s in ['ll','ht','bst']]\n",
|
||||
" means_find_so = [sum(find_so[s])/len(find_so[s]) for s in ['ll','ht','bst']]\n",
|
||||
" rects1 = ax2.bar(x - width/2, means_find_sh, width, label='Случайный порядок', color='skyblue')\n",
|
||||
" rects2 = ax2.bar(x + width/2, means_find_so, width, label='Отсортированный порядок', color='salmon')\n",
|
||||
" ax2.set_ylabel('Время (сек)')\n",
|
||||
" ax2.set_title('Поиск (100 существующих + 10 отсутствующих)')\n",
|
||||
" ax2.set_xticks(x)\n",
|
||||
" ax2.set_xticklabels(['Связный список', 'Хеш-таблица', 'Двоичное дерево'])\n",
|
||||
" ax2.legend()\n",
|
||||
" for rect in rects1 + rects2:\n",
|
||||
" h = rect.get_height()\n",
|
||||
" ax2.annotate(f'{h:.5f}', xy=(rect.get_x()+rect.get_width()/2, h),\n",
|
||||
" xytext=(0,3), textcoords=\"offset points\", ha='center', va='bottom', fontsize=8)\n",
|
||||
" plt.tight_layout()\n",
|
||||
" plt.savefig('find_comparison.png')\n",
|
||||
" plt.show()\n",
|
||||
"\n",
|
||||
" # График удаления \n",
|
||||
" fig3, ax3 = plt.subplots(figsize=(10,6))\n",
|
||||
" means_del_sh = [sum(delete_sh[s])/len(delete_sh[s]) for s in ['ll','ht','bst']]\n",
|
||||
" means_del_so = [sum(delete_so[s])/len(delete_so[s]) for s in ['ll','ht','bst']]\n",
|
||||
" rects1 = ax3.bar(x - width/2, means_del_sh, width, label='Случайный порядок', color='skyblue')\n",
|
||||
" rects2 = ax3.bar(x + width/2, means_del_so, width, label='Отсортированный порядок', color='salmon')\n",
|
||||
" ax3.set_ylabel('Время (сек)')\n",
|
||||
" ax3.set_title('Удаление 50 случайных записей')\n",
|
||||
" ax3.set_xticks(x)\n",
|
||||
" ax3.set_xticklabels(['Связный список', 'Хеш-таблица', 'Двоичное дерево'])\n",
|
||||
" ax3.legend()\n",
|
||||
" for rect in rects1 + rects2:\n",
|
||||
" h = rect.get_height()\n",
|
||||
" ax3.annotate(f'{h:.5f}', xy=(rect.get_x()+rect.get_width()/2, h),\n",
|
||||
" xytext=(0,3), textcoords=\"offset points\", ha='center', va='bottom', fontsize=8)\n",
|
||||
" plt.tight_layout()\n",
|
||||
" plt.savefig('delete_comparison.png')\n",
|
||||
" plt.show()\n",
|
||||
" print(\"Графики сохранены: insert_comparison.png, find_comparison.png, delete_comparison.png\")\n",
|
||||
" except Exception as e:\n",
|
||||
" print(f\"Не удалось построить графики: {e}\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "dd2b18cd-f37c-4f9f-a92a-325be857deb0",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Запуск эксперимента\n",
|
||||
"# Устанавливается увеличенная глубина рекурсии (на случай глубоких деревьев) и вызывается main()."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "04462e9d-aecc-44b3-b98b-0d06f856f38a",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"if __name__ == \"__main__\":\n",
|
||||
" sys.setrecursionlimit(20000)\n",
|
||||
" main()"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.13.5"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
149
filippovavm/docs/отчёт1.ipynb
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "0c973489-075d-42ac-a28f-f8d8d954a0da",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Анализ результатов\n",
|
||||
"\n",
|
||||
"## Предложенные вопросы\n",
|
||||
"\n",
|
||||
"- Как порядок входных данных влияет на скорость вставки в BST (деградация до O(n) на отсортированных данных)?\n",
|
||||
"- Почему хеш-таблица почти не чувствительна к порядку?\n",
|
||||
"- Почему связный список всегда медленен при поиске?\n",
|
||||
"- Как удаление работает в каждой структуре?\n",
|
||||
"- Вывод: какую структуру и для каких задач (частые вставки, частый поиск, необходимость получать данные в порядке) стоит выбирать в реальной жизни?\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "a265cc14-95ff-47ae-a346-ac38cb2a323e",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Выводы\n",
|
||||
"\n",
|
||||
"### 1) Как порядок входных данных влияет на скорость вставки в BST?\n",
|
||||
"\n",
|
||||
"Порядок отличается очень сильно. В обычном случае сложность равна **O(log n)**, а в худшем случае (как раз на отсортированных данных) – **O(n)**. \n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "950c5e97-12e9-4225-a91e-b8289fdfb5e6",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### 2) Почему хеш-таблица почти не чувствительна к порядку?\n",
|
||||
"\n",
|
||||
"Это происходит из‑за особенностей записи данных в память. Хеш-таблица вычисляет номер строки (корзины) по математической формуле, поэтому любой элемент можно найти за **O(1)** в среднем. Порядок поступления записей не влияет на расчёт индекса.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "5f6059bf-e99a-4b14-869c-32fb44b092fa",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### 3) Почему связный список всегда медленен при поиске?\n",
|
||||
"\n",
|
||||
"Это происходит из‑за способа записи. Доступ к следующему элементу возможен только последовательным перебором, равным номеру искомой позиции. Сложность поиска – **O(n)**.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "77ddd385-a50d-4ab6-b761-477460529e9d",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### 4) Как удаление работает в каждой структуре?\n",
|
||||
"\n",
|
||||
"- **Связный список** \n",
|
||||
" - Если список пустой → возвращаем `None`. \n",
|
||||
" - Если удаляем голову → новой головой становится следующий элемент. \n",
|
||||
" - Если удаляем промежуточный элемент – ищем нужный узел, затем у предыдущего узла меняем ссылку на следующий после удаляемого.\n",
|
||||
"\n",
|
||||
"- **Хеш-таблица** \n",
|
||||
" Реализация вычисляет номер корзины через хеш-ключ, затем использует функции связного списка для работы внутри корзины. Таким образом, удаление в хеш-таблице наследует логику удаления из связного списка, но применяется только к элементам одной корзины.\n",
|
||||
"\n",
|
||||
"- **Бинарное дерево (BST)** \n",
|
||||
" Рассматриваются 4 случая (логика похожа на связный список, но с учётом двух потомков): \n",
|
||||
" - Если дерево пустое → вернуть `None`. \n",
|
||||
" - Если удаляемый элемент меньше корня → спуститься в левую ветвь. \n",
|
||||
" - Если больше корня → спуститься в правую ветвь. \n",
|
||||
" - Если у удаляемого узла два потомка – находим преемника (самый левый узел в правом поддереве), копируем его данные в удаляемый узел и удаляем преемника. \n",
|
||||
" При нахождении элемента ссылка от родителя к удаляемому узлу заменяется на ссылку на соответствующего потомка (левый или правый).\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "97b09bb6-e8ef-486e-9cdd-fc37b19bfeb2",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### 5) Какую структуру и для каких задач стоит выбирать в реальной жизни?\n",
|
||||
"\n",
|
||||
"- **Для частых вставок и поиска элементов** – лучше всего использовать **хеш-таблицу**, так как добавление происходит за счёт математического вычисления индекса, а не последовательного перебора. \n",
|
||||
"- **Если нужны упорядоченные данные** (например, вывод записей в алфавитном порядке) – подходит **двоичное дерево поиска** (BST) благодаря свойству in‑order обхода. \n",
|
||||
"- **Связный список** неэффективен для больших объёмов данных.\n",
|
||||
"\n",
|
||||
"---"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "3b242f54-fd27-4f6e-8a31-9b3a6bee5f59",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Дополнительные числовые результаты\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"1. **Связный список:** \n",
|
||||
" - Вставка: O(n) → ~0.25 с, порядок данных не влияет. \n",
|
||||
" - Поиск: O(n) → очень медленно (~0.5 с), порядок не влияет. \n",
|
||||
" - Удаление: O(n) → медленно.\n",
|
||||
"\n",
|
||||
"2. **Хеш-таблица:** \n",
|
||||
" - Вставка: O(1) в среднем → ~0.008 с, порядок данных не влияет. \n",
|
||||
" - Поиск: O(1) → ~0.002 с, самый быстрый. \n",
|
||||
" - Удаление: O(1) → ~0.002 с.\n",
|
||||
"\n",
|
||||
"3. **Двоичное дерево поиска:** \n",
|
||||
" - На случайных данных: O(log n) → вставка ~0.018 с, поиск ~0.0015 с, удаление ~0.0016 с. \n",
|
||||
" - На отсортированных данных: дерево вырождается в линейный список → вставка ~2.3 с, поиск и удаление также становятся O(n) (на графиках виден рост времени).\n",
|
||||
"\n",
|
||||
"**ИТОГОВЫЙ ВЫВОД:** \n",
|
||||
"- Для частого поиска, вставки и удаления – лучший выбор **хеш-таблица**. \n",
|
||||
"- Если данные поступают в отсортированном порядке – BST не подходит из‑за деградации. \n",
|
||||
"- Если нужна частая выдача записей в алфавитном порядке и данные случайны – BST хорош. \n",
|
||||
"- Связный список неэффективен для больших объёмов."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "1b39f136-0c95-46f0-b794-7136232bcd3c",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.13.5"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||