八数码问题的深搜和广搜解法 | Python实现
八数码问题简介
在3×3的棋盘上,摆有八个棋子,每个棋子上标有1至8的某一数字。棋盘中留有一个空格,空格用0来表示。空格周围的棋子可以移到空格中。
要求解的问题是:给出一种初始布局(初始状态)和目标布局(为了使题目简单,设目标状态为123804765),找到一种最少步骤的移动方法,实现从初始布局到目标布局的转变。
数据结构
为了记录每个每一步走的状态,我们需要自己定义一个棋盘类Board
(类似结点)。
我们主要用到的属性有两个:前驱棋盘self.pre
和步数self.steps
。
class Board:
-
首先需要定义棋盘的构造方法
- 初始化它的前驱为空
- 初始化它的棋盘矩阵
- 同时更新它的步数(在一开始实例化的时候步数为0,因为前驱结点为空)
# 初始化棋盘状态,matrix为棋盘的矩阵 def __init__(self, matrix): self.pre = None self.matrix = matrix self.update_steps() # 根据父结点的步数来计算当前结点走的步数 def update_steps(self): if self.pre: self.steps = self.pre.steps + 1 else: self.steps = 0
-
定义打印棋盘结点的方式
- 一行行打印即可
# 打印棋盘 def show(self): res = [] res.append("第" + str(self.steps)+"步:\n") for i in range(3): res.append(" ".join([str(j) for j in self.matrix[i]]) + "\n") res.append("-" * 20+"\n") return res
-
从当前整个路径的根节点开始打印
- 不断找前驱结点,把每个棋盘的打印放入列表中
# 打印从根节点开始到当前节点的路径 def show_path(self): path = [] node = self while node: path = node.show()+path node = node.pre path = ["共需要"+str(self.steps)+"步\n"] + path return path
-
定位0的方法
- 返回0所在横坐标和纵坐标
# 找到0当前在棋盘中的位置 def find_zero(self): for i in range(3): for j in range(3): if self.matrix[i][j] == 0: return i, j
-
获取下一步可能的所有棋盘矩阵
- 先找到0的位置
- 然后尝试把0和上下左右的棋子交换位置,如果合法,就把可能的矩阵添加到数组中
- 最后返回一个三维的数组
# directions数组用来记录0可能的走的四个方向 directions = [[0, 1], [1, 0], [0, -1], [-1, 0]] # 下一步可能的棋盘 def next_possible_boards(self): res = [] i, j = self.find_zero() for direction in self.directions: x, y = direction if i + x >= 0 and i + x <= 2 and j + y >= 0 and j + y <= 2: next_board = copy.deepcopy(self.matrix) next_board[i][j], next_board[i + x][j +y] = next_board[i + x][j + y], next_board[i][j] res.append(next_board) return res
-
将棋盘序列化,即把棋盘的矩阵转成字符串
- 这是为了在遍历的时候更加方便地实现去重
# 将当前棋盘序列化,二维数组=>字符串 def serialize(self): tmp = "" for i in self.matrix: tmp += "".join([str(j) for j in i]) return tmp
-
判断是否到达目标状态
- 直接将当前棋盘序列化后和
123804765
对比
- 直接将当前棋盘序列化后和
文件I/O
-
读取文件
def read_file(file_name): lines = [] with open(file_name, encoding="utf-8") as f: lines = f.readlines() return lines
-
将从文件中读取的内容转换成矩阵
def get_matrix(lines): matrix = [] for line in lines: line = line.strip() tmp = [int(i) for i in line.split(" ")] matrix.append(tmp) return matrix
-
写文件
def write_file(file_name, lines): with open(file_name, 'w', encoding="utf-8") as f: for line in lines: f.write(str(line))
BFS
- 整体流程
BFS_8puzzle.py
- 算法实现
- 用一个数组去模拟队列,每一次遍历一层
- 遍历的时候,获取每个可能的棋盘的下一步棋盘,添加到下一次遍历的队列里
- 为了避免重复遍历,我们定义了一个
visited
集合,里面存储了已经遍历过的可能性的棋盘序列
def bfs(b):
# visited数组用来取消一些
visited = set()
queue = []
queue.append(b)
level = 0
while queue:
show_cur_level(queue, level)
# 每次遍历一层
tmp = []
for board in queue:
if board.finished():
path = board.show_path()
write_file("bfs_output.txt", path)
return
next_boards = board.next_possible_boards()
for matrix in next_boards:
next_board = Board(matrix)
if next_board.serialize() not in visited:
visited.add(next_board.serialize())
next_board.pre = board
next_board.update_steps()
tmp.append(next_board)
queue = tmp
level += 1
我也定义了一个函数,较清晰地展示每一层的情况
def show_cur_level(queue, level):
# 打印某个层级的所有棋盘
print("level:" + str(level))
for i in range(0, len(queue), 10):
for j in range(i, min(i+10, len(queue))):
print(queue[j].matrix[0], end=" ")
print()
for j in range(i, min(i+10, len(queue))):
print(queue[j].matrix[1], end=" ")
print()
for j in range(i, min(i+10, len(queue))):
print(queue[j].matrix[2], end=" ")
print("\n")
DFS
- 整体流程
- 算法实现
- 用一个递归函数来实现每次向下遍历
- 设定最大递归深度为10
- 同样的,利用
visited
集合来避免一条路上走回头路 - 向下遍历完之后运用回溯法的思想,消除回头路的证据