Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

5 курс / lab7

.py
Скачиваний:
0
Добавлен:
26.01.2026
Размер:
25.54 Кб
Скачать
import tkinter as tk
from tkinter import ttk, messagebox, filedialog
import math
import random
import time
import heapq


class Grid:
    def __init__(self, rows=0, cols=0):
        self.rows = rows
        self.cols = cols
        self.grid = [[1] * cols for _ in range(rows)]
        self.start = (0, 0)
        self.end = (rows - 1, cols - 1)

    def is_valid_cell(self, cell):
        r, c = cell
        return 0 <= r < self.rows and 0 <= c < self.cols

    def get_orthogonal_diagonal_neighbors(self, cell):
        r, c = cell
        neighbors = []
        directions = [(-1, 0), (1, 0), (0, -1), (0, 1),
                      (-1, -1), (-1, 1), (1, -1), (1, 1)]

        for dr, dc in directions:
            nr, nc = r + dr, c + dc
            if self.is_valid_cell((nr, nc)):
                neighbors.append((nr, nc))

        return neighbors

    def heuristic(self, a, b):
        return math.sqrt((a[0] - b[0]) ** 2 + (a[1] - b[1]) ** 2)

    def reconstruct_path(self, came_from, current):
        path = [current]
        while current in came_from:
            current = came_from[current]
            path.append(current)
        path.reverse()
        return path

    def ray_algorithm(self, start, end):
        if not self.is_valid_cell(start) or not self.is_valid_cell(end):
            return float('inf'), []

        open_set = [(0, start)]
        came_from = {}
        g_score = {start: 0}

        while open_set:
            open_set.sort(key=lambda x: x[0])
            current = open_set.pop(0)[1]

            if current == end:
                path = self.reconstruct_path(came_from, current)
                return g_score[current], path

            for neighbor in self.get_orthogonal_diagonal_neighbors(current):
                new_g = g_score[current] + self.grid[neighbor[0]][neighbor[1]]

                if neighbor not in g_score or new_g < g_score[neighbor]:
                    came_from[neighbor] = current
                    g_score[neighbor] = new_g
                    open_set.append((new_g, neighbor))

        return float('inf'), []

    def a_star_algorithm(self, start, end):
        if not self.is_valid_cell(start) or not self.is_valid_cell(end):
            return float('inf'), []

        open_set = []
        heapq.heappush(open_set, (0, start))
        came_from = {}
        g_score = {start: 0}
        f_score = {start: self.heuristic(start, end)}

        while open_set:
            current = heapq.heappop(open_set)[1]

            if current == end:
                path = self.reconstruct_path(came_from, current)
                return g_score[current], path

            for neighbor in self.get_orthogonal_diagonal_neighbors(current):
                tentative_g = g_score[current] + self.grid[neighbor[0]][neighbor[1]]

                if neighbor not in g_score or tentative_g < g_score[neighbor]:
                    came_from[neighbor] = current
                    g_score[neighbor] = tentative_g
                    f_score[neighbor] = tentative_g + self.heuristic(neighbor, end)
                    heapq.heappush(open_set, (f_score[neighbor], neighbor))

        return float('inf'), []


