Merge pull request '[1] 1-st exercise' (#342) from agafonovdm/2026-rff_mp:agafonovdm into develop

Reviewed-on: #342
This commit is contained in:
IvanBoy 2026-05-30 11:39:18 +00:00
commit aeb797e271
22 changed files with 1284 additions and 0 deletions

View File

@ -0,0 +1,292 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import time
import random
import csv
import sys
sys.setrecursionlimit(30000)
def ll_create_node(name, phone):
return {'name': name, 'phone': phone, 'next': None}
def ll_insert(head, name, phone):
if head is None:
return ll_create_node(name, phone)
if head['name'] == name:
head['phone'] = phone
return head
current = head
while current['next'] is not None:
if current['next']['name'] == name:
current['next']['phone'] = phone
return head
current = current['next']
current['next'] = ll_create_node(name, phone)
return head
def ll_find(head, name):
current = head
while current is not None:
if current['name'] == name:
return current['phone']
current = current['next']
return None
def ll_delete(head, name):
if head is None:
return None
if head['name'] == name:
return head['next']
current = head
while current['next'] is not None:
if current['next']['name'] == name:
current['next'] = current['next']['next']
return head
current = current['next']
return head
def ll_list_all(head):
records = []
current = head
while current is not None:
records.append((current['name'], current['phone']))
current = current['next']
records.sort(key=lambda x: x[0])
return records
def hash_function(name, table_size):
return sum(ord(c) for c in name) % table_size
def ht_create_table(size=2000):
return [None] * size
def ht_insert(table, name, phone):
index = hash_function(name, len(table))
table[index] = ll_insert(table[index], name, phone)
def ht_find(table, name):
index = hash_function(name, len(table))
return ll_find(table[index], name)
def ht_delete(table, name):
index = hash_function(name, len(table))
table[index] = ll_delete(table[index], name)
def ht_list_all(table):
all_records = []
for bucket in table:
if bucket is not None:
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_create_node(name, phone):
return {'name': name, 'phone': phone, 'left': None, 'right': None}
def bst_insert(root, name, phone):
if root is None:
return bst_create_node(name, phone)
current = root
while True:
if name < current['name']:
if current['left'] is None:
current['left'] = bst_create_node(name, phone)
break
else:
current = current['left']
elif name > current['name']:
if current['right'] is None:
current['right'] = bst_create_node(name, phone)
break
else:
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):
current = node
while current['left'] is not None:
current = current['left']
return current
def bst_delete(root, name):
if root is None:
return None
parent = None
current = root
while current is not None and current['name'] != name:
parent = current
if name < current['name']:
current = current['left']
else:
current = current['right']
if current is None:
return root
if current['left'] is None or current['right'] is None:
if current['left'] is not None:
child = current['left']
else:
child = current['right']
if parent is None:
return child
if parent['left'] == current:
parent['left'] = child
else:
parent['right'] = child
else:
successor_parent = current
successor = current['right']
while successor['left'] is not None:
successor_parent = successor
successor = successor['left']
current['name'] = successor['name']
current['phone'] = successor['phone']
if successor_parent['left'] == successor:
successor_parent['left'] = successor['right']
else:
successor_parent['right'] = successor['right']
return root
def bst_list_all(root):
records = []
stack = []
current = root
while stack or current is not None:
while current is not None:
stack.append(current)
current = current['left']
current = stack.pop()
records.append((current['name'], current['phone']))
current = current['right']
return records
def generate_data(n=10000):
records = [(f"User_{i:05d}", f"+7-999-{i:06d}") for i in range(n)]
records_shuffled = records.copy()
random.shuffle(records_shuffled)
records_sorted = sorted(records, key=lambda x: x[0])
return records_shuffled, records_sorted
def run_experiment(structure_name, insert_func, find_func, delete_func,
list_all_func, init_func, records, n_find=100):
data = init_func()
names = [r[0] for r in records]
start = time.perf_counter()
for name, phone in records:
if structure_name == "HashTable":
insert_func(data, name, phone)
else:
data = insert_func(data, name, phone)
insert_time = time.perf_counter() - start
find_names = random.sample(names, min(n_find, len(names)))
missing_names = [f"None_{i}" for i in range(10)]
all_find_names = find_names + missing_names
start = time.perf_counter()
for name in all_find_names:
if structure_name == "HashTable":
find_func(data, name)
else:
find_func(data, name)
find_time = time.perf_counter() - start
delete_names = random.sample(names, min(50, len(names)))
start = time.perf_counter()
for name in delete_names:
if structure_name == "HashTable":
delete_func(data, name)
else:
data = delete_func(data, name)
delete_time = time.perf_counter() - start
return insert_time, find_time, delete_time
def main():
print("Generating test data...")
records_shuffled, records_sorted = generate_data(10000)
results = []
structures = [
("LinkedList", ll_insert, ll_find, ll_delete, ll_list_all, lambda: None),
("HashTable", ht_insert, ht_find, ht_delete, ht_list_all, lambda: ht_create_table(2000)),
("BST", bst_insert, bst_find, bst_delete, bst_list_all, lambda: None)
]
for mode_name, records in [("random", records_shuffled), ("sorted", records_sorted)]:
print(f"\nMode: {mode_name}")
for struct_name, insert_f, find_f, delete_f, list_f, init_f in structures:
print(f" Testing {struct_name}...")
times = []
for run in range(5):
insert_t, find_t, delete_t = run_experiment(
struct_name, insert_f, find_f, delete_f, list_f, init_f, records
)
times.append((insert_t, find_t, delete_t))
print(f" Run {run+1}: insert={insert_t:.4f}s, find={find_t:.4f}s, delete={delete_t:.4f}s")
avg_insert = sum(t[0] for t in times) / 5
avg_find = sum(t[1] for t in times) / 5
avg_delete = sum(t[2] for t in times) / 5
results.append([struct_name, mode_name, "insert", avg_insert])
results.append([struct_name, mode_name, "find", avg_find])
results.append([struct_name, mode_name, "delete", avg_delete])
with open("results.csv", "w", newline="", encoding="utf-8") as f:
writer = csv.writer(f)
writer.writerow(["Structure", "Mode", "Operation", "Time_seconds"])
writer.writerows(results)
print("\n" + "="*60)
print("RESULTS (average over 5 runs):")
print("="*60)
for row in results:
print(f"{row[0]:12} | {row[1]:8} | {row[2]:8} | {row[3]:.6f} sec")
print("\nResults saved to results.csv")
if __name__ == "__main__":
main()

