A*算法求解八数码难题(python实现)

Posted Life-lover

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了A*算法求解八数码难题(python实现)相关的知识,希望对你有一定的参考价值。

目录

文章目录

前言

一、八数码难题是什么?

二、算法详解

1.启发函数(曼哈顿距离)

2.状态移动处理

3. A*搜索并返回路径

 三、完整代码(注释很详尽)

总结



前言

        本文用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实现)的主要内容,如果未能解决你的问题,请参考以下文章

八数码问题的问题,有解条件以及求解算法(宽度优先搜索)

八数码难题

1225 八数码难题

1225 八数码难题

codevs1225八数码难题(搜索·)

什么是启发式搜索?并以八数码难题为例,说明其原理