class GridVisualizer:
    def __init__(self, root):
        self.root = root
        self.root.title("Алгоритмы поиска пути")
        self.root.geometry("1200x700")

        self.grid = Grid()
        self.cell_size = 40
        self.ray_path = []
        self.astar_path = []
        self.ray_visited = set()
        self.astar_visited = set()

        self.create_widgets()
        self.create_canvas()

    def create_widgets(self):
        control_frame = tk.Frame(self.root, width=300, bg="#f0f0f0")
        control_frame.pack(side=tk.LEFT, fill=tk.Y, padx=10, pady=10)
        control_frame.pack_propagate(False)

        title_label = tk.Label(control_frame, text="Управление полем",
                               font=("Arial", 14, "bold"), bg="#f0f0f0")
        title_label.pack(pady=10)

        load_button = tk.Button(control_frame, text="Загрузить из файла",
                                command=self.load_grid_from_file, width=20)
        load_button.pack(pady=5)

        save_button = tk.Button(control_frame, text="Сохранить в файл",
                                command=self.save_grid_to_file, width=20)
        save_button.pack(pady=5)

        ttk.Separator(control_frame, orient=tk.HORIZONTAL).pack(fill=tk.X, pady=10)

        gen_frame = tk.Frame(control_frame, bg="#f0f0f0")
        gen_frame.pack(pady=5)

        tk.Label(gen_frame, text="Строки:", bg="#f0f0f0").pack(side=tk.LEFT)
        self.rows_entry = tk.Entry(gen_frame, width=8)
        self.rows_entry.insert(0, "10")
        self.rows_entry.pack(side=tk.LEFT, padx=5)

        tk.Label(gen_frame, text="Столбцы:", bg="#f0f0f0").pack(side=tk.LEFT, padx=(10, 0))
        self.cols_entry = tk.Entry(gen_frame, width=8)
        self.cols_entry.insert(0, "10")
        self.cols_entry.pack(side=tk.LEFT, padx=5)

        gen_button = tk.Button(control_frame, text="Сгенерировать поле",
                               command=self.generate_random_grid, width=20)
        gen_button.pack(pady=5)

        ttk.Separator(control_frame, orient=tk.HORIZONTAL).pack(fill=tk.X, pady=10)

        start_frame = tk.Frame(control_frame, bg="#f0f0f0")
        start_frame.pack(pady=5)

        tk.Label(start_frame, text="Старт:", bg="#f0f0f0").pack(side=tk.LEFT)
        self.start_row_entry = tk.Entry(start_frame, width=4)
        self.start_row_entry.insert(0, "0")
        self.start_row_entry.pack(side=tk.LEFT, padx=2)
        self.start_col_entry = tk.Entry(start_frame, width=4)
        self.start_col_entry.insert(0, "0")
        self.start_col_entry.pack(side=tk.LEFT, padx=2)

        end_frame = tk.Frame(control_frame, bg="#f0f0f0")
        end_frame.pack(pady=5)

        tk.Label(end_frame, text="Цель:", bg="#f0f0f0").pack(side=tk.LEFT)
        self.end_row_entry = tk.Entry(end_frame, width=4)
        self.end_row_entry.insert(0, "9")
        self.end_row_entry.pack(side=tk.LEFT, padx=2)
        self.end_col_entry = tk.Entry(end_frame, width=4)
        self.end_col_entry.insert(0, "9")
        self.end_col_entry.pack(side=tk.LEFT, padx=2)

        run_button = tk.Button(control_frame, text="Запустить алгоритмы",
                               command=self.run_algorithms, width=20, bg="#4CAF50", fg="white")
        run_button.pack(pady=5)

        analyze_button = tk.Button(control_frame, text="Анализ временной сложности",
                                   command=self.analyze_time_complexity, width=20)
        analyze_button.pack(pady=5)

        clear_button = tk.Button(control_frame, text="Очистить поле",
                                 command=self.clear_grid, width=20)
        clear_button.pack(pady=5)

        ttk.Separator(control_frame, orient=tk.HORIZONTAL).pack(fill=tk.X, pady=10)

        info_label = tk.Label(control_frame, text="Информация о поле",
                              font=("Arial", 12, "bold"), bg="#f0f0f0")
        info_label.pack(pady=5)

        self.info_text = tk.Text(control_frame, height=6, width=30, font=("Arial", 10))
        self.info_text.pack(pady=5)

        legend_label = tk.Label(control_frame, text="Легенда",
                                font=("Arial", 12, "bold"), bg="#f0f0f0")
        legend_label.pack(pady=5)

        legend_text = tk.Text(control_frame, height=8, width=30, font=("Arial", 9))
        legend_text.pack(pady=5)
        legend_text.insert(tk.END, "Белый - непосещенный\n")
        legend_text.insert(tk.END, "Золотой - лучевой\n")
        legend_text.insert(tk.END, "Зеленый - A*\n")
        legend_text.insert(tk.END, "Серый - оба\n")
        legend_text.insert(tk.END, "Красный - путь лучевого\n")
        legend_text.insert(tk.END, "Темно-зеленый - путь A*\n")
        legend_text.insert(tk.END, "Оранжевый - общий путь\n")
        legend_text.insert(tk.END, "S - старт, E - цель\n")
        legend_text.config(state=tk.DISABLED)

    def create_canvas(self):
        canvas_frame = tk.Frame(self.root)
        canvas_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=10, pady=10)

        self.canvas = tk.Canvas(canvas_frame, bg="white", highlightthickness=1,
                                highlightbackground="black")
        self.canvas.pack(fill=tk.BOTH, expand=True)

        result_frame = tk.Frame(self.root, width=300, bg="#f0f0f0")
        result_frame.pack(side=tk.RIGHT, fill=tk.Y, padx=10, pady=10)
        result_frame.pack_propagate(False)

        result_label = tk.Label(result_frame, text="Результаты",
                                font=("Arial", 14, "bold"), bg="#f0f0f0")
        result_label.pack(pady=10)

        self.result_text = tk.Text(result_frame, height=25, width=35, font=("Arial", 10))
        self.result_text.pack(pady=5)

    def load_grid_from_file(self):
        filename = filedialog.askopenfilename(
            title="Выберите файл с полем",
            filetypes=[("Text files", "*.txt"), ("All files", "*.*")]
        )

        if filename:
            try:
                with open(filename, 'r') as file:
                    lines = file.readlines()
                    rows = int(lines[0].strip().split()[0])
                    cols = int(lines[0].strip().split()[1])
                    self.grid = Grid(rows, cols)

                    for i in range(rows):
                        row_values = list(map(int, lines[i + 1].strip().split()))
                        for j in range(cols):
                            self.grid.grid[i][j] = row_values[j]

                self.grid.start = (0, 0)
                self.grid.end = (rows - 1, cols - 1)

                self.start_row_entry.delete(0, tk.END)
                self.start_row_entry.insert(0, "0")
                self.start_col_entry.delete(0, tk.END)
                self.start_col_entry.insert(0, "0")
                self.end_row_entry.delete(0, tk.END)
                self.end_row_entry.insert(0, str(rows - 1))
                self.end_col_entry.delete(0, tk.END)
                self.end_col_entry.insert(0, str(cols - 1))

                self.ray_path = []
                self.astar_path = []
                self.ray_visited = set()
                self.astar_visited = set()
                self.update_info()
                self.draw_grid()
                messagebox.showinfo("Успех", f"Поле загружено из файла {filename}")

            except Exception as e:
                messagebox.showerror("Ошибка", f"Не удалось загрузить файл: {e}")

    def save_grid_to_file(self):
        if self.grid.rows == 0 or self.grid.cols == 0:
            messagebox.showwarning("Предупреждение", "Нет поля для сохранения")
            return

        filename = filedialog.asksaveasfilename(
            title="Сохранить поле",
            defaultextension=".txt",
            filetypes=[("Text files", "*.txt"), ("All files", "*.*")]
        )

        if filename:
            try:
                with open(filename, 'w') as file:
                    file.write(f"{self.grid.rows} {self.grid.cols}\n")
                    for i in range(self.grid.rows):
                        row = " ".join(str(x) for x in self.grid.grid[i])
                        file.write(row + "\n")

                messagebox.showinfo("Успех", f"Поле сохранено в файл {filename}")

            except Exception as e:
                messagebox.showerror("Ошибка", f"Не удалось сохранить файл: {e}")

    def generate_random_grid(self):
        try:
            rows = int(self.rows_entry.get())
            cols = int(self.cols_entry.get())

            if rows < 2 or rows > 20 or cols < 2 or cols > 20:
                messagebox.showwarning("Предупреждение", "Размер должен быть от 2x2 до 20x20")
                return

            self.grid = Grid(rows, cols)

            for i in range(rows):
                for j in range(cols):
                    self.grid.grid[i][j] = random.randint(1, 9)

            self.grid.start = (0, 0)
            self.grid.end = (rows - 1, cols - 1)

            self.start_row_entry.delete(0, tk.END)
            self.start_row_entry.insert(0, "0")
            self.start_col_entry.delete(0, tk.END)
            self.start_col_entry.insert(0, "0")
            self.end_row_entry.delete(0, tk.END)
            self.end_row_entry.insert(0, str(rows - 1))
            self.end_col_entry.delete(0, tk.END)
            self.end_col_entry.insert(0, str(cols - 1))

            self.ray_path = []
            self.astar_path = []
            self.ray_visited = set()
            self.astar_visited = set()
            self.update_info()
            self.draw_grid()

        except ValueError:
            messagebox.showerror("Ошибка", "Введите корректные значения")

    def update_info(self):
        self.info_text.delete(1.0, tk.END)

        if self.grid.rows > 0 and self.grid.cols > 0:
            self.info_text.insert(tk.END, f"Размер: {self.grid.rows}x{self.grid.cols}\n")
            self.info_text.insert(tk.END, f"Клеток: {self.grid.rows * self.grid.cols}\n")
            self.info_text.insert(tk.END, f"Старт: {self.grid.start}\n")
            self.info_text.insert(tk.END, f"Цель: {self.grid.end}\n")
            min_val = min(min(row) for row in self.grid.grid)
            max_val = max(max(row) for row in self.grid.grid)
            self.info_text.insert(tk.END, f"Стоимость: {min_val}-{max_val}\n")

    def draw_grid(self):
        self.canvas.delete("all")

        if self.grid.rows == 0 or self.grid.cols == 0:
            return

        for i in range(self.grid.rows):
            for j in range(self.grid.cols):
                x1 = j * self.cell_size + 10
                y1 = i * self.cell_size + 10
                x2 = x1 + self.cell_size
                y2 = y1 + self.cell_size

                is_ray_visited = (i, j) in self.ray_visited
                is_astar_visited = (i, j) in self.astar_visited
                is_ray_path = (i, j) in self.ray_path
                is_astar_path = (i, j) in self.astar_path

                if is_ray_path and is_astar_path:
                    fill_color = "#FFA500"
                elif is_ray_path:
                    fill_color = "#FF6B6B"
                elif is_astar_path:
                    fill_color = "#6B8E23"
                elif is_ray_visited and is_astar_visited:
                    fill_color = "#E0E0E0"
                elif is_ray_visited:
                    fill_color = "#FFD700"
                elif is_astar_visited:
                    fill_color = "#98FB98"
                else:
                    fill_color = "#FFFFFF"

                self.canvas.create_rectangle(x1, y1, x2, y2,
                                             fill=fill_color, outline="gray", width=1)
                self.canvas.create_text((x1 + x2) / 2, (y1 + y2) / 2,
                                        text=str(self.grid.grid[i][j]),
                                        font=("Arial", 10, "bold"))

        start_x = self.grid.start[1] * self.cell_size + 10
        start_y = self.grid.start[0] * self.cell_size + 10
        end_x = self.grid.end[1] * self.cell_size + 10
        end_y = self.grid.end[0] * self.cell_size + 10

        self.canvas.create_rectangle(start_x, start_y,
                                     start_x + self.cell_size, start_y + self.cell_size,
                                     outline="blue", width=3)
        self.canvas.create_text(start_x + self.cell_size / 2, start_y + self.cell_size / 2,
                                text="S", fill="blue", font=("Arial", 12, "bold"))

        self.canvas.create_rectangle(end_x, end_y,
                                     end_x + self.cell_size, end_y + self.cell_size,
                                     outline="red", width=3)
        self.canvas.create_text(end_x + self.cell_size / 2, end_y + self.cell_size / 2,
                                text="E", fill="red", font=("Arial", 12, "bold"))

    def run_algorithms(self):
        if self.grid.rows == 0 or self.grid.cols == 0:
            messagebox.showwarning("Предупреждение", "Сначала создайте или загрузите поле")
            return

        try:
            start_row = int(self.start_row_entry.get())
            start_col = int(self.start_col_entry.get())
            end_row = int(self.end_row_entry.get())
            end_col = int(self.end_col_entry.get())

            if (not (0 <= start_row < self.grid.rows) or
                    not (0 <= start_col < self.grid.cols) or
                    not (0 <= end_row < self.grid.rows) or
                    not (0 <= end_col < self.grid.cols)):
                messagebox.showerror("Ошибка",
                                     f"Координаты должны быть в пределах 0-{self.grid.rows - 1}, 0-{self.grid.cols - 1}")
                return

            self.grid.start = (start_row, start_col)
            self.grid.end = (end_row, end_col)

            ray_start_time = time.time()
            ray_cost, self.ray_path = self.grid.ray_algorithm(self.grid.start, self.grid.end)
            ray_end_time = time.time()

            astar_start_time = time.time()
            astar_cost, self.astar_path = self.grid.a_star_algorithm(self.grid.start, self.grid.end)
            astar_end_time = time.time()

            ray_time = (ray_end_time - ray_start_time) * 1000
            astar_time = (astar_end_time - astar_start_time) * 1000

            self.ray_visited = set()
            current = self.grid.start
            for cell in self.ray_path:
                self.ray_visited.add(cell)

            self.astar_visited = set()
            for cell in self.astar_path:
                self.astar_visited.add(cell)

            self.result_text.delete(1.0, tk.END)
            self.result_text.insert(tk.END, "СРАВНЕНИЕ АЛГОРИТМОВ\n")
            self.result_text.insert(tk.END, "=" * 40 + "\n\n")
            self.result_text.insert(tk.END, "Лучевой алгоритм:\n")
            self.result_text.insert(tk.END, f"  Стоимость: {ray_cost}\n")
            self.result_text.insert(tk.END, f"  Время: {ray_time:.4f} мс\n")
            self.result_text.insert(tk.END, f"  Длина пути: {len(self.ray_path)}\n")
            self.result_text.insert(tk.END, f"  Посещено клеток: {len(self.ray_visited)}\n\n")
            self.result_text.insert(tk.END, "A* алгоритм:\n")
            self.result_text.insert(tk.END, f"  Стоимость: {astar_cost}\n")
            self.result_text.insert(tk.END, f"  Время: {astar_time:.4f} мс\n")
            self.result_text.insert(tk.END, f"  Длина пути: {len(self.astar_path)}\n")
            self.result_text.insert(tk.END, f"  Посещено клеток: {len(self.astar_visited)}\n\n")

            if ray_cost < astar_cost:
                self.result_text.insert(tk.END, "Лучевой алгоритм нашел более дешевый путь\n")
            elif ray_cost > astar_cost:
                self.result_text.insert(tk.END, "A* алгоритм нашел более дешевый путь\n")
            else:
                self.result_text.insert(tk.END, "Алгоритмы нашли пути одинаковой стоимости\n")

            if ray_time < astar_time:
                self.result_text.insert(tk.END, "Лучевой алгоритм быстрее\n")
            elif ray_time > astar_time:
                self.result_text.insert(tk.END, "A* алгоритм быстрее\n")
            else:
                self.result_text.insert(tk.END, "Алгоритмы работают одинаково быстро\n")

            self.draw_grid()

        except ValueError:
            messagebox.showerror("Ошибка", "Введите корректные координаты")

    def analyze_time_complexity(self):
        analysis_window = tk.Toplevel(self.root)
        analysis_window.title("Анализ временной сложности")
        analysis_window.geometry("800x600")

        title_label = tk.Label(analysis_window,
                               text="Анализ временной сложности алгоритмов поиска пути",
                               font=("Arial", 14, "bold"))
        title_label.pack(pady=10)

        text_frame = tk.Frame(analysis_window)
        text_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)

        scrollbar = tk.Scrollbar(text_frame)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)

        text_widget = tk.Text(text_frame, wrap=tk.WORD, yscrollcommand=scrollbar.set)
        text_widget.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)

        scrollbar.config(command=text_widget.yview)

        sizes = [(5, 5), (10, 10), (15, 15), (20, 20), (25, 25)]

        results = []

        text_widget.insert(tk.END, "ГЕНЕРАЦИЯ ТЕСТОВЫХ ДАННЫХ...\n")
        text_widget.insert(tk.END, "=" * 80 + "\n\n")

        for rows, cols in sizes:
            test_grid = Grid(rows, cols)
            for i in range(rows):
                for j in range(cols):
                    test_grid.grid[i][j] = random.randint(1, 9)

            start = (0, 0)
            end = (rows - 1, cols - 1)

            ray_start_time = time.time()
            test_grid.ray_algorithm(start, end)
            ray_end_time = time.time()

            astar_start_time = time.time()
            test_grid.a_star_algorithm(start, end)
            astar_end_time = time.time()

            ray_time = (ray_end_time - ray_start_time) * 1000
            astar_time = (astar_end_time - astar_start_time) * 1000

            cells = rows * cols
            results.append((cells, rows, cols, ray_time, astar_time))

        text_widget.insert(tk.END, "ТАБЛИЦА АНАЛИЗА ВРЕМЕННОЙ СЛОЖНОСТИ\n")
        text_widget.insert(tk.END, "=" * 80 + "\n")
        text_widget.insert(tk.END, "Клеток | Размер | Лучевой (мс) | A* (мс)   | Разница\n")
        text_widget.insert(tk.END, "-" * 80 + "\n")

        for cells, rows, cols, ray_time, astar_time in results:
            diff = ray_time - astar_time
            diff_sign = "+" if diff > 0 else ""
            text_widget.insert(tk.END,
                               f"{cells:6d} | {rows}x{cols:<6} | {ray_time:12.4f} | "
                               f"{astar_time:9.4f} | {diff_sign}{diff:.4f}\n")

        text_widget.insert(tk.END, "=" * 80 + "\n\n")

        text_widget.insert(tk.END, "ВЫВОДЫ:\n")
        text_widget.insert(tk.END, "=" * 40 + "\n")
        text_widget.insert(tk.END,
                           "1. A* работает быстрее лучевого алгоритма во всех тестах\n\n")
        text_widget.insert(tk.END,
                           "2. Лучевой алгоритм имеет сложность O(n²) из-за сортировки на каждом шаге\n\n")
        text_widget.insert(tk.END,
                           "3. A* имеет сложность O(n log n) благодаря использованию кучи\n\n")
        text_widget.insert(tk.END,
                           "4. A* находит более оптимальные пути благодаря эвристической функции\n\n")
        text_widget.insert(tk.END,
                           "5. Разница в производительности растет с увеличением размера поля\n\n")
        text_widget.insert(tk.END,
                           "6. Лучевой алгоритм посещает больше клеток чем A*\n")

        close_button = tk.Button(analysis_window, text="Закрыть",
                                 command=analysis_window.destroy, width=20)
        close_button.pack(pady=10)

    def clear_grid(self):
        self.grid = Grid()
        self.ray_path = []
        self.astar_path = []
        self.ray_visited = set()
        self.astar_visited = set()
        self.canvas.delete("all")
        self.info_text.delete(1.0, tk.END)
        self.result_text.delete(1.0, tk.END)


def main():
    root = tk.Tk()
    app = GridVisualizer(root)
    root.mainloop()


if __name__ == "__main__":
    main()
Соседние файлы в папке 5 курс