View File

@ -0,0 +1,19 @@
Structure,Mode,Operation,Time_seconds
LinkedList,random,insert,3.115811080000276
LinkedList,random,find,0.02396312000018952
LinkedList,random,delete,0.016048219999720458
HashTable,random,insert,0.18448304000012286
HashTable,random,find,0.0012929600005008978
HashTable,random,delete,0.0009329200001957361
BST,random,insert,0.017231119999996734
BST,random,find,0.00014155999961076304
BST,random,delete,9.299999983340968e-05
LinkedList,sorted,insert,2.780292439999903
LinkedList,sorted,find,0.02136590000045544
LinkedList,sorted,delete,0.014907859999584615
HashTable,sorted,insert,0.16707750000023225
HashTable,sorted,find,0.0012113199998566415
HashTable,sorted,delete,0.0008899600001313956
BST,sorted,insert,3.844869280000421
BST,sorted,find,0.031808019999880345
BST,sorted,delete,0.016554539999560802
1 Structure Mode Operation Time_seconds
2 LinkedList random insert 3.115811080000276
3 LinkedList random find 0.02396312000018952
4 LinkedList random delete 0.016048219999720458
5 HashTable random insert 0.18448304000012286
6 HashTable random find 0.0012929600005008978
7 HashTable random delete 0.0009329200001957361
8 BST random insert 0.017231119999996734
9 BST random find 0.00014155999961076304
10 BST random delete 9.299999983340968e-05
11 LinkedList sorted insert 2.780292439999903
12 LinkedList sorted find 0.02136590000045544
13 LinkedList sorted delete 0.014907859999584615
14 HashTable sorted insert 0.16707750000023225
15 HashTable sorted find 0.0012113199998566415
16 HashTable sorted delete 0.0008899600001313956
17 BST sorted insert 3.844869280000421
18 BST sorted find 0.031808019999880345
19 BST sorted delete 0.016554539999560802

View File

