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

5 курс / lab6

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


class DirectedGraph:
    def __init__(self, vertices=0):
        self.vertices = vertices
        self.adj_matrix = [[0] * vertices for _ in range(vertices)]
        self.edges = []

    def add_edge(self, u, v, weight):
        if 0 <= u < self.vertices and 0 <= v < self.vertices:
            self.adj_matrix[u][v] = weight
            self.edges.append((u, v, weight))

    def bellman_ford(self, source):
        if self.vertices == 0:
            return [], []

        distances = [float('inf')] * self.vertices
        predecessors = [-1] * self.vertices
        distances[source] = 0

        for _ in range(self.vertices - 1):
            for u, v, w in self.edges:
                if distances[u] != float('inf') and distances[u] + w < distances[v]:
                    distances[v] = distances[u] + w
                    predecessors[v] = u

        for u, v, w in self.edges:
            if distances[u] != float('inf') and distances[u] + w < distances[v]:
                return None, None

        return distances, predecessors

    def get_edge_list(self):
        edges = []
        for i in range(self.vertices):
            for j in range(self.vertices):
                if self.adj_matrix[i][j] != 0:
                    edges.append((i, j, self.adj_matrix[i][j]))
        return edges


class GraphVisualizer:
    def __init__(self, root):
        self.root = root
        self.root.title("Алгоритм Беллмана-Форда")
        self.root.geometry("1200x700")

        self.graph = DirectedGraph()
        self.vertex_positions = []
        self.highlighted_path = []

        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_graph_from_file, width=20)
        load_button.pack(pady=5)

        save_button = tk.Button(control_frame, text="Сохранить в файл",
                                command=self.save_graph_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.vertices_entry = tk.Entry(gen_frame, width=8)
        self.vertices_entry.insert(0, "8")
        self.vertices_entry.pack(side=tk.LEFT, padx=5)

        tk.Label(gen_frame, text="Плотность %:", bg="#f0f0f0").pack(side=tk.LEFT, padx=(10, 0))
        self.density_entry = tk.Entry(gen_frame, width=8)
        self.density_entry.insert(0, "30")
        self.density_entry.pack(side=tk.LEFT, padx=5)

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

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

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

        tk.Label(path_frame, text="Из:", bg="#f0f0f0").pack(side=tk.LEFT)
        self.source_entry = tk.Entry(path_frame, width=8)
        self.source_entry.insert(0, "0")
        self.source_entry.pack(side=tk.LEFT, padx=5)

        tk.Label(path_frame, text="В:", bg="#f0f0f0").pack(side=tk.LEFT, padx=(10, 0))
        self.target_entry = tk.Entry(path_frame, width=8)
        self.target_entry.insert(0, "3")
        self.target_entry.pack(side=tk.LEFT, padx=5)

        bellman_button = tk.Button(control_frame, text="Запустить Беллмана-Форда",
                                   command=self.run_bellman_ford, width=20, bg="#4CAF50", fg="white")
        bellman_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_graph, 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=8, width=30, font=("Arial", 10))
        self.info_text.pack(pady=5)

        edges_label = tk.Label(control_frame, text="Список ребер",
                               font=("Arial", 12, "bold"), bg="#f0f0f0")
        edges_label.pack(pady=5)

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

    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_graph_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()
                    vertices = int(lines[0].strip())
                    self.graph = DirectedGraph(vertices)

                    for i in range(1, len(lines)):
                        row = list(map(int, lines[i].strip().split()))
                        for j in range(vertices):
                            self.graph.adj_matrix[i - 1][j] = row[j]
                            if row[j] != 0:
                                self.graph.edges.append((i - 1, j, row[j]))

                self.highlighted_path = []
                self.update_edge_list()
                self.draw_graph()
                messagebox.showinfo("Успех", f"Граф загружен из файла {filename}")

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

    def save_graph_to_file(self):
        if self.graph.vertices == 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(str(self.graph.vertices) + "\n")
                    for i in range(self.graph.vertices):
                        row = " ".join(str(x) for x in self.graph.adj_matrix[i])
                        file.write(row + "\n")

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

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

    def generate_random_graph(self):
        try:
            vertices = int(self.vertices_entry.get())
            density = float(self.density_entry.get()) / 100

            if vertices < 2 or vertices > 20:
                messagebox.showwarning("Предупреждение", "Количество вершин должно быть от 2 до 20")
                return

            self.graph = DirectedGraph(vertices)
            self.graph.edges = []

            for i in range(vertices):
                for j in range(vertices):
                    if i != j and random.random() < density:
                        weight = random.randint(-5, 20)
                        if weight <= 0:
                            weight = random.randint(1, 20)
                        self.graph.adj_matrix[i][j] = weight
                        self.graph.edges.append((i, j, weight))

            self.highlighted_path = []
            self.update_edge_list()
            self.draw_graph()

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

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

        if self.graph.vertices > 0:
            edges = self.graph.get_edge_list()
            self.info_text.insert(tk.END, f"Вершин: {self.graph.vertices}\n")
            self.info_text.insert(tk.END, f"Ребер: {len(edges)}\n")

            for u, v, w in edges:
                self.edges_text.insert(tk.END, f"{u} → {v} : {w}\n")

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

        if self.graph.vertices == 0:
            return

        center_x, center_y = 400, 300
        radius = 250

        self.vertex_positions = []
        for i in range(self.graph.vertices):
            angle = 2 * math.pi * i / self.graph.vertices
            x = center_x + radius * math.cos(angle)
            y = center_y + radius * math.sin(angle)
            self.vertex_positions.append((x, y))

        for i in range(self.graph.vertices):
            for j in range(self.graph.vertices):
                if self.graph.adj_matrix[i][j] > 0:
                    x1, y1 = self.vertex_positions[i]
                    x2, y2 = self.vertex_positions[j]

                    dx = x2 - x1
                    dy = y2 - y1
                    length = math.sqrt(dx * dx + dy * dy)

                    if length > 0:
                        offset_x = (dx / length) * 20
                        offset_y = (dy / length) * 20

                        x1 += offset_x
                        y1 += offset_y
                        x2 -= offset_x
                        y2 -= offset_y

                    is_highlighted = any((self.highlighted_path[k] == i and
                                          self.highlighted_path[k + 1] == j)
                                         for k in range(len(self.highlighted_path) - 1))

                    color = "red" if is_highlighted else "gray"
                    width = 3 if is_highlighted else 1

                    self.canvas.create_line(x1, y1, x2, y2, arrow=tk.LAST,
                                            fill=color, width=width)

                    mid_x = (x1 + x2) / 2
                    mid_y = (y1 + y2) / 2

                    if is_highlighted:
                        self.canvas.create_oval(mid_x - 15, mid_y - 15, mid_x + 15, mid_y + 15,
                                                fill="yellow", outline="red", width=2)

                    self.canvas.create_text(mid_x, mid_y,
                                            text=str(self.graph.adj_matrix[i][j]),
                                            fill="blue", font=("Arial", 10, "bold"))

        for i, (x, y) in enumerate(self.vertex_positions):
            is_highlighted = i in self.highlighted_path
            fill_color = "orange" if is_highlighted else "lightblue"
            outline_color = "red" if is_highlighted else "black"
            width = 3 if is_highlighted else 2

            self.canvas.create_oval(x - 20, y - 20, x + 20, y + 20,
                                    fill=fill_color, outline=outline_color, width=width)
            self.canvas.create_text(x, y, text=str(i),
                                    font=("Arial", 12, "bold"))

    def run_bellman_ford(self):
        if self.graph.vertices == 0:
            messagebox.showwarning("Предупреждение", "Сначала создайте или загрузите граф")
            return

        try:
            source = int(self.source_entry.get())
            target = int(self.target_entry.get())

            if source < 0 or source >= self.graph.vertices or target < 0 or target >= self.graph.vertices:
                messagebox.showerror("Ошибка",
                                     f"Вершины должны быть от 0 до {self.graph.vertices - 1}")
                return

            start_time = time.time()
            distances, predecessors = self.graph.bellman_ford(source)
            end_time = time.time()

            execution_time = (end_time - start_time) * 1000

            self.result_text.delete(1.0, tk.END)

            if distances is None:
                self.result_text.insert(tk.END, "Обнаружен цикл отрицательного веса!\n")
                self.result_text.insert(tk.END, "Алгоритм не может найти кратчайшие пути.\n")
                return

            self.result_text.insert(tk.END, f"Время выполнения: {execution_time:.4f} мс\n\n")
            self.result_text.insert(tk.END, f"Кратчайшие расстояния от вершины {source}:\n")

            for i in range(self.graph.vertices):
                dist = distances[i] if distances[i] != float('inf') else "∞"
                self.result_text.insert(tk.END, f"  до {i}: {dist}\n")

            self.result_text.insert(tk.END, f"\nКратчайший путь из {source} в {target}:\n")

            if distances[target] == float('inf'):
                self.result_text.insert(tk.END, "Путь не существует\n")
                return

            path = []
            current = target
            while current != -1:
                path.append(current)
                current = predecessors[current]
            path.reverse()

            self.highlighted_path = path

            path_str = " → ".join(str(v) for v in path)
            self.result_text.insert(tk.END, f"  Путь: {path_str}\n")
            self.result_text.insert(tk.END, f"  Длина: {distances[target]}\n")

            self.draw_graph()

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

    def highlight_path(self, path):
        self.highlighted_path = path
        self.draw_graph()

    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)

        vertices_list = [5, 10, 15, 20]
        density_list = [0.3, 0.5, 0.7]

        results = []

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

        for vertices in vertices_list:
            for density in density_list:
                test_graph = DirectedGraph(vertices)
                edges_count = 0

                for i in range(vertices):
                    for j in range(vertices):
                        if i != j and random.random() < density:
                            weight = random.randint(1, 20)
                            test_graph.adj_matrix[i][j] = weight
                            test_graph.edges.append((i, j, weight))
                            edges_count += 1

                start_time = time.time()
                test_graph.bellman_ford(0)
                end_time = time.time()

                execution_time = (end_time - start_time) * 1000
                results.append((vertices, edges_count, density, execution_time))

        text_widget.insert(tk.END, "ТАБЛИЦА АНАЛИЗА ВРЕМЕННОЙ СЛОЖНОСТИ\n")
        text_widget.insert(tk.END, "=" * 80 + "\n")
        text_widget.insert(tk.END, "Вершин | Ребра | Плотность | Время (мс) | O-нотация\n")
        text_widget.insert(tk.END, "-" * 80 + "\n")

        for vertices, edges, density, time_ms in results:
            theoretical_complexity = vertices * edges
            text_widget.insert(tk.END,
                               f"{vertices:7d} | {edges:5d} | {density:9.1%} | {time_ms:10.4f} | "
                               f"O(V*E) = O({vertices}*{edges}) = {theoretical_complexity}\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. Временная сложность алгоритма Беллмана-Форда: O(V*E)\n\n")
        text_widget.insert(tk.END,
                           "2. Время выполнения линейно зависит от количества ребер\n\n")
        text_widget.insert(tk.END,
                           "3. Алгоритм работает с графами с отрицательными весами\n\n")
        text_widget.insert(tk.END,
                           "4. Обнаруживает циклы отрицательного веса\n\n")
        text_widget.insert(tk.END,
                           "5. Медленнее алгоритма Дейкстры для графов без отрицательных весов\n\n")
        text_widget.insert(tk.END,
                           "6. Эффективен для разреженных графов\n")

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

    def clear_graph(self):
        self.graph = DirectedGraph()
        self.highlighted_path = []
        self.canvas.delete("all")
        self.info_text.delete(1.0, tk.END)
        self.edges_text.delete(1.0, tk.END)
        self.result_text.delete(1.0, tk.END)


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


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