八皇后问题
问题
八皇后问题是一个以国际象棋为背景的问题:如何能够在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展示了完整的穷举过程,可以访问穷举结果。穷举中如果出现错误的情况就回溯。
执行效果

