Compare commits

...

17 Commits

Author SHA1 Message Date
aadb5b2e3b Основная функция main 2026-05-24 20:24:56 +03:00
118945f625 Анализ эффективности алгоритмов 2026-05-24 20:20:06 +03:00
7eb2e40099 Экспериментальная часть 2026-05-24 20:15:48 +03:00
8aa0d0ee0e Паттерн Command (пошаговое движение с отменой) 2026-05-24 20:14:31 +03:00
49ef151e63 Паттерн Observer 2026-05-24 20:12:46 +03:00
9f5a0055f3 Оркестратор MazeSolver и статистика 2026-05-24 20:10:49 +03:00
4c14b6d0b3 Паттерн Strategy 2026-05-24 20:09:28 +03:00
02842819e9 Библиотеки и базовые классы Cell и Maze 2026-05-24 19:28:32 +03:00
bd2c4fd939 [1]итог 2026-05-23 00:17:04 +03:00
69095c1b52 Запуск программы 2026-05-22 23:17:59 +03:00
b652b1515c Основная функция main() 2026-05-22 23:11:58 +03:00
a8baea68c7 Функции построения графиков 2026-05-22 23:07:21 +03:00
27417decc0 Генерация данных и функции замеров 2026-05-22 23:01:54 +03:00
da179c5dde Двоичное дерево поиска 2026-05-22 23:00:35 +03:00
951190aa68 Хеш-таблица 2026-05-22 22:59:13 +03:00
07fe035d63 Связный список 2026-05-22 22:08:31 +03:00
b4daf9ea72 [0] 2026-02-14 14:44:01 +03:00
22 changed files with 1275 additions and 0 deletions

1
tseremonnikovaaa/427 Normal file
View File

@ -0,0 +1 @@
427

View File