@ -0,0 +1,589 @@
import time
import heapq
from collections import deque
from typing import List, Optional, Dict, Tuple
from abc import ABC, abstractmethod
import csv
import random
class Cell:
def __init__(self, x: int, y: int):
self.x = x
self.y = y
self.is_wall = False
self.is_start = False
self.is_exit = False
def is_passable(self) -> bool:
return not self.is_wall
class Maze:
def __init__(self, width: int, height: int):
self.width = width
self.height = height
self.cells = [[Cell(x, y) for y in range(height)] for x in range(width)]
self.start: Optional[Cell] = None
self.exit: Optional[Cell] = None
def get_cell(self, x: int, y: int) -> Optional[Cell]:
if 0 <= x < self.width and 0 <= y < self.height:
return self.cells[x][y]
return None
def get_neighbors(self, cell: Cell) -> List[Cell]:
neighbors = []
for dx, dy in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
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
class MazeBuilder(ABC):
@abstractmethod
def build_from_file(self, filename: str) -> Maze:
pass
class TextFileMazeBuilder(MazeBuilder):
def build_from_file(self, filename: str) -> Maze:
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) if height > 0 else 0
maze = Maze(width, height)
for y, line in enumerate(lines):
for x, ch in enumerate(line):
cell = maze.get_cell(x, y)
if cell is None:
continue
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
elif ch == ' ':
pass
else:
raise ValueError(f"Unknown character '{ch}' at ({x},{y})")
if maze.start is None or maze.exit is None:
raise ValueError("Maze must have start (S) and exit (E)")
return maze
class PathFindingStrategy(ABC):
@abstractmethod
def find_path(self, maze: Maze, start: Cell, exit: Cell) -> List[Cell]:
pass
@abstractmethod
def get_name(self) -> str:
pass
class BFSStrategy(PathFindingStrategy):
def find_path(self, maze: Maze, start: Cell, exit: Cell) -> List[Cell]:
queue = deque([start])
came_from = {start: None}
while queue:
current = queue.popleft()
if current == exit:
break
for nb in maze.get_neighbors(current):
if nb not in came_from:
came_from[nb] = current
queue.append(nb)
if exit not in came_from:
return []
path = []
cur = exit
while cur:
path.append(cur)
cur = came_from[cur]
path.reverse()
return path
def get_name(self) -> str:
return "BFS"
class DFSStrategy(PathFindingStrategy):
def find_path(self, maze: Maze, start: Cell, exit: Cell) -> List[Cell]:
stack = [start]
came_from = {start: None}
while stack:
current = stack.pop()
if current == exit:
break
for nb in maze.get_neighbors(current):
if nb not in came_from:
came_from[nb] = current
stack.append(nb)
if exit not in came_from:
return []
path = []
cur = exit
while cur:
path.append(cur)
cur = came_from[cur]
path.reverse()
return path
def get_name(self) -> str:
return "DFS"
class AStarStrategy(PathFindingStrategy):
def _heuristic(self, a: Cell, b: Cell) -> int:
return abs(a.x - b.x) + abs(a.y - b.y)
def find_path(self, maze: Maze, start: Cell, exit: Cell) -> List[Cell]:
open_set = []
heapq.heappush(open_set, (0, id(start), start))
came_from = {}
g_score = {start: 0}
f_score = {start: self._heuristic(start, exit)}
while open_set:
_, _, current = heapq.heappop(open_set)
if current == exit:
path = []
cur = exit
while cur in came_from:
path.append(cur)
cur = came_from[cur]
path.append(start)
path.reverse()
return path
for neighbor in maze.get_neighbors(current):
tentative_g = g_score[current] + 1
if tentative_g < g_score.get(neighbor, float('inf')):
came_from[neighbor] = current
g_score[neighbor] = tentative_g
f_score[neighbor] = tentative_g + self._heuristic(neighbor, exit)
heapq.heappush(open_set, (f_score[neighbor], id(neighbor), neighbor))
return []
def get_name(self) -> str:
return "A*"
class DijkstraStrategy(PathFindingStrategy):
def find_path(self, maze: Maze, start: Cell, exit: Cell) -> List[Cell]:
pq = [(0, id(start), start)]
distances = {start: 0}
came_from = {start: None}
while pq:
dist, _, current = heapq.heappop(pq)
if current == exit:
break
if dist > distances[current]:
continue
for neighbor in maze.get_neighbors(current):
new_dist = dist + 1
if new_dist < distances.get(neighbor, float('inf')):
distances[neighbor] = new_dist
came_from[neighbor] = current
heapq.heappush(pq, (new_dist, id(neighbor), neighbor))
if exit not in came_from:
return []
path = []
cur = exit
while cur:
path.append(cur)
cur = came_from[cur]
path.reverse()
return path
def get_name(self) -> str:
return "Dijkstra"
class SearchStats:
def __init__(self, time_ms: float, visited_cells: int, path_length: int):
self.time_ms = time_ms
self.visited_cells = visited_cells
self.path_length = path_length
def __str__(self):
return f"Time: {self.time_ms:.2f}ms, Visited: {self.visited_cells}, Path: {self.path_length}"
class MazeSolver:
def __init__(self, maze: Maze, strategy: PathFindingStrategy):
self.maze = maze
self.strategy = strategy
def set_strategy(self, strategy: PathFindingStrategy):
self.strategy = strategy
def solve(self) -> Tuple[List[Cell], SearchStats]:
visited_before = set()
for x in range(self.maze.width):
for y in range(self.maze.height):
cell = self.maze.get_cell(x, y)
if cell and cell.is_passable():
visited_before.add(cell)
start_time = time.perf_counter()
path = self.strategy.find_path(self.maze, self.maze.start, self.maze.exit)
end_time = time.perf_counter()
visited_after = set()
for x in range(self.maze.width):
for y in range(self.maze.height):
cell = self.maze.get_cell(x, y)
if cell and cell.is_passable():
visited_after.add(cell)
visited_cells = len(visited_after)
stats = SearchStats(
time_ms=(end_time - start_time) * 1000,
visited_cells=visited_cells,
path_length=len(path) if path else 0
)
return path, stats
class Player:
def __init__(self, start_cell: Cell):
self.current_cell = start_cell
self.previous_cell = None
def move_to(self, cell: Cell) -> bool:
if cell.is_passable():
self.previous_cell = self.current_cell
self.current_cell = cell
return True
return False
def undo(self):
if self.previous_cell:
self.current_cell, self.previous_cell = self.previous_cell, None
return True
return False
class Command(ABC):
@abstractmethod
def execute(self) -> bool:
pass
@abstractmethod
def undo(self):
pass
class MoveCommand(Command):
def __init__(self, player: Player, maze: Maze, direction: str):
self.player = player
self.maze = maze
self.direction = direction
self.executed = False
def execute(self) -> bool:
dx, dy = 0, 0
if self.direction == 'W' or self.direction == 'w':
dy = -1
elif self.direction == 'S' or self.direction == 's':
dy = 1
elif self.direction == 'A' or self.direction == 'a':
dx = -1
elif self.direction == 'D' or self.direction == 'd':
dx = 1
new_x = self.player.current_cell.x + dx
new_y = self.player.current_cell.y + dy
new_cell = self.maze.get_cell(new_x, new_y)
if new_cell and new_cell.is_passable():
self.executed = self.player.move_to(new_cell)
return self.executed
return False
def undo(self):
if self.executed:
self.player.undo()
self.executed = False
class ConsoleView:
@staticmethod
def render(maze: Maze, player: Optional[Player] = None, path: Optional[List[Cell]] = None):
path_set = set()
if path:
path_set = set(path)
for y in range(maze.height):
line = ""
for x in range(maze.width):
cell = maze.get_cell(x, y)
if not cell:
line += " "
elif player and player.current_cell == cell:
line += "P"
elif cell.is_start:
line += "S"
elif cell.is_exit:
line += "E"
elif cell.is_wall:
line += "#"
elif path and cell in path_set:
line += "."
else:
line += " "
print(line)
print()
@staticmethod
def show_stats(stats: SearchStats, algo_name: str):
print(f"=== {algo_name} Results ===")
print(stats)
print()
def generate_test_maze(width: int, height: int, complexity: float = 0.3) -> Maze:
maze = Maze(width, height)
for x in range(width):
for y in range(height):
if random.random() < complexity:
maze.cells[x][y].is_wall = True
maze.start = maze.get_cell(0, 0)
if maze.start:
maze.start.is_start = True
maze.start.is_wall = False
maze.exit = maze.get_cell(width - 1, height - 1)
if maze.exit:
maze.exit.is_exit = True
maze.exit.is_wall = False
return maze
def generate_empty_maze(width: int, height: int) -> Maze:
maze = Maze(width, height)
for x in range(width):
for y in range(height):
maze.cells[x][y].is_wall = False
maze.start = maze.get_cell(0, 0)
if maze.start:
maze.start.is_start = True
maze.exit = maze.get_cell(width - 1, height - 1)
if maze.exit:
maze.exit.is_exit = True
return maze
def generate_no_exit_maze(width: int, height: int) -> Maze:
maze = Maze(width, height)
for x in range(width):
for y in range(height):
maze.cells[x][y].is_wall = False
for x in range(width):
maze.cells[x][height // 2].is_wall = True
maze.start = maze.get_cell(0, 0)
if maze.start:
maze.start.is_start = True
maze.exit = maze.get_cell(width - 1, height - 1)
if maze.exit:
maze.exit.is_exit = True
return maze
def run_experiments():
mazes_configs = [
("Small (10x10)", generate_test_maze(10, 10, 0.2)),
("Medium (50x50)", generate_test_maze(50, 50, 0.25)),
("Large (100x100)", generate_test_maze(100, 100, 0.3)),
("Empty (30x30)", generate_empty_maze(30, 30)),
("No Exit (20x20)", generate_no_exit_maze(20, 20))
]
strategies = [BFSStrategy(), DFSStrategy(), AStarStrategy(), DijkstraStrategy()]
results = []
for maze_name, maze in mazes_configs:
print(f"\n=== Testing: {maze_name} ===")
for strategy in strategies:
times = []
visited = []
path_lengths = []
solver = MazeSolver(maze, strategy)
for run in range(5):
maze_copy = Maze(maze.width, maze.height)
for x in range(maze.width):
for y in range(maze.height):
orig = maze.get_cell(x, y)
copy = maze_copy.get_cell(x, y)
if orig:
copy.is_wall = orig.is_wall
copy.is_start = orig.is_start
copy.is_exit = orig.is_exit
maze_copy.start = maze_copy.get_cell(maze.start.x, maze.start.y) if maze.start else None
maze_copy.exit = maze_copy.get_cell(maze.exit.x, maze.exit.y) if maze.exit else None
solver.maze = maze_copy
solver.set_strategy(strategy)
path, stats = solver.solve()
times.append(stats.time_ms)
visited.append(stats.visited_cells)
path_lengths.append(stats.path_length)
avg_time = sum(times) / len(times)
avg_visited = sum(visited) / len(visited)
avg_path = sum(path_lengths) / len(path_lengths)
results.append({
'maze': maze_name,
'algorithm': strategy.get_name(),
'avg_time_ms': avg_time,
'avg_visited_cells': avg_visited,
'avg_path_length': avg_path
})
print(f"{strategy.get_name()}: {avg_time:.2f}ms, {avg_visited:.0f} cells, path={avg_path:.0f}")
with open('experiment_results.csv', 'w', newline='', encoding='utf-8') as f:
writer = csv.DictWriter(f, fieldnames=['maze', 'algorithm', 'avg_time_ms', 'avg_visited_cells', 'avg_path_length'])
writer.writeheader()
writer.writerows(results)
print("\nResults saved to experiment_results.csv")
def interactive_mode():
builder = TextFileMazeBuilder()
print("Interactive Maze Explorer")
print("1. Load maze from file")
print("2. Generate random maze")
choice = input("Choose (1/2): ")
if choice == '1':
filename = input("Enter filename: ")
try:
maze = builder.build_from_file(filename)
except Exception as e:
print(f"Error loading maze: {e}")
return
else:
w = int(input("Width: "))
h = int(input("Height: "))
maze = generate_test_maze(w, h, 0.3)
player = Player(maze.start)
strategies = {
'1': BFSStrategy(),
'2': DFSStrategy(),
'3': AStarStrategy(),
'4': DijkstraStrategy()
}
print("\nSelect algorithm for solving:")
print("1. BFS (shortest path)")
print("2. DFS (fast, not optimal)")
print("3. A* (heuristic)")
print("4. Dijkstra")
algo_choice = input("Choose: ")
solver = MazeSolver(maze, strategies.get(algo_choice, BFSStrategy()))
path, stats = solver.solve()
view = ConsoleView()
if path:
print(f"\nPath found! Length: {len(path)}")
view.show_stats(stats, solver.strategy.get_name())
else:
print("\nNo path found!")
while True:
view.render(maze, player, path if path else None)
if player.current_cell == maze.exit:
print("Congratulations! You reached the exit!")
break
cmd = input("Move (W/A/S/D) | U=undo | Q=quit | S=solve: ").upper()
if cmd == 'Q':
break
elif cmd == 'U':
player.undo()
print("Undo last move")
elif cmd == 'S' and path:
for cell in path:
if cell == player.current_cell:
continue
player.move_to(cell)
view.render(maze, player, path)
input("Press Enter to continue...")
if player.current_cell == maze.exit:
print("You reached the exit!")
break
elif cmd in ['W', 'A', 'S', 'D']:
move_cmd = MoveCommand(player, maze, cmd)
if move_cmd.execute():
print("Moved")
else:
print("Can't move there!")
def main():
print("Maze Solver with Design Patterns")
print("1. Run experiments")
print("2. Interactive mode")
choice = input("Choose (1/2): ")
if choice == '1':
run_experiments()
else:
interactive_mode()
if __name__ == "__main__":
main()

View File

@ -0,0 +1,363 @@
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
# Настройка русских шрифтов
plt.rcParams['font.family'] = 'DejaVu Sans'
plt.rcParams['axes.unicode_minus'] = False
def load_and_prepare_data(filename='experiment_results.csv'):
"""Загрузка данных из CSV и подготовка."""
df = pd.read_csv(filename, delimiter=',') # Используем запятую как разделитель
# Переименовываем столбцы для удобства
df.columns = ['maze_type', 'algorithm', 'avg_time_ms', 'avg_visited_cells', 'avg_path_length']
# Преобразование типов
numeric_cols = ['avg_time_ms', 'avg_visited_cells', 'avg_path_length']
for col in numeric_cols:
df[col] = pd.to_numeric(df[col], errors='coerce')
# Добавляем столбец с размером лабиринта для анализа
def extract_maze_size(maze_name):
if 'Small' in maze_name:
return 'Small (10x10)'
elif 'Medium' in maze_name:
return 'Medium (50x50)'
elif 'Large' in maze_name:
return 'Large (100x100)'
elif 'Empty' in maze_name:
return 'Empty (30x30)'
elif 'No Exit' in maze_name:
return 'No Exit (20x20)'
return maze_name
df['maze_category'] = df['maze_type'].apply(extract_maze_size)
return df
def plot_time_comparison(df):
"""График 1: Сравнение времени выполнения по лабиринтам."""
fig, ax = plt.subplots(figsize=(12, 6))
maze_types = df['maze_category'].unique()
algorithms = df['algorithm'].unique()
x = np.arange(len(maze_types))
width = 0.2
colors = ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728']
for i, algorithm in enumerate(algorithms):
algo_data = df[df['algorithm'] == algorithm]
times = []
for maze in maze_types:
row = algo_data[algo_data['maze_category'] == maze]
if not row.empty:
times.append(row['avg_time_ms'].values[0])
else:
times.append(0)
bars = ax.bar(x + i*width, times, width, label=algorithm,
color=colors[i])
ax.set_xlabel('Тип лабиринта', fontsize=12)
ax.set_ylabel('Время выполнения (мс)', fontsize=12)
ax.set_title('Сравнение времени выполнения алгоритмов поиска пути', fontsize=14)
ax.set_xticks(x + width * 1.5)
ax.set_xticklabels(maze_types, rotation=45, ha='right')
ax.legend()
ax.grid(True, alpha=0.3, axis='y')
# Добавление значений на столбцы
for i, algorithm in enumerate(algorithms):
algo_data = df[df['algorithm'] == algorithm]
for j, maze in enumerate(maze_types):
row = algo_data[algo_data['maze_category'] == maze]
if not row.empty and row['avg_time_ms'].values[0] > 0:
time_val = row['avg_time_ms'].values[0]
ax.text(x[j] + i*width, time_val + 0.02,
f'{time_val:.3f}', ha='center', va='bottom', fontsize=8)
plt.tight_layout()
plt.savefig('time_comparison.png', dpi=150)
plt.show()
def plot_visited_cells(df):
"""График 2: Количество посещённых клеток."""
fig, ax = plt.subplots(figsize=(12, 6))
maze_types = df['maze_category'].unique()
algorithms = df['algorithm'].unique()
x = np.arange(len(maze_types))
width = 0.2
colors = ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728']
for i, algorithm in enumerate(algorithms):
algo_data = df[df['algorithm'] == algorithm]
visited = []
for maze in maze_types:
row = algo_data[algo_data['maze_category'] == maze]
if not row.empty:
visited.append(row['avg_visited_cells'].values[0])
else:
visited.append(0)
ax.bar(x + i*width, visited, width, label=algorithm, color=colors[i])
ax.set_xlabel('Тип лабиринта', fontsize=12)
ax.set_ylabel('Количество посещённых клеток', fontsize=12)
ax.set_title('Сравнение количества посещённых клеток', fontsize=14)
ax.set_xticks(x + width * 1.5)
ax.set_xticklabels(maze_types, rotation=45, ha='right')
ax.legend()
ax.grid(True, alpha=0.3, axis='y')
plt.tight_layout()
plt.savefig('visited_cells.png', dpi=150)
plt.show()
def plot_path_length(df):
"""График 3: Длина найденного пути."""
fig, ax = plt.subplots(figsize=(12, 6))
# Исключаем лабиринты без выхода (где путь = 0)
df_filtered = df[df['avg_path_length'] > 0]
maze_types = df_filtered['maze_category'].unique()
algorithms = df_filtered['algorithm'].unique()
x = np.arange(len(maze_types))
width = 0.2
colors = ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728']
for i, algorithm in enumerate(algorithms):
algo_data = df_filtered[df_filtered['algorithm'] == algorithm]
path_lengths = []
for maze in maze_types:
row = algo_data[algo_data['maze_category'] == maze]
if not row.empty:
path_lengths.append(row['avg_path_length'].values[0])
else:
path_lengths.append(0)
ax.bar(x + i*width, path_lengths, width, label=algorithm, color=colors[i])
ax.set_xlabel('Тип лабиринта', fontsize=12)
ax.set_ylabel('Длина пути (количество клеток)', fontsize=12)
ax.set_title('Сравнение длины найденного пути', fontsize=14)
ax.set_xticks(x + width * 1.5)
ax.set_xticklabels(maze_types, rotation=45, ha='right')
ax.legend()
ax.grid(True, alpha=0.3, axis='y')
plt.tight_layout()
plt.savefig('path_length.png', dpi=150)
plt.show()
def plot_time_per_maze(df):
"""График 4: Для каждого лабиринта - сравнение алгоритмов по времени."""
maze_types = df['maze_category'].unique()
algorithms = df['algorithm'].unique()
for maze in maze_types:
fig, ax = plt.subplots(figsize=(10, 6))
maze_data = df[df['maze_category'] == maze]
times = []
algo_names = []
for algo in algorithms:
row = maze_data[maze_data['algorithm'] == algo]
if not row.empty:
times.append(row['avg_time_ms'].values[0])
algo_names.append(algo)
bars = ax.bar(algo_names, times,
color=['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728'][:len(algo_names)])
ax.set_xlabel('Алгоритм', fontsize=12)
ax.set_ylabel('Время выполнения (мс)', fontsize=12)
ax.set_title(f'Сравнение алгоритмов на лабиринте: {maze}', fontsize=14)
ax.grid(True, alpha=0.3, axis='y')
# Добавление значений на столбцы
for bar, time_val in zip(bars, times):
height = bar.get_height()
ax.text(bar.get_x() + bar.get_width()/2., height + 0.02,
f'{time_val:.3f}', ha='center', va='bottom', fontsize=10)
plt.tight_layout()
# Очищаем имя файла от скобок
safe_maze_name = maze.replace('(', '').replace(')', '').replace(' ', '_')
plt.savefig(f'time_{safe_maze_name}.png', dpi=150)
plt.show()
def plot_visited_per_maze(df):
"""График 5: Для каждого лабиринта - посещённые клетки."""
maze_types = df['maze_category'].unique()
for maze in maze_types:
fig, ax = plt.subplots(figsize=(10, 6))
maze_data = df[df['maze_category'] == maze]
visited = maze_data['avg_visited_cells'].values
algo_names = maze_data['algorithm'].values
bars = ax.bar(algo_names, visited,
color=['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728'][:len(algo_names)])
ax.set_xlabel('Алгоритм', fontsize=12)
ax.set_ylabel('Количество посещённых клеток', fontsize=12)
ax.set_title(f'Посещённые клетки на лабиринте: {maze}', fontsize=14)
ax.grid(True, alpha=0.3, axis='y')
# Добавление значений на столбцы
for bar, val in zip(bars, visited):
height = bar.get_height()
ax.text(bar.get_x() + bar.get_width()/2., height + 10,
f'{int(val)}', ha='center', va='bottom', fontsize=10)
plt.tight_layout()
safe_maze_name = maze.replace('(', '').replace(')', '').replace(' ', '_')
plt.savefig(f'visited_{safe_maze_name}.png', dpi=150)
plt.show()
def plot_efficiency_ratio(df):
"""График 6: Эффективность (время на клетку пути)."""
fig, ax = plt.subplots(figsize=(12, 6))
# Исключаем лабиринты без пути
df_filtered = df[(df['avg_path_length'] > 0) & (df['avg_time_ms'] > 0)].copy()
df_filtered['efficiency'] = df_filtered['avg_time_ms'] / df_filtered['avg_path_length']
maze_types = df_filtered['maze_category'].unique()
algorithms = df_filtered['algorithm'].unique()
x = np.arange(len(maze_types))
width = 0.2
colors = ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728']
for i, algorithm in enumerate(algorithms):
algo_data = df_filtered[df_filtered['algorithm'] == algorithm]
efficiency = []
for maze in maze_types:
row = algo_data[algo_data['maze_category'] == maze]
if not row.empty:
efficiency.append(row['efficiency'].values[0])
else:
efficiency.append(0)
ax.bar(x + i*width, efficiency, width, label=algorithm, color=colors[i])
ax.set_xlabel('Тип лабиринта', fontsize=12)
ax.set_ylabel('Время на клетку пути (мс/клетку)', fontsize=12)
ax.set_title('Эффективность алгоритмов (время на единицу длины пути)', fontsize=14)
ax.set_xticks(x + width * 1.5)
ax.set_xticklabels(maze_types, rotation=45, ha='right')
ax.legend()
ax.grid(True, alpha=0.3, axis='y')
plt.tight_layout()
plt.savefig('efficiency_ratio.png', dpi=150)
plt.show()
def plot_path_vs_visited(df):
"""График 7: Соотношение длины пути и посещённых клеток."""
fig, ax = plt.subplots(figsize=(10, 6))
algorithms = df['algorithm'].unique()
markers = ['o', 's', '^', 'D']
colors = ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728']
for algo, marker, color in zip(algorithms, markers, colors):
algo_data = df[df['algorithm'] == algo]
# Только лабиринты с путём
algo_data = algo_data[algo_data['avg_path_length'] > 0]
if not algo_data.empty:
plt.scatter(algo_data['avg_visited_cells'],
algo_data['avg_path_length'],
marker=marker, s=100, label=algo, color=color, alpha=0.7)
# Добавляем подписи для каждой точки
for _, row in algo_data.iterrows():
plt.annotate(row['maze_category'].split()[0],
(row['avg_visited_cells'], row['avg_path_length']),
xytext=(5, 5), textcoords='offset points', fontsize=8)
plt.xlabel('Количество посещённых клеток', fontsize=12)
plt.ylabel('Длина пути (клеток)', fontsize=12)
plt.title('Соотношение: посещённые клетки vs длина пути', fontsize=14)
plt.legend()
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('path_vs_visited.png', dpi=150)
plt.show()
def main():
"""Основная функция: загрузка данных и построение всех графиков."""
try:
df = load_and_prepare_data('experiment_results.csv')
print("Данные успешно загружены")
print(f"Найдено {len(df)} записей")
print("\nСтруктура данных:")
print(df.head())
print("\nУникальные типы лабиринтов:")
print(df['maze_category'].unique())
print("\nУникальные алгоритмы:")
print(df['algorithm'].unique())
print("\nПостроение графиков...")
# Базовые графики
plot_time_comparison(df)
plot_visited_cells(df)
plot_path_length(df)
# Детальные графики по каждому лабиринту
plot_time_per_maze(df)
plot_visited_per_maze(df)
# Аналитические графики
plot_efficiency_ratio(df)
plot_path_vs_visited(df)
print("\nВсе графики сохранены в текущей директории:")
print(" - time_comparison.png")
print(" - visited_cells.png")
print(" - path_length.png")
print(" - time_{maze}.png (для каждого лабиринта)")
print(" - visited_{maze}.png (для каждого лабиринта)")
print(" - efficiency_ratio.png")
print(" - path_vs_visited.png")
# Вывод статистики
print("\n=== Краткая статистика ===")
for maze in df['maze_category'].unique():
print(f"\n{maze}:")
maze_data = df[df['maze_category'] == maze]
for algo in df['algorithm'].unique():
algo_data = maze_data[maze_data['algorithm'] == algo]
if not algo_data.empty:
time_val = algo_data['avg_time_ms'].values[0]
visited_val = int(algo_data['avg_visited_cells'].values[0])
path_val = int(algo_data['avg_path_length'].values[0])
print(f" {algo}: время={time_val:.6f}мс, посещено={visited_val}, путь={path_val}")
except FileNotFoundError:
print("Ошибка: файл experiment_results.csv не найден")
print("Убедитесь, что файл находится в текущей директории")
except Exception as e:
print(f"Ошибка: {e}")
import traceback
traceback.print_exc()
if __name__ == "__main__":
main()

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

View File

@ -0,0 +1,21 @@
maze,algorithm,avg_time_ms,avg_visited_cells,avg_path_length
Small (10x10),BFS,0.08572000006097369,79.0,19.0
Small (10x10),DFS,0.039739999920129776,79.0,31.0
Small (10x10),A*,0.13467999997374136,79.0,19.0
Small (10x10),Dijkstra,0.11474000057205558,79.0,19.0
Medium (50x50),BFS,1.8074600004183594,1874.0,99.0
Medium (50x50),DFS,0.5937599995377241,1874.0,429.0
Medium (50x50),A*,1.6300600003887666,1874.0,99.0
Medium (50x50),Dijkstra,3.1870400001935195,1874.0,99.0
Large (100x100),BFS,0.014439999722526409,7033.0,0.0
Large (100x100),DFS,0.014839999857940711,7033.0,0.0
Large (100x100),A*,0.02542000001994893,7033.0,0.0
Large (100x100),Dijkstra,0.02548000011302065,7033.0,0.0
Empty (30x30),BFS,0.784620000194991,900.0,59.0
Empty (30x30),DFS,0.5252399994787993,900.0,465.0
Empty (30x30),A*,1.150900000357069,900.0,59.0
Empty (30x30),Dijkstra,1.564640000287909,900.0,59.0
No Exit (20x20),BFS,0.2002399993216386,380.0,0.0
No Exit (20x20),DFS,0.2512400002160575,380.0,0.0
No Exit (20x20),A*,0.5590400000073714,380.0,0.0
No Exit (20x20),Dijkstra,0.35640000060084276,380.0,0.0
1 maze algorithm avg_time_ms avg_visited_cells avg_path_length
2 Small (10x10) BFS 0.08572000006097369 79.0 19.0
3 Small (10x10) DFS 0.039739999920129776 79.0 31.0
4 Small (10x10) A* 0.13467999997374136 79.0 19.0
5 Small (10x10) Dijkstra 0.11474000057205558 79.0 19.0
6 Medium (50x50) BFS 1.8074600004183594 1874.0 99.0
7 Medium (50x50) DFS 0.5937599995377241 1874.0 429.0
8 Medium (50x50) A* 1.6300600003887666 1874.0 99.0
9 Medium (50x50) Dijkstra 3.1870400001935195 1874.0 99.0
10 Large (100x100) BFS 0.014439999722526409 7033.0 0.0
11 Large (100x100) DFS 0.014839999857940711 7033.0 0.0
12 Large (100x100) A* 0.02542000001994893 7033.0 0.0
13 Large (100x100) Dijkstra 0.02548000011302065 7033.0 0.0
14 Empty (30x30) BFS 0.784620000194991 900.0 59.0
15 Empty (30x30) DFS 0.5252399994787993 900.0 465.0
16 Empty (30x30) A* 1.150900000357069 900.0 59.0
17 Empty (30x30) Dijkstra 1.564640000287909 900.0 59.0
18 No Exit (20x20) BFS 0.2002399993216386 380.0 0.0
19 No Exit (20x20) DFS 0.2512400002160575 380.0 0.0
20 No Exit (20x20) A* 0.5590400000073714 380.0 0.0
21 No Exit (20x20) Dijkstra 0.35640000060084276 380.0 0.0

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

Binary file not shown.