from tkinter import *
from tkinter.messagebox import *
import random
from collections import deque
import heapq
import time
class PuzzleSolver:
def __init__(self, root, title, x_pos, y_pos, initial_state=None):
# 创建独立窗口
self.window = Toplevel(root)
self.window.title(title)
self.window.geometry(f"350x500+{x_pos}+{y_pos}")
# 常量定义
self.WIDTH, self.HEIGHT = 312, 450
self.IMAGE_WIDTH, self.IMAGE_HEIGHT = self.WIDTH // 3, self.HEIGHT // 3
self.ROWS, self.COLS = 3, 3
self.GOAL_STATE = ((0, 1, 2), (3, 4, 5), (6, 7, None))
# 加载图片
self.Pics = []
for i in range(9):
filename = f"cx_{i}.png"
try:
self.Pics.append(PhotoImage(file=filename))
except:
# 创建空白图像作为替代
self.Pics.append(PhotoImage(width=100, height=100))
print(f"警告:图片 {filename} 不存在,使用空图像替代")
# 拼图状态
self.board = [[None for _ in range(self.COLS)] for _ in range(self.ROWS)]
self.results = {"steps": 0, "search_space": 0, "time": 0}
self.initial_state = initial_state # 用于同步初始状态
# 创建界面
self.create_ui()
# 初始化拼图
self.init_board()
# 图像块类
class Square:
def __init__(self, orderID):
# 严格验证ID有效性
if not (isinstance(orderID, int) and 0 <= orderID <= 7):
raise ValueError(f"无效拼图ID: {orderID},必须是0-7的整数")
self.orderID = orderID
# 获取当前状态(确保类型安全)
def get_current_state(self):
state = []
for i in range(self.ROWS):
row = []
for j in range(self.COLS):
val = self.board[i][j].orderID if self.board[i][j] is not None else None
row.append(val)
state.append(tuple(row))
return tuple(state)
# 设置拼图状态
def set_board_state(self, state):
# 验证状态合法性
for i in range(self.ROWS):
for j in range(self.COLS):
val = state[i][j]
if val is not None and not (isinstance(val, int) and 0 <= val <= 7):
raise ValueError(f"无效状态值: {val},必须是0-7的整数或None")
self.board = [[None for _ in range(self.COLS)] for _ in range(self.ROWS)]
for i in range(self.ROWS):
for j in range(self.COLS):
val = state[i][j]
if val is not None:
self.board[i][j] = self.Square(val)
self.update_display()
# 找到空白块位置
def find_empty(self, state):
for i in range(self.ROWS):
for j in range(self.COLS):
if state[i][j] is None:
return i, j
return -1, -1 # 理论上不会触发
# 生成新状态
def generate_new_state(self, state, i1, j1, i2, j2):
if not (0 <= i1 < self.ROWS and 0 <= j1 < self.COLS and
0 <= i2 < self.ROWS and 0 <= j2 < self.COLS):
return state
# 转换为列表进行修改
state_list = [list(row) for row in state]
# 交换两个位置
state_list[i1][j1], state_list[i2][j2] = state_list[i2][j2], state_list[i1][j1]
return tuple(tuple(row) for row in state_list)
# 初始化拼图(使用相同的初始状态)
def init_board(self):
if self.initial_state is not None:
# 使用提供的初始状态
self.set_board_state(self.initial_state)
return
# 生成新的可解状态
while True:
# 生成0-7和None的随机序列
nums = list(range(8)) + [None]
random.shuffle(nums)
state = (tuple(nums[:3]), tuple(nums[3:6]), tuple(nums[6:9]))
# 可解性检查
flat = [x for row in state for x in row if x is not None]
inversions = 0
for i in range(len(flat)):
for j in range(i + 1, len(flat)):
if flat[i] > flat[j]:
inversions += 1
blank_row = self.find_empty(state)[0]
# 可解条件:逆序数 + 空白块行号为偶数
if (inversions + blank_row) % 2 == 0:
self.initial_state = state # 保存初始状态供另一个拼图使用
self.set_board_state(state)
break
# 绘制拼图
def draw_board(self):
self.canvas.delete('all')
# 画外框
self.canvas.create_polygon((0, 0, self.WIDTH, 0, self.WIDTH, self.HEIGHT, 0, self.HEIGHT),
width=1, outline='Black')
# 画拼图块
for i in range(self.ROWS):
for j in range(self.COLS):
if self.board[i][j] is not None:
self.canvas.create_image(
self.IMAGE_WIDTH * (j + 0.5),
self.IMAGE_HEIGHT * (i + 0.5),
image=self.Pics[self.board[i][j].orderID]
)
# 更新显示
def update_display(self):
self.draw_board()
self.window.update()
# 创建界面元素
def create_ui(self):
self.canvas = Canvas(self.window, bg='green', width=self.WIDTH, height=self.HEIGHT)
self.status_label = Label(self.window, text="等待求解...", fg="blue")
self.result_label = Label(self.window, text="结果将显示在这里", justify=LEFT, wraplength=300)
self.canvas.pack()
self.status_label.pack(pady=5)
self.result_label.pack(pady=5)
# BFS求解
def bfs_solve(self):
start_state = self.get_current_state()
if start_state == self.GOAL_STATE:
showinfo("提示", "已是完成状态!")
return
self.status_label.config(text="BFS求解中...")
start_time = time.time()
queue = deque([(start_state, [])])
visited = set([start_state])
found = False
try:
while queue:
current_state, path = queue.popleft()
if current_state == self.GOAL_STATE:
self.results["steps"] = len(path)
self.results["search_space"] = len(visited)
self.results["time"] = time.time() - start_time
# 展示求解过程
self.set_board_state(start_state)
self.window.update()
time.sleep(1)
for move in path:
self.set_board_state(move)
time.sleep(0.5)
self.status_label.config(text="BFS求解完成")
self.result_label.config(
text=f"BFS结果:\n步数: {self.results['steps']}\n搜索空间: {self.results['search_space']}\n耗时: {self.results['time']:.4f}秒")
found = True
break
i, j = self.find_empty(current_state)
# 尝试四个方向移动
for di, dj in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
ni, nj = i + di, j + dj
if 0 <= ni < self.ROWS and 0 <= nj < self.COLS:
new_state = self.generate_new_state(current_state, i, j, ni, nj)
if new_state not in visited:
visited.add(new_state)
queue.append((new_state, path + [new_state]))
if not found:
self.status_label.config(text="BFS未找到解")
except Exception as e:
self.status_label.config(text=f"BFS出错")
showinfo("错误", f"BFS算法出错: {str(e)}")
# A*求解 - 修复后的版本
def astar_solve(self):
start_state = self.get_current_state()
if start_state == self.GOAL_STATE:
showinfo("提示", "已是完成状态!")
return
self.status_label.config(text="A*求解中...")
start_time = time.time()
# 曼哈顿距离启发函数(确保始终返回整数)
def manhattan_distance(state):
distance = 0
for i in range(self.ROWS):
for j in range(self.COLS):
val = state[i][j]
# 只处理整数拼图块(空白块None跳过)
if isinstance(val, int):
target_i = val // 3
target_j = val % 3
distance += abs(i - target_i) + abs(j - target_j)
return distance # 确保返回整数
# 优先队列初始化
initial_g = 0
initial_h = manhattan_distance(start_state)
initial_f = initial_g + initial_h
# 添加节点ID防止比较冲突
next_node_id = 0
priority_queue = []
heapq.heappush(priority_queue, (initial_f, next_node_id, initial_g, start_state, []))
next_node_id += 1
visited = {start_state: initial_g}
found = False
try:
while priority_queue:
# 弹出f值最小的状态
current_f, _, current_g, current_state, path = heapq.heappop(priority_queue)
# 检查是否达到目标状态
if current_state == self.GOAL_STATE:
self.results["steps"] = len(path)
self.results["search_space"] = len(visited)
self.results["time"] = time.time() - start_time
# 展示求解过程
self.set_board_state(start_state)
self.window.update()
time.sleep(1)
for move in path:
self.set_board_state(move)
time.sleep(0.5)
self.status_label.config(text="A*求解完成")
self.result_label.config(
text=f"A*结果:\n步数: {self.results['steps']}\n搜索空间: {self.results['search_space']}\n耗时: {self.results['time']:.4f}秒")
found = True
break
# 找到空白块位置
empty_i, empty_j = self.find_empty(current_state)
# 尝试四个方向移动
for di, dj in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
ni, nj = empty_i + di, empty_j + dj
if 0 <= ni < self.ROWS and 0 <= nj < self.COLS:
new_state = self.generate_new_state(current_state, empty_i, empty_j, ni, nj)
new_g = current_g + 1 # 步数+1
# 验证启发值计算正确
try:
new_h = manhattan_distance(new_state)
new_f = new_g + new_h
except Exception as e:
print(f"启发式计算错误: {e}")
continue
# 检查是否发现更优路径
if new_state not in visited or new_g < visited[new_state]:
visited[new_state] = new_g
# 添加唯一节点ID防止比较冲突
heapq.heappush(priority_queue,
(new_f, next_node_id, new_g, new_state, path + [new_state]))
next_node_id += 1
if not found:
self.status_label.config(text="A*未找到解")
except Exception as e:
self.status_label.config(text=f"A*出错")
showinfo("错误", f"A*算法出错: {str(e)}")
# 主程序
if __name__ == "__main__":
root = Tk()
root.title("拼图算法比较")
root.geometry("400x150")
# 存储两个拼图实例和共享的初始状态
puzzle_bfs = None
puzzle_astar = None
shared_initial_state = None
# 创建两个相同初始状态的拼图
def create_identical_puzzles():
global puzzle_bfs, puzzle_astar, shared_initial_state
# 先创建BFS拼图并获取其初始状态
puzzle_bfs = PuzzleSolver(root, "BFS算法求解", 100, 100)
shared_initial_state = puzzle_bfs.initial_state
# 使用相同的初始状态创建A*拼图
puzzle_astar = PuzzleSolver(root, "A*算法求解", 500, 100, shared_initial_state)
# 启用求解按钮
btn_solve_bfs.config(state=NORMAL)
btn_solve_astar.config(state=NORMAL)
# 显示对比结果
def show_comparison():
if not puzzle_bfs or not puzzle_astar:
showinfo("提示", "请先创建拼图")
return
bfs = puzzle_bfs.results
astar = puzzle_astar.results
if bfs["steps"] == 0 or astar["steps"] == 0:
showinfo("提示", "请先完成两种算法的求解")
return
result = "算法效率对比(相同初始状态):\n\n"
result += f"BFS:\n 步数: {bfs['steps']}\n 搜索空间: {bfs['search_space']}\n 耗时: {bfs['time']:.4f}秒\n\n"
result += f"A*:\n 步数: {astar['steps']}\n 搜索空间: {astar['search_space']}\n 耗时: {astar['time']:.4f}秒\n\n"
# 计算效率提升
space_ratio = bfs["search_space"] / astar["search_space"]
time_ratio = bfs["time"] / astar["time"] if astar["time"] > 0 else 0
result += f"A*搜索空间约为BFS的{1 / space_ratio:.2%}\n"
result += f"A*速度约为BFS的{time_ratio:.2f}倍"
showinfo("算法对比结果", result)
# 主界面按钮
Label(root, text="拼图搜索算法比较", font=("Arial", 16)).pack(pady=10)
frame = Frame(root)
frame.pack(pady=10)
btn_create = Button(frame, text="创建相同初始状态拼图", command=create_identical_puzzles, width=20)
btn_solve_bfs = Button(frame, text="BFS求解", command=lambda: puzzle_bfs.bfs_solve() if puzzle_bfs else None,
width=10, state=DISABLED)
btn_solve_astar = Button(frame, text="A*求解", command=lambda: puzzle_astar.astar_solve() if puzzle_astar else None,
width=10, state=DISABLED)
btn_compare = Button(root, text="显示算法对比", command=show_comparison, width=20)
btn_create.grid(row=0, column=0, padx=5)
btn_solve_bfs.grid(row=0, column=1, padx=5)
btn_solve_astar.grid(row=0, column=2, padx=5)
btn_compare.pack(pady=10)
root.mainloop()
对这个代码进行模块划分
最新发布