@ -0,0 +1,656 @@
import time
import csv
import heapq
from collections import deque
from abc import ABC, abstractmethod
import matplotlib.pyplot as plt
import pandas as pd
from dataclasses import dataclass
import os
class Cell:
"""Клетка лабиринта"""
def __init__(self, x, y, is_wall=False):
self.x = x
self.y = y
self.is_wall = is_wall
self.is_start = False
self.is_exit = False
def is_passable(self):
return not self.is_wall
class Maze:
"""Лабиринт"""
def __init__(self, width, height):
self.width = width
self.height = height
self.cells = [[Cell(x, y) for x in range(width)] for y in range(height)]
self.start = None
self.exit = None
def get_cell(self, x, y):
if 0 <= x < self.width and 0 <= y < self.height:
return self.cells[y][x]
return None
def get_neighbors(self, cell):
neighbors = []
for dx, dy in [(0, 1), (0, -1), (1, 0), (-1, 0)]:
nx, ny = cell.x + dx, cell.y + dy
nb = self.get_cell(nx, ny)
if nb and nb.is_passable():
neighbors.append(nb)
return neighbors
def __str__(self):
result = ""
for y in range(self.height):
for x in range(self.width):
cell = self.get_cell(x, y)
if cell is None:
result += "?"
elif cell.is_wall:
result += "#"
elif cell.is_start:
result += "S"
elif cell.is_exit:
result += "E"
else:
result += " "
result += "\n"
return result
class MazeBuilder(ABC):
@abstractmethod
def build_from_file(self, filename):
pass
class TextFileMazeBuilder(MazeBuilder):
def build_from_file(self, filename):
with open(filename, 'r', encoding='utf-8') as f:
lines = [line.rstrip('\n') for line in f.readlines()]
height = len(lines)
width = max(len(line) for line in lines)
maze = Maze(width, height)
for y, line in enumerate(lines):
for x, ch in enumerate(line):
cell = maze.get_cell(x, y)
if ch == '#':
cell.is_wall = True
elif ch == 'S':
cell.is_start = True
maze.start = cell
elif ch == 'E':
cell.is_exit = True
maze.exit = cell
else:
cell.is_wall = False
return maze
class PathFindingStrategy(ABC):
@abstractmethod
def find_path(self, maze, start, exit):
pass
class BFSStrategy(PathFindingStrategy):
"""Поиск в ширину"""
def find_path(self, maze, start, exit):
visited = set()
if start == exit:
return [start], 1
queue = deque([start])
visited.add(start)
parent = {start: None}
while queue:
current = queue.popleft()
for nb in maze.get_neighbors(current):
if nb not in visited:
visited.add(nb)
parent[nb] = current
if nb == exit:
path = []
node = nb
while node is not None:
path.append(node)
node = parent[node]
path.reverse()
return path, len(visited)
queue.append(nb)
return [], len(visited)
class DFSStrategy(PathFindingStrategy):
"""Поиск в глубину"""
def find_path(self, maze, start, exit):
visited = set()
stack = [(start, [start])]
while stack:
current, path = stack.pop()
if current == exit:
return path, len(visited)
visited.add(current)
for nb in maze.get_neighbors(current):
if nb not in visited:
stack.append((nb, path + [nb]))
return [], len(visited)
class AStarStrategy(PathFindingStrategy):
"""Алгоритм A"""
def heuristic(self, cell, exit):
return abs(cell.x - exit.x) + abs(cell.y - exit.y)
def find_path(self, maze, start, exit):
open_set = []
counter = 0
heapq.heappush(open_set, (0, counter, start))
counter += 1
came_from = {}
g_score = {start: 0}
f_score = {start: self.heuristic(start, exit)}
visited = set()
while open_set:
_, _, current = heapq.heappop(open_set)
visited.add(current)
if current == exit:
path = []
node = current
while node in came_from:
path.append(node)
node = came_from[node]
path.append(start)
path.reverse()
return path, len(visited)
for nb in maze.get_neighbors(current):
tentative_g = g_score[current] + 1
if tentative_g < g_score.get(nb, float('inf')):
came_from[nb] = current
g_score[nb] = tentative_g
f = tentative_g + self.heuristic(nb, exit)
heapq.heappush(open_set, (f, counter, nb))
counter += 1
return [], len(visited)
@dataclass
class SearchStats:
time_ms: float
visited_cells: int
path_length: int
algorithm: str
class MazeSolver:
def __init__(self, maze, strategy):
self.maze = maze
self.strategy = strategy
def set_strategy(self, strategy):
self.strategy = strategy
def solve(self):
if self.maze.start is None or self.maze.exit is None:
raise ValueError("Лабиринт не имеет старта или выхода")
start_time = time.perf_counter()
path, visited = self.strategy.find_path(self.maze, self.maze.start, self.maze.exit)
end_time = time.perf_counter()
stats = SearchStats(
time_ms=(end_time - start_time) * 1000,
visited_cells=visited,
path_length=len(path),
algorithm=self.strategy.__class__.__name__
)
return path, stats
class Observer(ABC):
@abstractmethod
def update(self, event_type, data=None):
pass
class ConsoleLogger(Observer):
def update(self, event_type, data=None):
if event_type == "search_start":
print(f"[LOG] Поиск пути начат")
elif event_type == "path_found":
print(f"[LOG] Путь найден! Длина: {data}")
elif event_type == "no_path":
print("[LOG] Путь не найден")
elif event_type == "step":
print(f"[LOG] Шаг: {data}")
class MazeSolverWithObserver(MazeSolver):
def __init__(self, maze, strategy, observers=None):
super().__init__(maze, strategy)
self.observers = observers if observers else []
def attach(self, observer):
self.observers.append(observer)
def detach(self, observer):
self.observers.remove(observer)
def notify(self, event_type, data=None):
for obs in self.observers:
obs.update(event_type, data)
def solve(self):
if self.maze.start is None or self.maze.exit is None:
raise ValueError("Лабиринт не имеет старта или выхода")
self.notify("search_start")
start_time = time.perf_counter()
path, visited = self.strategy.find_path(self.maze, self.maze.start, self.maze.exit)
end_time = time.perf_counter()
if path:
self.notify("path_found", len(path))
else:
self.notify("no_path")
stats = SearchStats(
time_ms=(end_time - start_time) * 1000,
visited_cells=visited,
path_length=len(path),
algorithm=self.strategy.__class__.__name__
)
return path, stats
class Command(ABC):
@abstractmethod
def execute(self):
pass
@abstractmethod
def undo(self):
pass
class MoveCommand(Command):
def __init__(self, player, direction, maze):
self.player = player
self.direction = direction
self.maze = maze
self.prev_pos = None
def execute(self):
self.prev_pos = self.player.current_cell
dx, dy = self.direction
nx, ny = self.player.current_cell.x + dx, self.player.current_cell.y + dy
new_cell = self.maze.get_cell(nx, ny)
if new_cell and new_cell.is_passable():
self.player.current_cell = new_cell
return True
return False
def undo(self):
if self.prev_pos:
self.player.current_cell = self.prev_pos
return True
return False
class Player:
def __init__(self, start_cell):
self.current_cell = start_cell
def interactive_move_demo(maze, path):
"""Демонстрация движения с отменой последнего шага"""
if not path:
print("Путь не найден, демонстрация движения невозможна.")
return
player = Player(maze.start)
command_history = []
print("\n Интерактивное движение по найденному пути")
print("Текущая позиция: старт")
for step, cell in enumerate(path):
if cell == maze.start:
continue
prev = path[step-1]
dx = cell.x - prev.x
dy = cell.y - prev.y
cmd = MoveCommand(player, (dx, dy), maze)
cmd.execute()
command_history.append(cmd)
print(f"Шаг {step}: перемещение на ({dx},{dy}), позиция ({player.current_cell.x},{player.current_cell.y})")
if cell == maze.exit:
print("Достигнут выход!")
break
if command_history:
print("\nДемонстрация отмены последнего шага")
cmd = command_history[-1]
cmd.undo()
print(f"Отменён последний шаг, позиция: ({player.current_cell.x},{player.current_cell.y})")
def test_single_maze(filename, strategies, repeats=5):
"""Тестирование одного лабиринта с разными стратегиями"""
builder = TextFileMazeBuilder()
maze = builder.build_from_file(filename)
results = []
for strategy in strategies:
solver = MazeSolver(maze, strategy)
times = []
visits = []
lengths = []
for _ in range(repeats):
_, stats = solver.solve()
times.append(stats.time_ms)
visits.append(stats.visited_cells)
lengths.append(stats.path_length)
results.append({
'algorithm': strategy.__class__.__name__,
'avg_time_ms': sum(times) / repeats,
'avg_visited': sum(visits) / repeats,
'avg_path_len': sum(lengths) / repeats
})
return results
def save_maze_to_file(maze, filename):
"""Сохранение лабиринта в файл"""
os.makedirs(os.path.dirname(filename), exist_ok=True)
with open(filename, 'w', encoding='utf-8') as f:
for y in range(maze.height):
line = ""
for x in range(maze.width):
cell = maze.get_cell(x, y)
if cell.is_wall:
line += "#"
elif cell.is_start:
line += "S"
elif cell.is_exit:
line += "E"
else:
line += " "
f.write(line + "\n")
def create_test_mazes():
"""Создание тестовых лабиринтов"""
os.makedirs("mazes", exist_ok=True)
# 1. Простой лабиринт 10x10 (tiny.txt)
maze1 = Maze(10, 10)
for y in range(10):
for x in range(10):
is_start = (x == 0 and y == 0)
is_exit = (x == 9 and y == 0)
is_wall = False
if y == 1 and x not in [0, 1, 9]:
is_wall = True
if y == 2 and x not in [9]:
is_wall = True
if y == 3 and x not in [0, 9]:
is_wall = True
if y == 4 and x not in [0, 1, 9]:
is_wall = True
if y == 5 and x not in [9]:
is_wall = True
if y == 6 and x not in [0, 9]:
is_wall = True
if y == 7 and x not in [9]:
is_wall = True
if y == 8 and x not in [0, 9]:
is_wall = True
cell = Cell(x, y, is_wall=is_wall)
cell.is_start = is_start
cell.is_exit = is_exit
maze1.cells[y][x] = cell
if is_start:
maze1.start = cell
if is_exit:
maze1.exit = cell
save_maze_to_file(maze1, "mazes/tiny.txt")
# 2. Средний лабиринт 15x15 (medium.txt)
maze2 = Maze(15, 15)
for y in range(15):
for x in range(15):
is_start = (x == 0 and y == 0)
is_exit = (x == 14 and y == 14)
is_wall = (x % 3 == 1 and y % 2 == 0) and not is_start and not is_exit
cell = Cell(x, y, is_wall=is_wall)
cell.is_start = is_start
cell.is_exit = is_exit
maze2.cells[y][x] = cell
if is_start:
maze2.start = cell
if is_exit:
maze2.exit = cell
save_maze_to_file(maze2, "mazes/medium.txt")
# 3. Большой лабиринт 30x30 (large.txt)
maze3 = Maze(30, 30)
for y in range(30):
for x in range(30):
is_start = (x == 0 and y == 0)
is_exit = (x == 29 and y == 29)
is_wall = (x % 2 == 0 and y % 3 == 0) and not is_start and not is_exit
cell = Cell(x, y, is_wall=is_wall)
cell.is_start = is_start
cell.is_exit = is_exit
maze3.cells[y][x] = cell
if is_start:
maze3.start = cell
if is_exit:
maze3.exit = cell
save_maze_to_file(maze3, "mazes/large.txt")
# 4. Пустой лабиринт 15x15 (empty.txt)
maze4 = Maze(15, 15)
for y in range(15):
for x in range(15):
is_start = (x == 0 and y == 0)
is_exit = (x == 14 and y == 14)
cell = Cell(x, y, is_wall=False)
cell.is_start = is_start
cell.is_exit = is_exit
maze4.cells[y][x] = cell
if is_start:
maze4.start = cell
if is_exit:
maze4.exit = cell
save_maze_to_file(maze4, "mazes/empty.txt")
# 5. Лабиринт без выхода 10x10 (no_exit.txt)
maze5 = Maze(10, 10)
for y in range(10):
for x in range(10):
is_start = (x == 0 and y == 0)
is_exit = (x == 9 and y == 9)
is_wall = (x > 0 and y > 0) and not is_start
cell = Cell(x, y, is_wall=is_wall)
cell.is_start = is_start
cell.is_exit = is_exit
maze5.cells[y][x] = cell
if is_start:
maze5.start = cell
if is_exit:
maze5.exit = cell
save_maze_to_file(maze5, "mazes/no_exit.txt")
def print_analysis():
"""Вывод анализа эффективности алгоритмов"""
print(" АНАЛИЗ ЭФФЕКТИВНОСТИ АЛГОРИТМОВ ПОИСКА ПУТИ")
print("""
BFS (Поиск в ширину):
- Всегда находит КРАТЧАЙШИЙ путь
- Сложность O(V+E)
- Много памяти (очередь)
- Лучший выбор для поиска минимального пути
DFS (Поиск в глубину):
- НЕ гарантирует кратчайший путь
- Сложность O(V+E)
- Мало памяти
- Быстрый, но путь может быть очень длинным
- Хорош для проверки существования пути
A* (Алгоритм с эвристикой):
- Находит КРАТЧАЙШИЙ путь (при допустимой эвристике)
- Эвристика: манхэттенское расстояние |x1-x2| + |y1-y2|
- Быстрее BFS благодаря целенаправленному поиску
- Лучший выбор для больших запутанных лабиринтов
""")
print("""
ВЛИЯНИЕ ТИПА ЛАБИРИНТА:
Простой лабиринт (tiny.txt):
- Все алгоритмы работают быстро
- Разница в скорости незначительна
- BFS и A* находят оптимальный путь
- DFS может найти более длинный путь
Средний лабиринт (medium.txt):
- A* начинает показывать преимущество
- BFS исследует больше клеток
- DFS может заблудиться в тупиках
Большой лабиринт (large.txt):
- A* значительно быстрее BFS
- DFS сильно проигрывает на запутанных лабиринтах
Пустой лабиринт (empty.txt):
- A* значительно быстрее BFS
- DFS быстро уходит вглубь, но путь неоптимальный
Лабиринт без выхода (no_exit.txt):
- Все алгоритмы обходят все достижимые клетки
- Возвращают пустой путь
""")
print("""
ВЫВОДЫ ПО ПАТТЕРНАМ:
BUILDER:
- Легко добавить новый формат
- Код загрузки не смешивается с логикой лабиринта
STRATEGY:
- Алгоритмы можно менять во время выполнения
- Легко добавить новый алгоритм
- Код не дублируется
OBSERVER:
- Отделяет визуализацию от логики
- Легко добавить GUI или логирование
- Наблюдателей можно добавлять динамически
COMMAND:
- Позволяет выполнять и отменять действия
- Удобно для пошагового управления
- История команд позволяет сохранять/загружать состояние
""")
def main():
print("ЛАБОРАТОРНАЯ РАБОТА №2: ПОИСК ВЫХОДА ИЗ ЛАБИРИНТА")
print("Паттерны: Builder, Strategy, Observer, Command")
# Создание тестовых лабиринтов
print("\n1. СОЗДАНИЕ ТЕСТОВЫХ ЛАБИРИНТОВ...")
create_test_mazes()
print(" Созданы лабиринты: tiny, medium, large, empty, no_exit")
# Список файлов лабиринтов
maze_files = [
"mazes/tiny.txt",
"mazes/medium.txt",
"mazes/large.txt",
"mazes/empty.txt",
"mazes/no_exit.txt"
]
strategies = [BFSStrategy(), DFSStrategy(), AStarStrategy()]
all_results = []
# Демонстрация Observer и Command на первом лабиринте
print("\n2. ДЕМОНСТРАЦИЯ РАБОТЫ ПРОГРАММЫ")
builder = TextFileMazeBuilder()
maze = builder.build_from_file("mazes/tiny.txt")
print("Лабиринт tiny.txt:")
print(maze)
logger = ConsoleLogger()
solver_with_observer = MazeSolverWithObserver(maze, strategies[0], observers=[logger])
path, _ = solver_with_observer.solve()
interactive_move_demo(maze, path)
# Эксперименты
print("3. ЭКСПЕРИМЕНТАЛЬНОЕ СРАВНЕНИЕ АЛГОРИТМОВ")
for maze_file in maze_files:
try:
results = test_single_maze(maze_file, strategies)
for r in results:
r['maze'] = maze_file
all_results.append(r)
print(f"\n{maze_file}:")
for r in results:
print(f" {r['algorithm']}: {r['avg_time_ms']:.3f} мс, "
f"посещено {r['avg_visited']:.1f}, путь {r['avg_path_len']:.1f}")
except Exception as e:
print(f"Ошибка при обработке {maze_file}: {e}")
# Сохранение CSV
if all_results:
os.makedirs("results", exist_ok=True)
with open('results/all_results.csv', 'w', newline='', encoding='utf-8') as f:
writer = csv.DictWriter(f, fieldnames=['maze', 'algorithm', 'avg_time_ms', 'avg_visited', 'avg_path_len'])
writer.writeheader()
writer.writerows(all_results)
print("\nРезультаты сохранены в results/all_results.csv")
# Построение графиков для каждого лабиринта
df = pd.DataFrame(all_results)
for maze in df['maze'].unique():
subset = df[df['maze'] == maze]
plt.figure(figsize=(8, 5))
bars = plt.bar(subset['algorithm'], subset['avg_time_ms'], color=['blue', 'green', 'red'])
plt.title(f'Сравнение алгоритмов на лабиринте {maze}')
plt.ylabel('Среднее время (мс)')
plt.xlabel('Алгоритм')
for bar, val in zip(bars, subset['avg_time_ms']):
plt.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 0.5,
f'{val:.3f}', ha='center', va='bottom', fontsize=9)
plt.tight_layout()
filename = f'results/plot_{maze.replace("/", "_")}.png'
plt.savefig(filename)
plt.close()
print(f" Сохранён график: {filename}")
# Сводный график
plt.figure(figsize=(12, 6))
for alg in df['algorithm'].unique():
subset = df[df['algorithm'] == alg]
plt.plot(subset['maze'], subset['avg_time_ms'], marker='o', linewidth=2, markersize=8, label=alg)
plt.xlabel('Лабиринт')
plt.ylabel('Среднее время (мс)')
plt.title('Сравнение эффективности алгоритмов на разных лабиринтах')
plt.legend()
plt.grid(True, alpha=0.3)
plt.xticks(rotation=45)
plt.tight_layout()
plt.savefig('results/summary_comparison.png')
plt.show()
print("\nГрафики сохранены в папке results/")
print(" - plot_*.png - графики для каждого лабиринта")
print(" - summary_comparison.png - сводный график")
print_analysis()
print("ЭКСПЕРИМЕНТ ЗАВЕРШЁН")
if __name__ == "__main__":
main()

