BFS实现八数码难题
这是一个非常经典的实验,作为人工智能导论课的第一个题目,思路上似乎不是很难,但是实现起来还是有点复杂,接下来讲解一下我的思路
BFS实现思路(源码会给在最下方)
BFS实现方法
- 大致思路
使用BFS实现还是比较简单的,只需要用队列的方式来储存每一个节点即可,使用python来存储这些类对象还是很简单的,然后就用while循环一层层的去对照节点的状态如何,直到队列空了则退出循环,视为查询失败,如果找到了目标节点,则提前退出while循环并返回该节点
该节点会存储着parent的指针,指向父类,这样就通过该节点向上找到整条路径,最后反过来输出就可以知道路径是怎么样的了
2. 代码实现
- 所需包:numpy,copy
- 所需数据结构队列(这里我自己写了一个封装好的Myqueue类)
其实直接用list也可以,用queue来作为OPEN表装节点
- Main函数内容:
首先是初始化最初状态和结束状态,这里我直接用一个一维数组来表示,可以方便扩展八数码问题 - 然后建立OPEN表和CLOSE表,用队列作为数据结构
- 将初始化State类,并将初始状态放入该类中
然后使用State类当中的Find()函数搜索
如果查询成功的话返回的是包含与TargetState相同的矩阵的State类
如果失败则返回一个没有parent的与OriginState相同矩阵的State类
State类构造以及Find函数
- State类的成员函数
parent指的是父亲节点,dir指的是一个字符串含有上一个节点到这一个节点的方向,默认为None
CuState是存储当前状态的数字一维矩阵,index表示八数码当中0(也就是空位)所处的索引位置
- Find函数讲解
首先是初始化的准备,全局引入一些变量,然后将初始节点入队
然后会通过计算逆序数来判断这个初始状态是否是有解的,也就是最终能否到达目标节点的状态
def calculate_Reverse(self,Matrix): #计算逆序数
State_Matrix = []
for i in range(9):
State_Matrix.append(Matrix[i])
State_Matrix.remove(0)
reverse_number = 0
for cur in State_Matrix:
index = State_Matrix.index(cur)
# 遍历cur后面的数字
for after_cur in State_Matrix[index:]:
if cur > after_cur:
reverse_number += 1
if reverse_number % 2 == 0:
return 0
else:
return 1
def is_Solvable(self,Tar_State): #判断该状态是否有解
init_Reverse_Num = self.calculate_Reverse(self.Matrix)
Tar_Reverse_Num = self.calculate_Reverse(Tar_State)
if init_Reverse_Num == Tar_Reverse_Num:
print("The State is solvable")
return 1
else:
print("The State is unsolvable")
return 0
接下来就可以开始进行while循环,每一次循环loop都是对队列的头节点取出来并且进行产生新的分支节点
!!这里要注意用深拷贝的方法取得一个新的结点,因为Fir_State同时指向了上一个节点的多个State对象(主要是懒得换名字)
下面是四个状态的代码(其实代码是差不多的,主要还是判断条件有点不太一样)
if ((Index + 1) % 3 != 0): #右边界条件
Next_State = self.Create_Next_State(Index, Index + 1, First_State, "Right")
if not self.is_State_Existed(CLOSE,Next_State):
print("Move Right")# 代表可以向右移动
stateNum += 1
if (Next_State.Matrix == TargetState).all():
return Next_State
else:
#Next_State.ShowState()
OPEN.push(Next_State)
if (Index % 3 != 0): # 代表可以向左移动
Next_State = self.Create_Next_State(Index, Index - 1, First_State, "Left")
if not self.is_State_Existed(CLOSE,Next_State):
print("Move Left")
stateNum += 1
if (Next_State.Matrix == TargetState).all():
return Next_State
else:
#Next_State.ShowState()
OPEN.push(Next_State)
if (Index - 3 >= 0): # 代表可以向上移动
Next_State = self.Create_Next_State(Index, Index - 3, First_State, "Up")
if not self.is_State_Existed(CLOSE,Next_State):
print("Move Up")
stateNum += 1
if (Next_State.Matrix == TargetState).all():
return Next_State
else:
#Next_State.ShowState()
OPEN.push(Next_State)
if (Index + 3 <= 8): # 代表可以向下移动
Next_State = self.Create_Next_State(Index, Index + 3, First_State, "Down")
if not self.is_State_Existed(CLOSE,Next_State):
print("Move Down")
stateNum += 1
if (Next_State.Matrix == TargetState).all():
return Next_State
else:
#Next_State.ShowState()
OPEN.push(Next_State)
- State类的成员函数(除了Find之外)
def ShowState(self):#打印矩阵信息
print("Move ",self.Direction)
for i in range(3):
for j in range(3):
print(self.Matrix[j + i * 3],end=" ")
print()
print()
def Swap(self, Cu_index, Next_index, NState):#交换矩阵当中的两个数
temp = NState[Cu_index]
NState[Cu_index] = NState[Next_index]
NState[Next_index] = temp
def Create_Next_State(self, Cu_index, Next_index, Fir_State, Dir):#通过上一个节点来产生一个新的State类,含有新的方向和数组
Next_State = State(
CuState=Fir_State.Matrix.copy(),
index=Next_index,
parent=Fir_State, #指向上一个节点
dir=Dir)
self.Swap(Cu_index, Next_index, Next_State.Matrix)
return Next_State
def is_State_Existed(self,CLOSE,Next_State):#用来判读新的状态是否和CLOSE表里面的有重叠
for node in CLOSE._queue:
if(Next_State.Matrix == node.Matrix).all():
return 1 #表示已经有元素存在,不会将该状态入队
return 0
比较重要的还是is_State_Existed()
用来判重,其他的基本就是字面意思,都很好理解
最后退出循环并且打印,没有就返回
成功返回状态节点后打印
遇到的难点
- 首先是思路的构建,如何让代码更加简洁的表示出来吧,目前代码还是感觉重写性很高,不够优美。
First_State = copy.deepcopy(Fir_State) #这里用深拷贝是因为之前四个状态都被同一个名字指向了
为什么这里要用深拷贝呢,是因为python它的局部变量竟然会保留并且跑出来,这让我如何是好,指针指向了多个对象实例,于是一旦更改就全部更改了,所以只能深拷贝一个防止修改了全部了
最后附上全部代码
import numpy as np
import copy
class MyQueue: # 队列数据结构
def __init__(self, size):
self.front = -1
self.rear = -1
self._size = size
self._queue = []
def push(self, data):
if not (self.isFull()):
self._queue.append(data)
self.rear += 1
def pop(self):
if not (self.isEmpty()):
self._queue.pop(0)
self.rear -= 1
def isFull(self): # 队列满时返回1
return self.rear == self._size - 1
def isEmpty(self): # 队列空时返回1
return self.rear == -1
def getFont(self):
if (self.isEmpty() == 0):
return self._queue[0]
class State:
def __init__(self, CuState, index, parent=None, dir=None):
self.parent = parent # 父亲节点
self.Direction = dir
self.Matrix = CuState
self.CuIndex = index
def ShowState(self):#打印矩阵信息
print("Move ",self.Direction)
for i in range(3):
for j in range(3):
print(self.Matrix[j + i * 3],end=" ")
print()
print()
def Swap(self, Cu_index, Next_index, NState):#交换矩阵当中的两个数
temp = NState[Cu_index]
NState[Cu_index] = NState[Next_index]
NState[Next_index] = temp
def Create_Next_State(self, Cu_index, Next_index, Fir_State, Dir):#通过上一个节点来产生一个新的State类,含有新的方向和数组
Next_State = State(
CuState=Fir_State.Matrix.copy(),
index=Next_index,
parent=Fir_State, #指向上一个节点
dir=Dir)
self.Swap(Cu_index, Next_index, Next_State.Matrix)
return Next_State
def is_State_Existed(self,CLOSE,Next_State):#用来判读新的状态是否和CLOSE表里面的有重叠
for node in CLOSE._queue:
if(Next_State.Matrix == node.Matrix).all():
return 1 #表示已经有元素存在,不会将该状态入队
return 0
def Find(self):
global OPEN,CLOSE, TargetState
OPEN.push(self)
stateNum = 0
Index = self.CuIndex # 当前索引位置
while True:
if (OPEN.isEmpty()): # 若表空,则表示查询失败
break
Fir_State = OPEN.getFont()
First_State = copy.deepcopy(Fir_State) #这里用深拷贝是因为之前四个状态都被同一个名字指向了
Index = First_State.CuIndex
OPEN.pop()
CLOSE.push(First_State)
if ((Index + 1) % 3 != 0): #右边界条件
Next_State = self.Create_Next_State(Index, Index + 1, First_State, "Right")
if not self.is_State_Existed(CLOSE,Next_State):
print("Move Right")# 代表可以向右移动
stateNum += 1
if (Next_State.Matrix == TargetState).all():
return Next_State
else:
#Next_State.ShowState()
OPEN.push(Next_State)
if (Index % 3 != 0): # 代表可以向左移动
Next_State = self.Create_Next_State(Index, Index - 1, First_State, "Left")
if not self.is_State_Existed(CLOSE,Next_State):
print("Move Left")
stateNum += 1
if (Next_State.Matrix == TargetState).all():
return Next_State
else:
#Next_State.ShowState()
OPEN.push(Next_State)
if (Index - 3 >= 0): # 代表可以向上移动
Next_State = self.Create_Next_State(Index, Index - 3, First_State, "Up")
if not self.is_State_Existed(CLOSE,Next_State):
print("Move Up")
stateNum += 1
if (Next_State.Matrix == TargetState).all():
return Next_State
else:
#Next_State.ShowState()
OPEN.push(Next_State)
if (Index + 3 <= 8): # 代表可以向下移动
Next_State = self.Create_Next_State(Index, Index + 3, First_State, "Down")
if not self.is_State_Existed(CLOSE,Next_State):
print("Move Down")
stateNum += 1
if (Next_State.Matrix == TargetState).all():
return Next_State
else:
#Next_State.ShowState()
OPEN.push(Next_State)
print("One Layer through,now StateNum is ",stateNum)
print()
return Or_State
if __name__ == '__main__':
# OriginStatus = input()
if 0:
OriginState = np.array([2, 8, 3,
1, 0, 4,
7, 6, 5])
if 1:
OriginState = np.array([1, 6, 2,
8, 0, 3,
7, 5, 4])
TargetState = np.array([1, 2, 3,
8, 0, 4,
7, 6, 5])
OPEN = MyQueue(300)
CLOSE = MyQueue(300)
Or_State = State(CuState=OriginState, index=4)
EndState = Or_State.Find() #若查询失败返回Origin节点,没有父类,因此不输出
Path = [] # 用来存储节点路径,用于正序输出
while (EndState.parent is not None):
Path.append(EndState)
EndState = EndState.parent
if(Path):
Or_State.ShowState()
Path.reverse()
for i in Path:
i.ShowState()