数独

数独 问题 数独是在一个9*9宫格中,保证每行每列以及九个九宫格中填入的1-9的数字不出现重复。通过穷举法实现求解。 程序 import tkinter as tk from tkinter import ttk, messagebox import time import threading class SudokuSolver: def __init__(self, root): self.root = root self.root.title("数独求解器 - 穷举法") self.root.geometry("600x700") # 初始化数独网格 self.board = [[0 for _ in range(9)] for _ in range(9)] self.initial_board = [[0 for _ in range(9)] for _ in range(9)] # 存储初始题目 self.cells = [[None for _ in range(9)] for _ in range(9)] self.selected_cell = None self.solving = False self.speed = 100 # 默认速度(毫秒) self.algorithm_time = 0 # 纯算法时间 self.step_count = 0 # 步骤计数器 # 初始化组件变量 self.speed_label = None self.speed_scale = None self.status_label = None self.stats_label = None self.create_widgets() def create_widgets(self): # 主框架 main_frame = ttk.Frame(self.root, padding="10") main_frame.pack(fill=tk.BOTH, expand=True) # 标题 title_label = ttk.Label(main_frame, text="数独求解器 - 穷举回溯法", font=("Arial", 16, "bold")) title_label.pack(pady=10) # 数独网格框架 grid_frame = ttk.Frame(main_frame) grid_frame.pack(pady=20) # 创建9x9网格 self.create_grid(grid_frame) # 控制面板 control_frame = ttk.Frame(main_frame) control_frame.pack(pady=20) # 速度控制 speed_frame = ttk.Frame(control_frame) speed_frame.pack(pady=10) ttk.Label(speed_frame, text="演示速度:").pack(side=tk.LEFT) self.speed_scale = ttk.Scale(speed_frame, from_=10, to=1000, orient=tk.HORIZONTAL, length=200, command=self.update_speed) self.speed_scale.set(100) self.speed_scale.pack(side=tk.LEFT, padx=10) self.speed_label = ttk.Label(speed_frame, text="100 ms") self.speed_label.pack(side=tk.LEFT) # 按钮 button_frame = ttk.Frame(control_frame) button_frame.pack(pady=10) ttk.Button(button_frame, text="开始求解", command=self.start_solving).pack(side=tk.LEFT, padx=5) ttk.Button(button_frame, text="直接作答", command=self.direct_solve).pack(side=tk.LEFT, padx=5) ttk.Button(button_frame, text="重置", command=self.reset_board).pack(side=tk.LEFT, padx=5) ttk.Button(button_frame, text="清空", command=self.clear_board).pack(side=tk.LEFT, padx=5) ttk.Button(button_frame, text="示例", command=self.load_example).pack(side=tk.LEFT, padx=5) # 状态显示 self.status_label = ttk.Label(main_frame, text="请点击格子并输入数字1-9", font=("Arial", 10)) self.status_label.pack(pady=10) # 统计信息 self.stats_label = ttk.Label(main_frame, text="步骤: 0 | 算法时间: 0.00s", font=("Arial", 10)) self.stats_label.pack(pady=5) # 操作说明 instruction_text = """操作说明: 1. 点击格子,然后按数字键1-9填入数字 2. 按0或Delete键清空格子 3. 调整速度滑块控制演示速度 4. 点击"开始求解"观看穷举求解过程 5. 点击"直接作答"直接获得答案 6. 紫色格子表示初始题目,绿色表示尝试的数字,红色表示回溯""" instruction_label = ttk.Label(main_frame, text=instruction_text, justify=tk.LEFT, font=("Arial", 9)) instruction_label.pack(pady=10) def create_grid(self, parent): # 创建9x9网格,每个3x3区域有更粗的边框 for i in range(9): for j in range(9): # 确定边框宽度 - 为3x3区域边界设置更粗的边框 border_width = 1 if i % 3 == 0 and i != 0: border_width = 3 if j % 3 == 0 and j != 0: border_width = 3 # 为外边框设置更粗的边框 if i == 0 or i == 8: border_width = 3 if j == 0 or j == 8: border_width = 3 cell = tk.Entry(parent, width=3, font=("Arial", 16), justify=tk.CENTER, relief="solid", borderwidth=border_width) cell.grid(row=i, column=j, padx=(1, 1), pady=(1, 1), ipady=5) # 绑定事件 cell.bind("<Button-1>", lambda e, row=i, col=j: self.select_cell(row, col)) cell.bind("<Key>", lambda e, row=i, col=j: self.on_key_press(e, row, col)) cell.bind("<FocusIn>", lambda e, row=i, col=j: self.select_cell(row, col)) self.cells[i][j] = cell def select_cell(self, row, col): # 取消之前选中格子的高亮 if self.selected_cell: old_row, old_col = self.selected_cell self.cells[old_row][old_col].config(bg=self.get_cell_color(old_row, old_col)) # 高亮当前选中的格子 self.selected_cell = (row, col) self.cells[row][col].config(bg="#e0e0ff") self.cells[row][col].focus_set() self.status_label.config(text=f"选中格子: ({row+1}, {col+1})") def get_cell_color(self, row, col): """根据格子内容返回对应的背景颜色""" if self.initial_board[row][col] != 0: return "#e0c0ff" # 初始题目 - 紫色 else: return "white" # 空白格子 - 白色 def on_key_press(self, event, row, col): if event.char in '123456789': self.set_cell_value(row, col, int(event.char)) elif event.keysym in ['Delete', 'BackSpace'] or event.char == '0': self.set_cell_value(row, col, 0) # 阻止默认行为 return "break" def set_cell_value(self, row, col, value): self.board[row][col] = value if value == 0: self.cells[row][col].delete(0, tk.END) else: self.cells[row][col].delete(0, tk.END) self.cells[row][col].insert(0, str(value)) # 更新格子颜色 self.cells[row][col].config(bg=self.get_cell_color(row, col)) def update_speed(self, value): self.speed = int(float(value)) # 确保 speed_label 已创建 if hasattr(self, 'speed_label') and self.speed_label is not None: self.speed_label.config(text=f"{self.speed} ms") def reset_board(self): for i in range(9): for j in range(9): self.set_cell_value(i, j, self.board[i][j]) self.status_label.config(text="棋盘已重置") def clear_board(self): for i in range(9): for j in range(9): self.set_cell_value(i, j, 0) self.initial_board[i][j] = 0 # 同时清空初始题目 self.status_label.config(text="棋盘已清空") self.step_count = 0 self.algorithm_time = 0 self.update_stats() def load_example(self): # 加载一个示例数独 example = [ [5, 3, 0, 0, 7, 0, 0, 0, 0], [6, 0, 0, 1, 9, 5, 0, 0, 0], [0, 9, 8, 0, 0, 0, 0, 6, 0], [8, 0, 0, 0, 6, 0, 0, 0, 3], [4, 0, 0, 8, 0, 3, 0, 0, 1], [7, 0, 0, 0, 2, 0, 0, 0, 6], [0, 6, 0, 0, 0, 0, 2, 8, 0], [0, 0, 0, 4, 1, 9, 0, 0, 5], [0, 0, 0, 0, 8, 0, 0, 7, 9] ] for i in range(9): for j in range(9): self.set_cell_value(i, j, example[i][j]) self.initial_board[i][j] = example[i][j] # 保存初始题目 self.status_label.config(text="已加载示例数独") def start_solving(self): if self.solving: return # 保存当前棋盘状态作为初始题目 for i in range(9): for j in range(9): if self.board[i][j] != 0: self.initial_board[i][j] = self.board[i][j] # 更新格子颜色为初始题目颜色 self.cells[i][j].config(bg=self.get_cell_color(i, j)) # 在新线程中运行求解过程,避免阻塞UI thread = threading.Thread(target=self.solve_thread) thread.daemon = True thread.start() def direct_solve(self): """直接求解,不演示中间过程""" if self.solving: return # 保存当前棋盘状态作为初始题目 for i in range(9): for j in range(9): if self.board[i][j] != 0: self.initial_board[i][j] = self.board[i][j] # 在新线程中运行直接求解过程 thread = threading.Thread(target=self.direct_solve_thread) thread.daemon = True thread.start() def solve_thread(self): """演示求解线程""" self.solving = True self.status_label.config(text="开始求解...") self.step_count = 0 self.algorithm_time = 0 # 禁用控制按钮 self.toggle_buttons(False) # 开始求解 start_time = time.time() solved = self.solve_sudoku() end_time = time.time() self.solving = False # 启用控制按钮 self.toggle_buttons(True) total_time = end_time - start_time if solved: self.status_label.config(text=f"求解完成! 总用时: {total_time:.2f}秒") else: self.status_label.config(text="无解!") def direct_solve_thread(self): """直接求解线程""" self.solving = True self.status_label.config(text="直接求解中...") # 禁用控制按钮 self.toggle_buttons(False) # 开始直接求解 start_time = time.time() solved = self.solve_sudoku_direct() end_time = time.time() self.solving = False # 启用控制按钮 self.toggle_buttons(True) total_time = end_time - start_time if solved: # 更新UI显示最终结果 self.update_final_board() self.status_label.config(text=f"直接求解完成! 用时: {total_time:.2f}秒") else: self.status_label.config(text="无解!") def solve_sudoku_direct(self): """直接求解数独,不更新UI,不延迟""" # 创建棋盘副本 board_copy = [row[:] for row in self.board] # 直接求解 solved = self.solve_direct(board_copy) # 如果求解成功,更新棋盘 if solved: self.board = board_copy return solved def solve_direct(self, board): """直接求解数独的递归函数""" empty_cell = self.find_empty_cell_direct(board) if not empty_cell: return True # 没有空格子,求解完成 row, col = empty_cell for num in range(1, 10): if self.is_valid_direct(board, row, col, num): # 尝试填入数字 board[row][col] = num # 递归求解 if self.solve_direct(board): return True # 回溯 board[row][col] = 0 return False def find_empty_cell_direct(self, board): """在直接求解模式下查找空格子""" for i in range(9): for j in range(9): if board[i][j] == 0: return (i, j) return None def is_valid_direct(self, board, row, col, num): """在直接求解模式下检查数字是否有效""" # 检查行 for i in range(9): if board[row][i] == num: return False # 检查列 for i in range(9): if board[i][col] == num: return False # 检查3x3宫格 start_row, start_col = 3 * (row // 3), 3 * (col // 3) for i in range(3): for j in range(3): if board[start_row + i][start_col + j] == num: return False return True def update_final_board(self): """更新UI显示最终结果""" for i in range(9): for j in range(9): value = self.board[i][j] if value != 0: self.cells[i][j].delete(0, tk.END) self.cells[i][j].insert(0, str(value)) # 如果是算法填入的数字,显示为浅蓝色 if self.initial_board[i][j] == 0: self.cells[i][j].config(bg="#c0e0ff") def toggle_buttons(self, state): state_str = "normal" if state else "disabled" for widget in self.root.winfo_children(): for child in widget.winfo_children(): if isinstance(child, ttk.Frame): for button in child.winfo_children(): if isinstance(button, ttk.Button): button.config(state=state_str) def is_valid(self, board, row, col, num): # 检查行 for i in range(9): if board[row][i] == num: return False # 检查列 for i in range(9): if board[i][col] == num: return False # 检查3x3宫格 start_row, start_col = 3 * (row // 3), 3 * (col // 3) for i in range(3): for j in range(3): if board[start_row + i][start_col + j] == num: return False return True def solve_sudoku(self): # 使用回溯法求解数独 empty_cell = self.find_empty_cell() if not empty_cell: return True # 没有空格子,求解完成 row, col = empty_cell for num in range(1, 10): # 记录算法开始时间 algo_start = time.time() if self.is_valid(self.board, row, col, num): # 尝试填入数字 self.board[row][col] = num self.step_count += 1 # 更新UI - 确保格子显示为绿色 self.root.after(0, lambda r=row, c=col, v=num: self.update_cell_ui(r, c, v, "try")) # 记录算法结束时间 algo_end = time.time() self.algorithm_time += (algo_end - algo_start) self.update_stats() # 延迟用于演示 time.sleep(self.speed / 1000.0) # 递归求解 if self.solve_sudoku(): return True # 回溯 algo_start = time.time() self.board[row][col] = 0 self.step_count += 1 # 更新UI self.root.after(0, lambda r=row, c=col, v=0: self.update_cell_ui(r, c, v, "backtrack")) # 记录算法结束时间 algo_end = time.time() self.algorithm_time += (algo_end - algo_start) self.update_stats() # 延迟用于演示 time.sleep(self.speed / 1000.0) else: # 即使数字无效,也记录算法时间 algo_end = time.time() self.algorithm_time += (algo_end - algo_start) self.update_stats() return False def find_empty_cell(self): for i in range(9): for j in range(9): if self.board[i][j] == 0: return (i, j) return None def update_cell_ui(self, row, col, value, action_type): # 如果是初始题目格子,保持紫色,不改变 if self.initial_board[row][col] != 0: return if value == 0: self.cells[row][col].delete(0, tk.END) if action_type == "backtrack": self.cells[row][col].config(bg="#ffcccc") # 回溯时显示红色 # 立即更新UI self.root.update_idletasks() self.root.update() else: self.cells[row][col].delete(0, tk.END) self.cells[row][col].insert(0, str(value)) if action_type == "try": self.cells[row][col].config(bg="#ccffcc") # 尝试时显示绿色 # 立即更新UI self.root.update_idletasks() self.root.update() def update_stats(self): if hasattr(self, 'stats_label') and self.stats_label is not None: self.root.after(0, lambda: self.stats_label.config( text=f"步骤: {self.step_count} | 算法时间: {self.algorithm_time:.3f}s")) def main(): root = tk.Tk() app = SudokuSolver(root) root.mainloop() if __name__ == "__main__": main() 执行结果 ...

November 2, 2025 · 云雾海