View File

@ -0,0 +1,15 @@
S
E

View File

@ -0,0 +1,30 @@
S # # # # # # # # # # # # # #
# # # # # # # # # # # # # # #
# # # # # # # # # # # # # # #
# # # # # # # # # # # # # # #
# # # # # # # # # # # # # # #
# # # # # # # # # # # # # # #
# # # # # # # # # # # # # # #
# # # # # # # # # # # # # # #
# # # # # # # # # # # # # # #
# # # # # # # # # # # # # # #
E

View File

@ -0,0 +1,15 @@
S# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #E

View File

@ -0,0 +1,10 @@
S
#########
#########
#########
#########
#########
#########
#########
#########
#########

View File

@ -0,0 +1,10 @@
S E
#######
#########
########
#######
#########
########
#########
########

View File

@ -0,0 +1,13 @@
maze,algorithm,avg_time_ms,avg_visited,avg_path_len
mazes/tiny.txt,BFSStrategy,0.051900000471505336,12.0,10.0
mazes/tiny.txt,DFSStrategy,0.040920000174082816,9.0,10.0
mazes/tiny.txt,AStarStrategy,0.07476000027963892,10.0,10.0
mazes/medium.txt,BFSStrategy,1.2075799993908731,185.0,29.0
mazes/medium.txt,DFSStrategy,0.999220000448986,119.0,113.0
mazes/medium.txt,AStarStrategy,1.3635600000270642,176.0,29.0
mazes/large.txt,BFSStrategy,3.158179999809363,751.0,59.0
mazes/large.txt,DFSStrategy,3.9773199998307973,624.0,583.0
mazes/large.txt,AStarStrategy,3.022899999996298,719.0,59.0
mazes/empty.txt,BFSStrategy,0.43741999979829416,225.0,29.0
mazes/empty.txt,DFSStrategy,0.5842599995958153,224.0,225.0
mazes/empty.txt,AStarStrategy,0.6680599992250791,225.0,29.0
1 maze algorithm avg_time_ms avg_visited avg_path_len
2 mazes/tiny.txt BFSStrategy 0.051900000471505336 12.0 10.0
3 mazes/tiny.txt DFSStrategy 0.040920000174082816 9.0 10.0
4 mazes/tiny.txt AStarStrategy 0.07476000027963892 10.0 10.0
5 mazes/medium.txt BFSStrategy 1.2075799993908731 185.0 29.0
6 mazes/medium.txt DFSStrategy 0.999220000448986 119.0 113.0
7 mazes/medium.txt AStarStrategy 1.3635600000270642 176.0 29.0
8 mazes/large.txt BFSStrategy 3.158179999809363 751.0 59.0
9 mazes/large.txt DFSStrategy 3.9773199998307973 624.0 583.0
10 mazes/large.txt AStarStrategy 3.022899999996298 719.0 59.0
11 mazes/empty.txt BFSStrategy 0.43741999979829416 225.0 29.0
12 mazes/empty.txt DFSStrategy 0.5842599995958153 224.0 225.0
13 mazes/empty.txt AStarStrategy 0.6680599992250791 225.0 29.0

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

