A*算法求解八数码难题(python实现)
Posted Life-lover
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了A*算法求解八数码难题(python实现)相关的知识,希望对你有一定的参考价值。
目录
前言
本文用python实现A*算法解决了八数码问题,有被八数码问题困扰的uu们,抓紧时间,进入正文,一定会对你有所帮助!
一、八数码难题是什么?
八数码问题是在一个3×3的棋盘上有1−8位数字随机分布,以及一个空格,与空格相连的棋子可以滑动到空格中,问题的解是通过空格滑动,使得棋盘转化为目标状态,如下图所示。
7 2 4 0 1 2
5 0 6 -----------> 3 4 5
8 3 1 6 7 8
init state target state
为了简化问题的输入,首先将空格用数字0表示,然后将3×3的棋盘用9位长的字符串表示,则上图的初始状态为724506831,目标状态为012345678.
对于上图的初始状态,将数字2移动到空格,称之为u操作(空格上移),将数字3移动到空格,称之为d操作(空格下移),将数字5移动到空格,称之为l操作(空格左移),将数字6移动到空格,称之为r操作(空格右移)
二、算法详解
1.启发函数(曼哈顿距离)
def calcDistH(self, src_map, dest_map):
#采用曼哈顿距离作为估值函数
cost = 0
for i in range(len(src_map)):
if (src_map[i] != '0'):
cost += abs(int(src_map[i])//3-i//3) + \\
abs(int(src_map[i]) % 3-i % 3)
return cost
2.状态移动处理
def moveMap(self, cur_map, i, j):
cur_state = [cur_map[i] for i in range(9)]
cur_state[i] = cur_state[j]
cur_state[j] = '0'
return "".join(cur_state)
3. A*搜索并返回路径
def salvePuzzle(self, init, targ):
#传入初始状态init,目标状态targ,返回移动路径(string形式)
open = [(init, self.calcDistH(init, targ))]
#open表元素(状态,f值)
closed =
dict_depth = init: 0
#深度表,用来记录节点是否被访问过及对应的深度
dict_link =
#关系表,用来记录父子关系,孩子-->父亲
dict_dirs = 'u': [-1, 0], 'd': [1, 0], 'l': [0, -1], 'r': [0, 1]
#移动方向,对应二维坐标的变化
dirs = ['l', 'r', 'u', 'd']
path = []
#用来记录移动路径
while (len(open)):
open.sort(key=lambda open: open[1])
#每循环一次对列表按由小到大排序一次
while (open[0][0] in closed):
open.pop([0])
#如果表头元素在closed表中,移出open表
if (open[0][0] == targ):
print("Successfully!")
break
top = open[0]
open[0:1] = []
closed[top[0]] = top[1]
#取表头元素,并将表头元素移出open表,压入closed表
cur_index = top[0].index('0')
for i in range(4):
x = cur_index // 3 + dict_dirs[dirs[i]][0]
y = cur_index % 3 + dict_dirs[dirs[i]][1]
if (x >= 0 and x < 3 and y >= 0 and y < 3):
next_index = x*3+y
next_state = self.moveMap(top[0], cur_index, next_index)
depth = dict_depth[top[0]]+1
#当前节点不在深度表中,压入深度表和open表,并建立父子关系
if ((next_state in dict_depth) == False):
dict_depth[next_state] = depth
open.append(
(next_state, depth+self.calcDistH(next_state, targ)))
dict_link[next_state] = top[0]
else:
#当前节点在深度表中且当前深度小于深度表中的值,更新深度表,建立父子关系
if (depth < dict_depth[next_state]):
dict_depth[next_state] = depth
dict_link[next_state] = top[0]
#如果当前节点在closed表中,将其移出closed表,将更新后的节点移入open表
if (next_state in closed):
del closed[next_state]
open.append(next_state, depth +
self.calcDistH(next_state, targ))
#循环结束,路径关系全部在dict_link中,从目标状态出发寻找路径
s = targ
while (s != init):
move = s.index('0')-dict_link[s].index('0')
if (move == -1):
path.append('l')
elif (move == 1):
path.append('r')
elif (move == -3):
path.append('u')
else:
path.append('d')
s = dict_link[s]
path.reverse()
#将path逆序(如果想要打印出路径每一步的状态,只需要按照path和init就能实现)
print("SearchPath->","".join(path))
return "".join(path)
三、完整代码(注释很详尽)
class Astar:
def salvePuzzle(self, init, targ):
#传入初始状态init,目标状态targ,返回移动路径(string形式)
open = [(init, self.calcDistH(init, targ))]
#open表元素(状态,f值)
closed =
dict_depth = init: 0
#深度表,用来记录节点是否被访问过及对应的深度
dict_link =
#关系表,用来记录父子关系,孩子-->父亲
dict_dirs = 'u': [-1, 0], 'd': [1, 0], 'l': [0, -1], 'r': [0, 1]
#移动方向,对应二维坐标的变化
dirs = ['l', 'r', 'u', 'd']
path = []
#用来记录移动路径
while (len(open)):
open.sort(key=lambda open: open[1])
#每循环一次对列表按由小到大排序一次
while (open[0][0] in closed):
open.pop([0])
#如果表头元素在closed表中,移出open表
if (open[0][0] == targ):
print("Successfully!")
break
top = open[0]
open[0:1] = []
closed[top[0]] = top[1]
#取表头元素,并将表头元素移出open表,压入closed表
cur_index = top[0].index('0')
for i in range(4):
x = cur_index // 3 + dict_dirs[dirs[i]][0]
y = cur_index % 3 + dict_dirs[dirs[i]][1]
if (x >= 0 and x < 3 and y >= 0 and y < 3):
next_index = x*3+y
next_state = self.moveMap(top[0], cur_index, next_index)
depth = dict_depth[top[0]]+1
#当前节点不在深度表中,压入深度表和open表,并建立父子关系
if ((next_state in dict_depth) == False):
dict_depth[next_state] = depth
open.append(
(next_state, depth+self.calcDistH(next_state, targ)))
dict_link[next_state] = top[0]
else:
#当前节点在深度表中且当前深度小于深度表中的值,更新深度表,建立父子关系
if (depth < dict_depth[next_state]):
dict_depth[next_state] = depth
dict_link[next_state] = top[0]
#如果当前节点在closed表中,将其移出closed表,将更新后的节点移入open表
if (next_state in closed):
del closed[next_state]
open.append(next_state, depth +
self.calcDistH(next_state, targ))
#循环结束,路径关系全部在dict_link中,从目标状态出发寻找路径
s = targ
while (s != init):
move = s.index('0')-dict_link[s].index('0')
if (move == -1):
path.append('l')
elif (move == 1):
path.append('r')
elif (move == -3):
path.append('u')
else:
path.append('d')
s = dict_link[s]
path.reverse()
#将path逆序(如果想要打印出路径每一步的状态,只需要按照path和init就能实现)
print("SearchPath->","".join(path))
return "".join(path)
def calcDistH(self, src_map, dest_map):
#采用曼哈顿距离作为估值函数
cost = 0
for i in range(len(src_map)):
if (src_map[i] != '0'):
cost += abs(int(src_map[i])//3-i//3) + \\
abs(int(src_map[i]) % 3-i % 3)
return cost
def moveMap(self, cur_map, i, j):
cur_state = [cur_map[i] for i in range(9)]
cur_state[i] = cur_state[j]
cur_state[j] = '0'
return "".join(cur_state)
#本程序实现了Astar类,可通过创建Astar对象来调用相关方法
#以下仅为测试用例
test=Astar()
test.salvePuzzle("724506831","012345678")
总结
以上就是今天要讲的内容,本文介绍了如何使用python实现A*算法,解决八数码难题,希望能够对uu们有所帮助,如果有uu们有不懂的问题或者有更好的创意和建议,可以在评论区留下宝贵意见,随时欢迎私信博主,给博主点个关注,留个赞,博主不胜感激
python实现求解八数码问题
哎,好久没写博文了,其实仔细想来,时间还是蛮多的,以后还是多写写吧!
之前看过经典的搜索路径方法,印象较深的也就BFS(广度优先),DFS(深度优先)以及A*搜索,但没实践过,就借八数码问题,来通通实现遍,观察下现象呗~~~
首先,怎么说也得把数码这玩意基本操作实现了呗!上代码~
class puzzled: def __init__(self,puzzled): self.puzzled=puzzled self.__getPuzzledInfo() def __getPuzzledInfo(self): self.puzzledWid=len(self.puzzled[0]) self.puzzledHei=len(self.puzzled) self.__f1=False for i in range(0,self.puzzledHei): for j in range(0,self.puzzledWid): if(self.puzzled[i][j]==0): self.zeroX=j self.zeroY=i self.__f1=True break if(self.__f1): break def printPuzzled(self): for i in range(0,len(self.puzzled)): print self.puzzled[i] print "" def isRight(self): if(self.puzzled[self.puzzledHei-1][self.puzzledWid-1]!=0): return False for i in range(0,self.puzzledHei): for j in range(0,self.puzzledWid): if(i*self.puzzledWid+j+1!=self.puzzled[i][j]): if(i!=self.puzzledHei-1 or j!=self.puzzledWid-1): return False return True def move(self,dere):#0 up,1 down,2 left,3 right if(dere==0 and self.zeroY!=0): self.puzzled[self.zeroY-1][self.zeroX],self.puzzled[self.zeroY][self.zeroX] = self.puzzled[self.zeroY][self.zeroX],self.puzzled[self.zeroY-1][self.zeroX] self.zeroY-=1 return True elif(dere==1 and self.zeroY!=self.puzzledHei-1): self.puzzled[self.zeroY+1][self.zeroX],self.puzzled[self.zeroY][self.zeroX] = self.puzzled[self.zeroY][self.zeroX],self.puzzled[self.zeroY+1][self.zeroX] self.zeroY+=1 return True elif(dere==2 and self.zeroX!=0): self.puzzled[self.zeroY][self.zeroX-1],self.puzzled[self.zeroY][self.zeroX] = self.puzzled[self.zeroY][self.zeroX],self.puzzled[self.zeroY][self.zeroX-1] self.zeroX-=1 return True elif(dere==3 and self.zeroX!=self.puzzledWid-1): self.puzzled[self.zeroY][self.zeroX+1],self.puzzled[self.zeroY][self.zeroX] = self.puzzled[self.zeroY][self.zeroX],self.puzzled[self.zeroY][self.zeroX+1] self.zeroX+=1 return True return False def getAbleMove(self): a=[] if(self.zeroY!=0): a.append(0) if(self.zeroY!=self.puzzledHei-1): a.append(1) if(self.zeroX!=0): a.append(2) if(self.zeroX!=self.puzzledWid-1): a.append(3) return a def clone(self): a=copy.deepcopy(self.puzzled) return puzzled(a) def toString(self): a="" for i in range(0,self.puzzledHei): for j in range(0,self.puzzledWid): a+=str(self.puzzled[i][j]) return a def isEqual(self,p): if(self.puzzled==p.puzzled): return True return False def toOneDimen(self): a=[] for i in range(0,self.puzzledHei): for j in range(0,self.puzzledWid): a.append(self.puzzled[i][j]) return a def getNotInPosNum(self): t=0 for i in range(0,self.puzzledHei): for j in range(0,self.puzzledWid): if(self.puzzled[i][j]!=i*self.puzzledWid+j+1): if(i==self.puzzledHei-1 and j==self.puzzledWid-1 and self.puzzled[i][j]==0): continue t+=1 return t def getNotInPosDis(self): t=0 it=0 jt=0 for i in range(0,self.puzzledHei): for j in range(0,self.puzzledWid): if(self.puzzled[i][j]!=0): it=(self.puzzled[i][j]-1)/self.puzzledWid jt=(self.puzzled[i][j]-1)%self.puzzledWid else: it=self.puzzledHei-1 jt=self.puzzledWid-1 t+=abs(it-i)+abs(jt-j) return t @staticmethod def generateRandomPuzzle(m,n,ran): tt=[] for i in range(0,m): t=[] for j in range(0,n): t.append(j+1+i*n) tt.append(t) tt[m-1][n-1]=0 a=puzzled(tt) i=0 while(i<ran): i+=1 a.move(random.randint(0,4)) return a
稍微注解一下,puzzled类表示一个数码类,初始化利用
a=puzzled( [1,2,3], [4,5,6], [7,8,0])
其中呢,0表示空格位置,上面初始化的便是一个正确的,未被打乱的位置~
其他的成员函数,看名称就很好理解了呗~
ok,基础打好了,接下来就该上节点类了:
class node: def __init__(self,p): self.puzzled=p self.childList=[] self.father=None def addChild(self,child): self.childList.append(child) child.setFather(self) def getChildList(self): return self.childList def setFather(self,fa): self.father=fa def displayToRootNode(self): t=self tt=0 while(True): tt+=1 t.puzzled.printPuzzled() t=t.father if(t==None): break print "it need "+str(tt)+ " steps!" def getFn(self): fn=self.getGn()+self.getHn() #A* #fn=self.getHn() #贪婪 return fn def getHn(self): Hn=self.puzzled.getNotInPosDis() return Hn def getGn(self): gn=0 t=self.father while(t!=None): gn+=1 t=t.father return gn
对于节点类吧,也还是很好理解的,初始化方法
a=node( puzzled([1,2,3], [4,5,6], [7,8,0]) )
基础都搭好了,重点任务该闪亮登场了呗~
class seartchTree: def __init__(self,root): self.root=root def __search2(self,hlist,m): #二分查找,经典算法,从大到小,返回位置 #若未查找到,则返回应该插入的位置 low = 0 high = len(hlist) - 1 mid=-1 while(low <= high): mid = (low + high)/2 midval = hlist[mid] if midval > m: low = mid + 1 elif midval < m: high = mid - 1 else: return (True,mid) return (False,mid) def __sortInsert(self,hlist,m):#对于一个从大到小的序列, #插入一个数,仍保持从大到小 t=self.__search2(hlist,m) if(t[1]==-1): hlist.append(m) return 0 if(m<hlist[t[1]]): hlist.insert(t[1]+1, m) return t[1]+1 else: hlist.insert(t[1], m) return t[1] def breadthFirstSearch(self):#广度优先搜索 numTree=NumTree.NumTree() numTree.insert(self.root.puzzled.toOneDimen()) t=[self.root] flag=True generation=0 while(flag): print "it‘s the "+str(generation)+" genneration now,the total num of items is "+str(len(t)) tb=[] for i in t: if(i.puzzled.isRight()==True): i.displayToRootNode() flag=False break else: for j in i.puzzled.getAbleMove(): tt=i.puzzled.clone() tt.move(j) a=node(tt) if(numTree.searchAndInsert(a.puzzled.toOneDimen())==False): i.addChild(a) tb.append(a) t=tb generation+=1 def depthFirstSearch(self):#深度优先搜索 numTree=NumTree.NumTree() numTree.insert(self.root.puzzled.toOneDimen()) t=self.root flag=True gen=0 while(flag): bran=0 print "genneration: "+str(gen) if(t.puzzled.isRight()==True): t.displayToRootNode() flag=False break else: f1=True for j in t.puzzled.getAbleMove(): tt=t.puzzled.clone() tt.move(j) a=node(tt) if(numTree.searchAndInsert(a.puzzled.toOneDimen())==False): t.addChild(a) t=a f1=False gen+=1 break if(f1==True): t=t.father gen-=1 def AStarSearch(self):#A* numTree=NumTree.NumTree() numTree.insert(self.root.puzzled.toOneDimen()) leaves=[self.root] leavesFn=[0] while True: t=leaves.pop() #open表 print leavesFn.pop() if(t.puzzled.isRight()==True): t.displayToRootNode() break for i in t.puzzled.getAbleMove(): tt=t.puzzled.clone() tt.move(i) a=node(tt) if(numTree.searchAndInsert(a.puzzled.toOneDimen())==False):#close表 t.addChild(a) fnS=self.__sortInsert(leavesFn,a.getFn()) leaves.insert(fnS, a)
本文出自 “文剑小调的随笔记” 博客,请务必保留此出处http://wenjianboy.blog.51cto.com/7389753/1758071
以上是关于A*算法求解八数码难题(python实现)的主要内容,如果未能解决你的问题,请参考以下文章