八皇后问题

问题

八皇后问题是一个以国际象棋为背景的问题:如何能够在8×8的国际象棋棋盘上放置八个皇后,使得任何一个皇后都无法直接吃掉其他的皇后?为了达到此目的,任两个皇后都不能处于同一条横行、纵列、正斜线或反斜线。使用穷举法列举所有可能。

程序

import tkinter as tk
from tkinter import messagebox
import time
import threading

class EightQueensGUI:
    def __init__(self, root):
        self.root = root
        self.root.title("八皇后问题演示")
        self.root.geometry("650x750")
        
        self.board_size = 8
        self.cell_size = 50
        self.delay = 100
        
        self.setup_ui()
        self.solutions = []
        self.current_solution = 0
        self.solving = False
        self.solve_thread = None
        
    def setup_ui(self):
        # 创建主框架
        main_frame = tk.Frame(self.root)
        main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
        
        # 创建画布
        self.canvas = tk.Canvas(main_frame, width=500, height=500, bg='white')
        self.canvas.pack(pady=10)
        
        # 创建控制按钮框架
        control_frame = tk.Frame(main_frame)
        control_frame.pack(pady=10, fill=tk.X)
        
        # 第一行按钮
        btn_frame1 = tk.Frame(control_frame)
        btn_frame1.pack(pady=5)
        
        self.solve_btn = tk.Button(btn_frame1, text="开始求解", command=self.solve, width=10)
        self.solve_btn.pack(side=tk.LEFT, padx=5)
        
        self.next_btn = tk.Button(btn_frame1, text="下一个解", command=self.next_solution, width=10)
        self.next_btn.pack(side=tk.LEFT, padx=5)
        
        self.prev_btn = tk.Button(btn_frame1, text="上一个解", command=self.prev_solution, width=10)
        self.prev_btn.pack(side=tk.LEFT, padx=5)
        
        self.stop_btn = tk.Button(btn_frame1, text="停止求解", command=self.stop_solving, width=10)
        self.stop_btn.pack(side=tk.LEFT, padx=5)
        
        # 第二行控件
        btn_frame2 = tk.Frame(control_frame)
        btn_frame2.pack(pady=5, fill=tk.X)
        
        # 速度控制
        speed_frame = tk.Frame(btn_frame2)
        speed_frame.pack(side=tk.LEFT, padx=10)
        
        tk.Label(speed_frame, text="速度:").pack(side=tk.LEFT)
        self.speed_scale = tk.Scale(speed_frame, from_=10, to=500, orient=tk.HORIZONTAL,
                                   length=200, showvalue=True, command=self.update_delay)
        self.speed_scale.set(self.delay)
        self.speed_scale.pack(side=tk.LEFT, padx=5)
        
        # 解决方案选择
        sol_frame = tk.Frame(btn_frame2)
        sol_frame.pack(side=tk.RIGHT, padx=10)
        
        tk.Label(sol_frame, text="直接查看解:").pack(side=tk.LEFT)
        self.solution_var = tk.StringVar(value="1")
        self.solution_spinbox = tk.Spinbox(sol_frame, from_=1, to=92, width=5, textvariable=self.solution_var,
                                     command=self.jump_to_solution)
        self.solution_spinbox.pack(side=tk.LEFT, padx=5)
        
        # 信息标签
        self.info_label = tk.Label(main_frame, text="点击'开始求解'来演示八皇后问题的解法", 
                                  font=("Arial", 12), wraplength=600)
        self.info_label.pack(pady=5)
        
        # 状态标签
        self.status_label = tk.Label(main_frame, text="", font=("Arial", 10), fg="blue")
        self.status_label.pack(pady=5)
        
        # 绘制初始棋盘
        self.draw_board()
    
    def draw_board(self):
        self.canvas.delete("all")
        
        # 绘制棋盘
        for row in range(self.board_size):
            for col in range(self.board_size):
                x1 = col * self.cell_size + 50
                y1 = row * self.cell_size + 50
                x2 = x1 + self.cell_size
                y2 = y1 + self.cell_size
                
                color = "#DEB887" if (row + col) % 2 == 0 else "#8B4513"
                self.canvas.create_rectangle(x1, y1, x2, y2, fill=color, outline="black")
        
        # 绘制行列标记
        for i in range(self.board_size):
            # 行标记 (A-H)
            self.canvas.create_text(25, i * self.cell_size + 50 + self.cell_size // 2, 
                                   text=chr(65 + i), font=("Arial", 12, "bold"))
            # 列标记 (1-8)
            self.canvas.create_text(i * self.cell_size + 50 + self.cell_size // 2, 25, 
                                   text=str(i + 1), font=("Arial", 12, "bold"))
    
    def draw_queens(self, board):
        self.draw_board()
        
        for row in range(self.board_size):
            for col in range(self.board_size):
                if board[row][col] == 1:
                    self.draw_queen(row, col)
        
        self.root.update()
    
    def draw_queen(self, row, col):
        x = col * self.cell_size + 50 + self.cell_size // 2
        y = row * self.cell_size + 50 + self.cell_size // 2
        radius = self.cell_size // 3
        
        # 绘制皇后
        self.canvas.create_oval(x - radius, y - radius, x + radius, y + radius, 
                               fill="red", outline="darkred", width=2)
        
        # 绘制皇冠
        crown_points = [
            x - radius//2, y - radius,
            x - radius//3, y - radius//2,
            x, y - radius,
            x + radius//3, y - radius//2,
            x + radius//2, y - radius
        ]
        self.canvas.create_polygon(crown_points, fill="gold", outline="darkgoldenrod", width=2)
    
    def is_safe(self, board, row, col):
        # 检查同一列
        for i in range(row):
            if board[i][col] == 1:
                return False
        
        # 检查左上对角线
        for i, j in zip(range(row-1, -1, -1), range(col-1, -1, -1)):
            if board[i][j] == 1:
                return False
        
        # 检查右上对角线
        for i, j in zip(range(row-1, -1, -1), range(col+1, self.board_size)):
            if board[i][j] == 1:
                return False
        
        return True
    
    def solve_n_queens(self, board, row=0):
        if not self.solving:
            return False
            
        if row >= self.board_size:
            # 找到解,复制当前棋盘状态
            solution = [row[:] for row in board]
            self.solutions.append(solution)
            self.update_status(f"找到第 {len(self.solutions)} 个解")
            
            # 更新显示
            self.draw_queens(board)
            time.sleep(self.delay / 1000)
            
            # 继续寻找更多解
            return False
        
        for col in range(self.board_size):
            if not self.solving:
                return False
                
            if self.is_safe(board, row, col):
                board[row][col] = 1
                
                # 更新显示
                self.draw_queens(board)
                self.update_info(f"尝试在位置 ({chr(65+row)}{col+1}) 放置皇后")
                time.sleep(self.delay / 1000)
                
                # 递归调用
                self.solve_n_queens(board, row + 1)
                
                # 回溯
                board[row][col] = 0
                
                # 更新显示
                self.draw_queens(board)
                self.update_info(f"回溯: 移除位置 ({chr(65+row)}{col+1}) 的皇后")
                time.sleep(self.delay / 1000)
        
        return False
    
    def solve(self):
        self.solve_btn.config(state=tk.DISABLED)
        self.stop_btn.config(state=tk.NORMAL)
        self.solutions = []
        self.solving = True
        board = [[0 for _ in range(self.board_size)] for _ in range(self.board_size)]
        
        # 在新线程中运行求解过程
        self.solve_thread = threading.Thread(target=self.solve_thread_func, args=(board,))
        self.solve_thread.daemon = True
        self.solve_thread.start()
    
    def solve_thread_func(self, board):
        # 使用改进的算法找到所有解
        self.find_all_solutions(board)
        
        if self.solving:
            if self.solutions:
                self.current_solution = 0
                self.show_solution_info()
                self.update_status(f"求解完成! 共找到 {len(self.solutions)} 个解")
                self.solution_spinbox.config(to=len(self.solutions))
                self.solution_var.set("1")
            else:
                self.update_status("未找到解!")
        
        self.solve_btn.config(state=tk.NORMAL)
        self.stop_btn.config(state=tk.DISABLED)
        self.solving = False
    
    def find_all_solutions(self, board, row=0):
        """找到所有解的递归函数"""
        if not self.solving:
            return
            
        if row >= self.board_size:
            # 找到一个解
            solution = [row[:] for row in board]
            self.solutions.append(solution)
            self.update_status(f"找到第 {len(self.solutions)} 个解")
            
            # 显示当前解
            self.draw_queens(board)
            time.sleep(self.delay / 1000)
            return
        
        for col in range(self.board_size):
            if not self.solving:
                return
                
            if self.is_safe(board, row, col):
                board[row][col] = 1
                
                # 更新显示
                self.draw_queens(board)
                self.update_info(f"尝试在位置 ({chr(65+row)}{col+1}) 放置皇后")
                time.sleep(self.delay / 1000)
                
                # 递归调用
                self.find_all_solutions(board, row + 1)
                
                # 回溯
                board[row][col] = 0
                
                # 更新显示
                self.draw_queens(board)
                self.update_info(f"回溯: 移除位置 ({chr(65+row)}{col+1}) 的皇后")
                time.sleep(self.delay / 1000)
    
    def stop_solving(self):
        self.solving = False
        self.update_status("求解已停止")
    
    def show_solution_info(self):
        if self.solutions and 0 <= self.current_solution < len(self.solutions):
            solution = self.solutions[self.current_solution]
            self.draw_queens(solution)
            self.update_info(f"解 {self.current_solution + 1}/{len(self.solutions)}")
    
    def next_solution(self):
        if self.solutions:
            self.current_solution = (self.current_solution + 1) % len(self.solutions)
            self.solution_var.set(str(self.current_solution + 1))
            self.show_solution_info()
    
    def prev_solution(self):
        if self.solutions:
            self.current_solution = (self.current_solution - 1) % len(self.solutions)
            self.solution_var.set(str(self.current_solution + 1))
            self.show_solution_info()
    
    def jump_to_solution(self):
        if self.solutions:
            try:
                sol_num = int(self.solution_var.get()) - 1
                if 0 <= sol_num < len(self.solutions):
                    self.current_solution = sol_num
                    self.show_solution_info()
            except ValueError:
                pass
    
    def update_delay(self, value):
        self.delay = int(value)
    
    def update_info(self, text):
        self.info_label.config(text=text)
        self.root.update_idletasks()
    
    def update_status(self, text):
        self.status_label.config(text=text)
        self.root.update_idletasks()

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

if __name__ == "__main__":
    main()

思想

该程序使用穷举法,并通过UI展示了完整的穷举过程,可以访问穷举结果。穷举中如果出现错误的情况就回溯。

执行效果

八皇后问题

image-20251102161310859