View File

@ -0,0 +1,506 @@
import time
import random
import csv
import sys
import matplotlib.pyplot as plt
import numpy as np
sys.setrecursionlimit(20000)
REPEATS = 5
N = 10000
def ll_insert(head, name, phone):
current = head
prev = None
while current is not None:
if current['name'] == name:
current['phone'] = phone
return head
prev = current
current = current['next']
new_node = {'name': name, 'phone': phone, 'next': None}
if prev is None:
return new_node
else:
prev['next'] = new_node
return head
def ll_find(head, name):
current = head
while current is not None:
if current['name'] == name:
return current['phone']
current = current['next']
return None
def ll_delete(head, name):
if head is None:
return None
if head['name'] == name:
return head['next']
current = head
while current['next'] is not None:
if current['next']['name'] == name:
current['next'] = current['next']['next']
return head
current = current['next']
return head
def ll_collect_all(head):
records = []
current = head
while current is not None:
records.append((current['name'], current['phone']))
current = current['next']
records.sort(key=lambda x: x[0])
return records
def hash_function(name, size):
total = 0
for ch in name:
total = (total * 31 + ord(ch)) % size
return total
def ht_create(size=2000):
return [None] * size
def ht_insert(buckets, name, phone):
idx = hash_function(name, len(buckets))
buckets[idx] = ll_insert(buckets[idx], name, phone)
def ht_find(buckets, name):
idx = hash_function(name, len(buckets))
return ll_find(buckets[idx], name)
def ht_delete(buckets, name):
idx = hash_function(name, len(buckets))
buckets[idx] = ll_delete(buckets[idx], name)
def ht_collect_all(buckets):
all_records = []
for bucket in buckets:
current = bucket
while current is not None:
all_records.append((current['name'], current['phone']))
current = current['next']
all_records.sort(key=lambda x: x[0])
return all_records
def bst_insert(root, name, phone):
new_node = {'name': name, 'phone': phone, 'left': None, 'right': None}
if root is None:
return new_node
current = root
while True:
if name < current['name']:
if current['left'] is None:
current['left'] = new_node
break
current = current['left']
elif name > current['name']:
if current['right'] is None:
current['right'] = new_node
break
current = current['right']
else:
current['phone'] = phone
break
return root
def bst_find(root, name):
current = root
while current is not None:
if name < current['name']:
current = current['left']
elif name > current['name']:
current = current['right']
else:
return current['phone']
return None
def bst_find_min(node):
while node['left'] is not None:
node = node['left']
return node
def bst_delete(root, name):
parent = None
current = root
while current is not None and current['name'] != name:
parent = current
if name < current['name']:
current = current['left']
else:
current = current['right']
if current is None:
return root
if current['left'] is None and current['right'] is None:
if parent is None:
return None
if parent['left'] is current:
parent['left'] = None
else:
parent['right'] = None
return root
if current['left'] is None:
if parent is None:
return current['right']
if parent['left'] is current:
parent['left'] = current['right']
else:
parent['right'] = current['right']
return root
if current['right'] is None:
if parent is None:
return current['left']
if parent['left'] is current:
parent['left'] = current['left']
else:
parent['right'] = current['left']
return root
succ_parent = current
succ = current['right']
while succ['left'] is not None:
succ_parent = succ
succ = succ['left']
current['name'] = succ['name']
current['phone'] = succ['phone']
if succ_parent['left'] is succ:
succ_parent['left'] = succ['right']
else:
succ_parent['right'] = succ['right']
return root
def bst_inorder_collect(root, records=None):
if records is None:
records = []
if root is not None:
bst_inorder_collect(root['left'], records)
records.append((root['name'], root['phone']))
bst_inorder_collect(root['right'], records)
return records
def generate_records(N=10000):
records = []
for i in range(N):
name = f"User_{i:05d}"
phone = f"+7-999-{random.randint(1000000, 9999999)}"
records.append((name, phone))
return records
def measure_insertion(struct_type, records):
times = []
for _ in range(REPEATS):
if struct_type == 'll':
head = None
start = time.perf_counter()
for name, phone in records:
head = ll_insert(head, name, phone)
end = time.perf_counter()
elif struct_type == 'ht':
buckets = ht_create(2000)
start = time.perf_counter()
for name, phone in records:
ht_insert(buckets, name, phone)
end = time.perf_counter()
else:
root = None
start = time.perf_counter()
for name, phone in records:
root = bst_insert(root, name, phone)
end = time.perf_counter()
times.append(end - start)
return times
def build_structure(struct_type, records):
if struct_type == 'll':
head = None
for name, phone in records:
head = ll_insert(head, name, phone)
return head
elif struct_type == 'ht':
buckets = ht_create(2000)
for name, phone in records:
ht_insert(buckets, name, phone)
return buckets
else:
root = None
for name, phone in records:
root = bst_insert(root, name, phone)
return root
def measure_search(struct_type, structure, records):
times = []
N_records = len(records)
for _ in range(REPEATS):
indices = random.sample(range(N_records), 100)
existing_names = [records[i][0] for i in indices]
missing_names = [f"None_{i}" for i in range(10)]
search_names = existing_names + missing_names
random.shuffle(search_names)
start = time.perf_counter()
if struct_type == 'll':
for name in search_names:
ll_find(structure, name)
elif struct_type == 'ht':
for name in search_names:
ht_find(structure, name)
else:
for name in search_names:
bst_find(structure, name)
times.append(time.perf_counter() - start)
return times
def measure_deletion(struct_type, records):
times = []
N_records = len(records)
for _ in range(REPEATS):
indices = random.sample(range(N_records), 50)
delete_names = [records[i][0] for i in indices]
if struct_type == 'll':
head = None
for name, phone in records:
head = ll_insert(head, name, phone)
start = time.perf_counter()
for name in delete_names:
head = ll_delete(head, name)
end = time.perf_counter()
elif struct_type == 'ht':
buckets = ht_create(2000)
for name, phone in records:
ht_insert(buckets, name, phone)
start = time.perf_counter()
for name in delete_names:
ht_delete(buckets, name)
end = time.perf_counter()
else:
root = None
for name, phone in records:
root = bst_insert(root, name, phone)
start = time.perf_counter()
for name in delete_names:
root = bst_delete(root, name)
end = time.perf_counter()
times.append(end - start)
return times
def plot_bar_charts(insert_data, search_data, delete_data):
"""Построение столбчатых диаграмм"""
structures = ['ll', 'ht', 'bst']
labels = ['Связный список', 'Хеш-таблица', 'Двоичное дерево']
mode_labels = ['Случайный порядок', 'Отсортированный порядок']
colors = ['skyblue', 'salmon']
x = np.arange(len(structures))
width = 0.35
# График вставки
fig1, ax1 = plt.subplots(figsize=(10, 6))
means_sh = [sum(insert_data[s]['shuffled'])/len(insert_data[s]['shuffled']) for s in structures]
means_so = [sum(insert_data[s]['sorted'])/len(insert_data[s]['sorted']) for s in structures]
rects1 = ax1.bar(x - width/2, means_sh, width, label=mode_labels[0], color=colors[0])
rects2 = ax1.bar(x + width/2, means_so, width, label=mode_labels[1], color=colors[1])
ax1.set_ylabel('Время (секунды)')
ax1.set_title('Вставка всех записей (10000 шт.)')
ax1.set_xticks(x)
ax1.set_xticklabels(labels)
ax1.legend()
ax1.set_yscale('log')
for rect in rects1 + rects2:
h = rect.get_height()
ax1.annotate(f'{h:.4f}', xy=(rect.get_x() + rect.get_width()/2, h),
xytext=(0, 3), textcoords="offset points", ha='center', va='bottom', fontsize=8)
plt.tight_layout()
plt.savefig('insert_comparison.png', dpi=150)
plt.show()
print(" График вставки сохранён: insert_comparison.png")
# График поиска
fig2, ax2 = plt.subplots(figsize=(10, 6))
means_sh = [sum(search_data[s]['shuffled'])/len(search_data[s]['shuffled']) for s in structures]
means_so = [sum(search_data[s]['sorted'])/len(search_data[s]['sorted']) for s in structures]
rects1 = ax2.bar(x - width/2, means_sh, width, label=mode_labels[0], color=colors[0])
rects2 = ax2.bar(x + width/2, means_so, width, label=mode_labels[1], color=colors[1])
ax2.set_ylabel('Время (секунды)')
ax2.set_title('Поиск (100 существующих + 10 отсутствующих)')
ax2.set_xticks(x)
ax2.set_xticklabels(labels)
ax2.legend()
for rect in rects1 + rects2:
h = rect.get_height()
ax2.annotate(f'{h:.6f}', xy=(rect.get_x() + rect.get_width()/2, h),
xytext=(0, 3), textcoords="offset points", ha='center', va='bottom', fontsize=8)
plt.tight_layout()
plt.savefig('search_comparison.png', dpi=150)
plt.show()
print(" График поиска сохранён: search_comparison.png")
# График удаления
fig3, ax3 = plt.subplots(figsize=(10, 6))
means_sh = [sum(delete_data[s]['shuffled'])/len(delete_data[s]['shuffled']) for s in structures]
means_so = [sum(delete_data[s]['sorted'])/len(delete_data[s]['sorted']) for s in structures]
rects1 = ax3.bar(x - width/2, means_sh, width, label=mode_labels[0], color=colors[0])
rects2 = ax3.bar(x + width/2, means_so, width, label=mode_labels[1], color=colors[1])
ax3.set_ylabel('Время (секунды)')
ax3.set_title('Удаление 50 случайных записей')
ax3.set_xticks(x)
ax3.set_xticklabels(labels)
ax3.legend()
for rect in rects1 + rects2:
h = rect.get_height()
ax3.annotate(f'{h:.6f}', xy=(rect.get_x() + rect.get_width()/2, h),
xytext=(0, 3), textcoords="offset points", ha='center', va='bottom', fontsize=8)
plt.tight_layout()
plt.savefig('delete_comparison.png', dpi=150)
plt.show()
print(" График удаления сохранён: delete_comparison.png")
def plot_attempts_graphs(data, op_name, op_title):
"""Построение графиков по 5 попыткам"""
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))
struct_config = [
('ll', 'Связный список', 'red', 'o'),
('ht', 'Хеш-таблица', 'green', 's'),
('bst', 'Двоичное дерево', 'blue', '^')
]
# Случайный порядок
for struct, label, color, marker in struct_config:
times = data[struct]['shuffled']
x = range(1, len(times) + 1)
ax1.plot(x, times, marker=marker, color=color, label=label,
linestyle='--', linewidth=1)
ax1.scatter(x, times, color=color, s=60, zorder=5)
ax1.set_xlabel('Номер попытки')
ax1.set_ylabel('Время (секунды)')
ax1.set_title(f'{op_title} случайный порядок')
ax1.legend()
ax1.grid(True, linestyle=':', alpha=0.7)
# Отсортированный порядок
for struct, label, color, marker in struct_config:
times = data[struct]['sorted']
x = range(1, len(times) + 1)
ax2.plot(x, times, marker=marker, color=color, label=label,
linestyle='--', linewidth=1)
ax2.scatter(x, times, color=color, s=60, zorder=5)
ax2.set_xlabel('Номер попытки')
ax2.set_ylabel('Время (секунды)')
ax2.set_title(f'{op_title} отсортированный порядок')
ax2.legend()
ax2.grid(True, linestyle=':', alpha=0.7)
plt.tight_layout()
plt.savefig(f'{op_name}_5attempts.png', dpi=150)
plt.show()
print(f" График {op_name}_5attempts.png сохранён")
def main():
print("ЛАБОРАТОРНАЯ РАБОТА №1: СРАВНЕНИЕ СТРУКТУР ДАННЫХ")
print("\n1. Генерация тестовых данных...")
records = generate_records(N)
random.shuffle(records)
records_sorted = sorted(records, key=lambda x: x[0])
print(f" Сгенерировано {N} записей")
results = []
struct_names = {'ll': 'Связный список', 'ht': 'Хеш-таблица', 'bst': 'Двоичное дерево'}
mode_names = {'shuffled': 'случайный', 'sorted': 'отсортированный'}
op_names = {'insert': 'Вставка всех записей', 'find': 'Поиск записей', 'delete': 'Удаление записей'}
insert_data = {'ll': {}, 'ht': {}, 'bst': {}}
search_data = {'ll': {}, 'ht': {}, 'bst': {}}
delete_data = {'ll': {}, 'ht': {}, 'bst': {}}
# Вставка
print("\n2. Тестирование ВСТАВКИ (10000 записей):")
for struct in ['ll', 'ht', 'bst']:
print(f"\n {struct_names[struct]}:")
times_sh = measure_insertion(struct, records)
times_so = measure_insertion(struct, records_sorted)
insert_data[struct]['shuffled'] = times_sh
insert_data[struct]['sorted'] = times_so
print(f" случайный: {[round(t,6) for t in times_sh]}, среднее = {sum(times_sh)/len(times_sh):.6f}")
print(f" отсортированный: {[round(t,6) for t in times_so]}, среднее = {sum(times_so)/len(times_so):.6f}")
results.append([struct_names[struct], mode_names['shuffled'], op_names['insert'], sum(times_sh)/len(times_sh)] + times_sh)
results.append([struct_names[struct], mode_names['sorted'], op_names['insert'], sum(times_so)/len(times_so)] + times_so)
# Поиск
print("\n3. Тестирование ПОИСКА (110 запросов):")
for struct in ['ll', 'ht', 'bst']:
print(f"\n {struct_names[struct]}:")
structure_sh = build_structure(struct, records)
times_find_sh = measure_search(struct, structure_sh, records)
search_data[struct]['shuffled'] = times_find_sh
print(f" случайный: {[round(t,6) for t in times_find_sh]}, среднее = {sum(times_find_sh)/len(times_find_sh):.6f}")
results.append([struct_names[struct], mode_names['shuffled'], op_names['find'], sum(times_find_sh)/len(times_find_sh)] + times_find_sh)
structure_so = build_structure(struct, records_sorted)
times_find_so = measure_search(struct, structure_so, records_sorted)
search_data[struct]['sorted'] = times_find_so
print(f" отсортированный: {[round(t,6) for t in times_find_so]}, среднее = {sum(times_find_so)/len(times_find_so):.6f}")
results.append([struct_names[struct], mode_names['sorted'], op_names['find'], sum(times_find_so)/len(times_find_so)] + times_find_so)
# Удаление
print("\n4. Тестирование УДАЛЕНИЯ (50 записей):")
for struct in ['ll', 'ht', 'bst']:
print(f"\n {struct_names[struct]}:")
times_del_sh = measure_deletion(struct, records)
delete_data[struct]['shuffled'] = times_del_sh
print(f" случайный: {[round(t,6) for t in times_del_sh]}, среднее = {sum(times_del_sh)/len(times_del_sh):.6f}")
results.append([struct_names[struct], mode_names['shuffled'], op_names['delete'], sum(times_del_sh)/len(times_del_sh)] + times_del_sh)
times_del_so = measure_deletion(struct, records_sorted)
delete_data[struct]['sorted'] = times_del_so
print(f" отсортированный: {[round(t,6) for t in times_del_so]}, среднее = {sum(times_del_so)/len(times_del_so):.6f}")
results.append([struct_names[struct], mode_names['sorted'], op_names['delete'], sum(times_del_so)/len(times_del_so)] + times_del_so)
# Сохранение CSV
print("\n5. Сохранение результатов в CSV...")
with open("phonebook_results.csv", "w", newline="", encoding="utf-8") as f:
writer = csv.writer(f)
writer.writerow(['Структура', 'Режим', 'Операция', 'Среднее', 'Замер1', 'Замер2', 'Замер3', 'Замер4', 'Замер5'])
writer.writerows(results)
print(" CSV-файл сохранён: phonebook_results.csv")
# Сводная таблица
print("СВОДНАЯ ТАБЛИЦА РЕЗУЛЬТАТОВ (средние значения)")
print(f"{'Структура':<20} {'Режим':<15} {'Вставка(с)':<12} {'Поиск(с)':<12} {'Удаление(с)':<12}")
print("-" * 70)
for struct in ['ll', 'ht', 'bst']:
for mode in ['shuffled', 'sorted']:
ins_avg = sum(insert_data[struct][mode]) / REPEATS
sea_avg = sum(search_data[struct][mode]) / REPEATS
del_avg = sum(delete_data[struct][mode]) / REPEATS
mode_rus = "случайный" if mode == 'shuffled' else "отсортированный"
print(f"{struct_names[struct]:<20} {mode_rus:<15} {ins_avg:<12.6f} {sea_avg:<12.6f} {del_avg:<12.6f}")
# Построение графиков
print("\n6. Построение графиков...")
try:
plot_bar_charts(insert_data, search_data, delete_data)
plot_attempts_graphs(insert_data, 'insert', 'Вставка')
plot_attempts_graphs(search_data, 'search', 'Поиск')
plot_attempts_graphs(delete_data, 'delete', 'Удаление')
print("\n Все графики успешно сохранены")
except Exception as e:
print(f" Ошибка при построении графиков: {e}")
if __name__ == "__main__":
main()

