状态空间搜索问题或者称迷宫问题的基本特征是:
1、存在一集合可能状态(位置、情况等。这一集合可能很大)。例:迷宫问题中的可能位置。
2、有一个初始状态S0,一个或者多个结束状态,或者有判断成功结束的方法。例子:迷宫问题中的入口表示初始状态,出口表示结束状态
3、对每一个状态s,有neighbors表示与s相邻的一组状态(一步可达的状态)。例如:迷宫中每个位置的四个相邻位置。
4、有一个判断函数valid判断s是否是可行状态,前面迷宫算法中的函数passable实现这种判断。
5、问题:找出从s0出发到达某个(或全部)结束状态的路径;或者从s0出发,设法找到一个或者全部解(即,一个或者全部结束状态。)
这种问题,可以用递归方法求解,通过函数递归的方式向前探查,通过函数返回的方式回溯。也可以用一个栈保存中间信息,采用非递归方式通过回溯法求解。
需要通过状态空间搜索解决的额问题很多,经典的简单实例如:
I、八皇后问题(在国际象棋棋盘上为八个皇后安排的位置,使之不能相互攻击)
II、骑士周游问题(在国际棋盘上为骑士找到一条路径,使之可以经过棋盘的每个格子恰好一次,骑士的走法和中国象棋中的马一样,走“日”字)。
许多实际问题需要通过空间搜索的方法解决,例如,许多的调度、规划、优化问题(背包问题),数学定理证明(基于一些事实和推理规则)等等。
对于这样的问题,我们采用一个迷宫问题来简单介绍一下,
其中有两个比较重要的是
1、如何表示“东南西北”或者“上下左右”四个方向,
为能方便计算相邻的位置,这里定义一个二元组(二元序对)的表,其元素是从位置(i,j)得到其四相邻位置应该加的数对:
dirs=[(0,1),(1,0),(0,-1),(-1,0)]
对于任何一个位置(i,j),给他加上dirs[0],dirs[1],dirs[2],dirs[3],就分别得到该位置东南西北四个位置
2、如何记录他已经走过的路径
为了是算法方便描述,我们要提前定义两个函数:
首先是递归解法:
算法:
1、用Mark函数标记当前位置
2、检查当前位置是否为出口,如果是则成功结束
3、逐个检查当前位置的四邻是否可以到达出口(递归调用前身)
4、如果对四邻的探索都失败,报告失败
# coding=gbk
dirs=[(0,1),(1,0),(0,-1),(-1,0)]
'''给迷宫maze的位置标2表示“到过了”'''
def make(pos,maze):
maze[pos[0]][pos[1]]=2
'''检测maze位置是否可行'''
def passable(maze,pos):
return maze[pos[0]][pos[1]] == 0
'''核心算法'''
def find_path(maze,pos,end):
make(pos,maze)
if pos==end:
print(pos,end=" ")
return True
for i in range(4):
nextp=pos[0]+dirs[i][0],pos[1]+dirs[i][1]
'''考虑下一个可能的方向'''
if passable(maze,pos):
if find_path(maze,nextp,end):
print(pos,end=" ")
return True
return False
栈与回溯法
回溯法:
前进:
条件:当前位置存在尚未探查的四邻位置
操作:选定下一个位置并向前探查。如果还存在其他可能未探查的分支,就记录相关信息,以便将来使用。
如果找到出口,则成功结束
后退:
条件:遇到思路,不存在尚未探查的四邻位置
操作:退回最近记录的那个分支点,检查那里是否还存在未探查分支。如果有,就取一个未探查林位置作为当前位置并前进,没有就将其删除并继续回溯。
已穷尽所有可能:
不能找到出口时以失败结束
#coding=gbk
dirs=[(0,1),(1,0),(0,-1),(-1,0)]
'''给迷宫maze的位置标2表示“到过了”'''
def mark(pos,maze):
maze[pos[0]][pos[1]]=2
'''检测maze位置是否可行'''
def passable(maze,pos):
return maze[pos[0]][pos[1]] == 0
def maze_slover(maze,start,end):
if start==end:
print(start)
return
st = []
mark(start, 0)
st.append((start,0))
while not st.empty():
pos,nxt = st.pop()
for i in range(nxt,4):
nextp = (pos[0]+dirs[i][0], pos[1]+dirs[i][1])
if nextp==end:
print(end,pos,st)
if passable(maze,nextp):
st.append((pos,i+1))
mark(nextp,maze)
st.apppend((nextp,0))
break
print("No path found.")
这个算法有几个问题需要申明:
1、算法发现一个新位置后,总是先标记它,然后再将其压入栈。这个做法保证了栈里保存的都是做过标记的位置,也保证了任何位置不会被压入两次。