一、实验描述
- 实验目的:
(1)熟悉人工智能系统中的问题求解过程;
(2)熟悉状态空间中的盲目搜索策略;
(3)掌握盲目搜索算法,重点是宽度优先搜索和深度优先搜索算法。 - 实验内容:
用Python语言编程,采用宽度优先搜索和深度优先搜索方法,求解8数码问题
(1)采用宽度优先算法,运行程序,要求输入初始状态
假设给定如下初始状态 S 0 S_0 S0
2 8 3
1 6 4
7 0 5
和目标状态 S g S_g Sg
2 1 6
4 0 8
7 5 3
验证程序的输出结果,写出心得体会。
(2)对代码进行修改(选作),实现深度优先搜索求解该问题
提示:每次选扩展节点时,从数组的最后一个生成的节点开始找,找一个没有被扩展的节点。这样也需要对节点添加一个是否被扩展过的标志。
二、涉及知识点
- 问题的状态空间表示
(1)状态
事物是运动、变化的,为描述问题的运动,变化,定义一组变量描述问题的变化特征和属性。形式化表示: ( s 1 , s 2 , . . . , s i , . . . , S − n ) (s_1,s_2,...,s_i,...,S-n) (s1,s2,...,si,...,S−n)对每一个分量都给以确定的值时,就得到了一个具体的状态。
(2)操作符(Operator)
也称为算符,它是把问题从一种状态变换为另一种状态的手段。操作可以是一个机械步骤,一个运算,一条规则或一个过程。操作可理解为状态集合上的一个函数,它描述了状态之间的关系。
(3)状态空间
用来描述一个问题的全部状态以及这些状态之间的相互关系。常用一个三元组表示为 ( S , F , G ) (S,F,G) (S,F,G)
其中:
S为问题的所有初始状态的集合
F为操作(函数、规则等)的集合
G为目标状态的集合
(4)状态空间图
状态空间的有向图表示:
结点(节点):节点表示问题的 状态
弧(有向边):标记操作符;可能的路径代价
(5)状态空间法求解问题的基本过程
首先为问题选择适当的“状态”及“操作”的形式化描述方法;然后从某个初始状态出发,每次使用一个满足前提条件的“操作”,且此操作产生了新的状态,递增地建立起操作序列,直到达到目标状态为止。 - 状态空间地盲目搜索
(1)基本思想:
先把问题地初始状态作为当前扩展节点对其进行扩展,生成一组子节点,然后检查问题地目标状态是否出现在这些子结点中。若出现,则搜索成功,找出了问题地解;若没出现,则再按照某种搜索策略从已生成的子结点中选择一个节点作为当前扩展节点。重复上述过程,知道目标状态出现在子结点中或者没有可供操作的节点为止。所谓对一个节点进行“扩展”是指对该节点用某个可用操作进行作用,生成该节点的一组子节点。
(2)扩展节点
对某一节点(状态),选择合适的操作符作用在节点上,使产生后继状态(子节点)的操作。类似数据结构中的寻找邻接点,但这里的邻接点使选择操作后产生的。
(3)Open表和Closed表
这两个表用来存放节点,Open表存放未扩展节点,Closed表存放已扩展节点和待扩展节点。两个表的结构可以相同。 - 宽度优先搜索(BFS)
(1)基本思想:
从初始节点S开始,依据到S的深度,逐层扩展节点并考察其是否目标节点。在第n层节点没有完全扩展之前,不对第n+1层节点进行扩展。即:Open表排序策略为新产生的节点放到Open表的末端。
(2)状态空间广度优先搜索
a. 把初始节点 S 0 S_0 S0 放入Open表中;
b. 如果Open表为空,则问题无解,失败退出;
c. 把Open表的第一个节点取出放入Closed表,并记该节点为n;
d. 考察节点n是否为目标节点。若是,则得到问题的解,成功退出;
e.若节点n不可扩展,则转第b步;
f. 扩展节点n,将其子节点放入Open表的尾部,并为每一个子节点设置指向父节点的指针,然后转第b步。 - 深度优先搜索(DFS)
(1)基本思想
从初始节点S开始,优先扩展最新产生的节点(最深的节点)。即:Open表的排序策略为新产生的节点放入Open表的前端,优先扩展。
(2)状态空间深度优先搜索
a. 把初始节点 S 0 S_0 S0 放入Open表中;
b. 如果Open表为空,则问题无解,失败退出;
c. 把Open表的第一个节点取出放入Closed表,并记该节点为n;
d. 考察节点n是否为目标节点。若是,则得到问题的解,成功退出;
e. 若节点n不可扩展,则转第b步;
f. 扩展节点n,将其子节点放入Open表的首,并为每一个子节点设置指向父节点的指针,然后转第b步。
三、实验步骤
系统给出该实验部分代码,需要用户补全
用户补全代码示例:
查看子状态中有没有最终状态,如果有则输出之前的父状态到path中,输出step+1
###########开始1#############
for s in subStates:
if (s.state == s.answer).all():#all() 函数用于判断给定的可迭代参数 iterable 中的所有元素是否都为 TRUE,如果是返回 True,否则返回 False。
path = []#存放父状态地方
while s.parent and s.parent != s1:
path.append(s.parent)
s = s.parent
path.reverse()#reverse() 函数用于反向列表中元素。
return path, steps+1
###########结束1#############
# 将子状态添加到openTable中
###########开始2#############
openTable.extend(subStates)
steps += 1
###########结束2#############
用户点击运行,系统返回运行结果给用户,如下:
测试输入:
2 8 3
1 0 4
7 6 5
输出:
2 8 3
1 0 4
7 6 5
->
2 0 3
1 8 4
7 6 5
->
0 2 3
1 8 4
7 6 5
->
1 2 3
0 8 4
7 6 5
->
[[1 2 3]
[8 0 4]
[7 6 5]]
Total steps is 27
代码实现
import numpy as np
class State:
def __init__(self, state, directionFlag=None, parent=None):
self.state = state
# state is a ndarray with a shape(3,3) to storage the state
self.direction = ['up', 'down', 'right', 'left']
if directionFlag:
self.direction.remove(directionFlag)
# record the possible directions to generate the sub-states
self.parent = parent
def showInfo(self):
for i in range(3):
for j in range(3):
print(self.state[i, j], end=' ')
print("\n")
print('->')
return
def getEmptyPos(self):
postion = np.where(self.state == self.symbol)
return postion
def generateSubStates(self):#产生子节点
if not self.direction:
return []
subStates = []
boarder = len(self.state) - 1
# the maximum of the x,y
row, col = self.getEmptyPos()
if 'left' in self.direction and col > 0:#向左移动
s = self.state.copy()
#标志位symbol=0向左移动,产生新的状态节点,加入到subStates中
temp = s.copy()
s[row, col] = s[row, col-1]
s[row, col-1] = temp[row, col]
news = State(s, directionFlag='right', parent=self)
subStates.append(news)
if 'up' in self.direction and row > 0:
s = self.state.copy()
# 标志位symbol=0向上移动,产生新的状态节点,加入到subStates中
temp = s.copy()
s[row, col] = s[row-1, col]
s[row-1, col] = temp[row, col]
news = State(s, directionFlag='down', parent=self)
subStates.append(news)
if 'down' in self.direction and row < boarder: #it can move to down place
s = self.state.copy()
# 标志位symbol=0向下移动,产生新的状态节点,加入到subStates中
temp = s.copy()
s[row, col] = s[row+1, col]
s[row+1, col] = temp[row, col]
news = State(s, directionFlag='up', parent=self)
subStates.append(news)
if self.direction.count('right') and col < boarder: #it can move to right place
s = self.state.copy()
# 标志位symbol=0向右移动,产生新的状态节点,加入到subStates中
temp = s.copy()
s[row, col] = s[row, col+1]
s[row, col+1] = temp[row, col]
news = State(s, directionFlag='left', parent=self)
subStates.append(news)
#end1
return subStates
def BFS(self):
# generate a empty openTable
openTable = [] #存放状态的地方
# append the origin state to the openTable
openTable.append(self)#将初始状态加入
steps = 1 #步骤
while len(openTable) > 0:
n = openTable.pop(0)#pop() 函数用于移除列表中的一个元素(默认最后一个元素),并且返回该元素的值。
subStates = n.generateSubStates()
# 查看子状态中有没有最终状态,如果有则输出之前的父状态到path中,输出step+1
###########开始1#############
path = []
for subState in subStates:
if np.array_equal(subState.state, State.answer):
while subState.parent and subState.parent != self:
path.append(subState.parent)
subState = subState.parent
path.reverse()
return path, steps+1
###########结束1#############
# 将子状态添加到openTable中
###########开始2#############
openTable.extend(subStates)
steps += 1
###########结束2#############
else:
return None, None
State.symbol = 0
State.answer = np.array([[1, 2, 3], [8, 0, 4], [7, 6, 5]])
s1 = State(np.array([[2, 8, 3], [1,6 , 4], [7, 0, 5]]))
path, steps = s1.BFS()
if path: # if find the solution
for node in path:
# print the path from the origin to final state
node.showInfo()
print(State.answer)
print("Total steps is %d" % steps)