View File

@ -0,0 +1,19 @@
Структура,Режим,Операция,Среднее,Замер1,Замер2,Замер3,Замер4,Замер5
Связный список,случайный,Вставка всех записей,3.7577814000003853,3.7547549000009894,4.714954399998533,3.444005000001198,3.4518932000028144,3.423299499998393
Связный список,отсортированный,Вставка всех записей,3.1234334600005242,3.1089575000005425,3.2897176000005857,3.2068743000018003,2.992147000000841,3.0194708999988507
Хеш-таблица,случайный,Вставка всех записей,0.01707952000069781,0.019611799998529023,0.016021200000977842,0.016751300001487834,0.0166919000002963,0.01632140000219806
Хеш-таблица,отсортированный,Вставка всех записей,0.015740299999743003,0.015199699999357108,0.015383899997686967,0.015607799999997951,0.01595409999936237,0.01655600000231061
Двоичное дерево,случайный,Вставка всех записей,0.03259613999980502,0.020392300000821706,0.02241409999987809,0.08032649999950081,0.02091419999851496,0.018933600000309525
Двоичное дерево,отсортированный,Вставка всех записей,5.587171799999487,6.069779999997991,5.295106199999282,5.2516906000018935,5.968475800000306,5.350806399997964
Связный список,случайный,Поиск записей,0.04256463999990956,0.039015399997879285,0.047764200000528945,0.04185150000193971,0.04288379999707104,0.04130830000212882
Связный список,отсортированный,Поиск записей,0.03533787999913329,0.03831979999813484,0.03551620000143885,0.033227799998712726,0.034846200000174576,0.034779399997205473
Хеш-таблица,случайный,Поиск записей,0.00018929999932879583,0.00021239999841782264,0.00018679999993764795,0.00018500000078347512,0.00017979999756789766,0.00018249999993713573
Хеш-таблица,отсортированный,Поиск записей,0.00019069999980274587,0.00019740000061574392,0.00019410000095376745,0.0001992999968933873,0.00017970000044442713,0.0001830000001064036
Двоичное дерево,случайный,Поиск записей,0.00021590000033029356,0.00024810000104480423,0.0002229999990959186,0.00021040000137872994,0.00020560000120894983,0.00019239999892306514
Двоичное дерево,отсортированный,Поиск записей,0.04444347999960883,0.0411448000013479,0.04647309999927529,0.045536099998571444,0.04352210000070045,0.04554129999814904
Связный список,случайный,Удаление записей,0.024450240000442137,0.029107700000167824,0.026141900001675822,0.024585999999544583,0.02166159999978845,0.020754000001034
Связный список,отсортированный,Удаление записей,0.0217564400008996,0.018747900001471862,0.026504800000111572,0.020920700000715442,0.022756800000934163,0.01985200000126497
Хеш-таблица,случайный,Удаление записей,8.250000100815669e-05,7.800000093993731e-05,7.380000170087442e-05,7.700000060140155e-05,7.059999916236848e-05,0.00011310000263620168
Хеш-таблица,отсортированный,Удаление записей,7.353999899351038e-05,7.029999687802047e-05,7.48999991628807e-05,7.550000009359792e-05,6.959999882383272e-05,7.74000000092201e-05
Двоичное дерево,случайный,Удаление записей,0.00011504000067361631,0.00011150000136694871,0.00010340000153519213,9.219999992637895e-05,0.0001550999986648094,0.00011300000187475234
Двоичное дерево,отсортированный,Удаление записей,0.025109319999319268,0.01966069999980391,0.02953200000047218,0.0236769999974058,0.02822290000040084,0.02445399999851361
1 Структура Режим Операция Среднее Замер1 Замер2 Замер3 Замер4 Замер5
2 Связный список случайный Вставка всех записей 3.7577814000003853 3.7547549000009894 4.714954399998533 3.444005000001198 3.4518932000028144 3.423299499998393
3 Связный список отсортированный Вставка всех записей 3.1234334600005242 3.1089575000005425 3.2897176000005857 3.2068743000018003 2.992147000000841 3.0194708999988507
4 Хеш-таблица случайный Вставка всех записей 0.01707952000069781 0.019611799998529023 0.016021200000977842 0.016751300001487834 0.0166919000002963 0.01632140000219806
5 Хеш-таблица отсортированный Вставка всех записей 0.015740299999743003 0.015199699999357108 0.015383899997686967 0.015607799999997951 0.01595409999936237 0.01655600000231061
6 Двоичное дерево случайный Вставка всех записей 0.03259613999980502 0.020392300000821706 0.02241409999987809 0.08032649999950081 0.02091419999851496 0.018933600000309525
7 Двоичное дерево отсортированный Вставка всех записей 5.587171799999487 6.069779999997991 5.295106199999282 5.2516906000018935 5.968475800000306 5.350806399997964
8 Связный список случайный Поиск записей 0.04256463999990956 0.039015399997879285 0.047764200000528945 0.04185150000193971 0.04288379999707104 0.04130830000212882
9 Связный список отсортированный Поиск записей 0.03533787999913329 0.03831979999813484 0.03551620000143885 0.033227799998712726 0.034846200000174576 0.034779399997205473
10 Хеш-таблица случайный Поиск записей 0.00018929999932879583 0.00021239999841782264 0.00018679999993764795 0.00018500000078347512 0.00017979999756789766 0.00018249999993713573
11 Хеш-таблица отсортированный Поиск записей 0.00019069999980274587 0.00019740000061574392 0.00019410000095376745 0.0001992999968933873 0.00017970000044442713 0.0001830000001064036
12 Двоичное дерево случайный Поиск записей 0.00021590000033029356 0.00024810000104480423 0.0002229999990959186 0.00021040000137872994 0.00020560000120894983 0.00019239999892306514
13 Двоичное дерево отсортированный Поиск записей 0.04444347999960883 0.0411448000013479 0.04647309999927529 0.045536099998571444 0.04352210000070045 0.04554129999814904
14 Связный список случайный Удаление записей 0.024450240000442137 0.029107700000167824 0.026141900001675822 0.024585999999544583 0.02166159999978845 0.020754000001034
15 Связный список отсортированный Удаление записей 0.0217564400008996 0.018747900001471862 0.026504800000111572 0.020920700000715442 0.022756800000934163 0.01985200000126497
16 Хеш-таблица случайный Удаление записей 8.250000100815669e-05 7.800000093993731e-05 7.380000170087442e-05 7.700000060140155e-05 7.059999916236848e-05 0.00011310000263620168
17 Хеш-таблица отсортированный Удаление записей 7.353999899351038e-05 7.029999687802047e-05 7.48999991628807e-05 7.550000009359792e-05 6.959999882383272e-05 7.74000000092201e-05
18 Двоичное дерево случайный Удаление записей 0.00011504000067361631 0.00011150000136694871 0.00010340000153519213 9.219999992637895e-05 0.0001550999986648094 0.00011300000187475234
19 Двоичное дерево отсортированный Удаление записей 0.025109319999319268 0.01966069999980391 0.02953200000047218 0.0236769999974058 0.02822290000040084 0.02445399999851361

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.