python实现用A*算法求解8数码问题

我的算法流程

  • 初始化open_list 和 close_list;
  • 将开始节点添加到open_list
    • 找到open_list最优的字典和索引
    • 如果最好的节点等于当前节点
      • 从终点开始逐步追踪父亲节点,一直找到开始节点
      • 返回找到的结果路径,算法结束
    • 如果最好的节点不等于当前节点
      • 将最小节点从open_list中删除
      • 将最小节点从添加到close_list中
      • 遍历最小节点所有的邻近节点
        • 如果该邻近节点在close_list中可以找到
          • 则continue
        • 如果该邻近节点在open_list中可以找到
          • 则continue
        • 将邻近节点加入open_list中

代码

class Solution:

    def salvePuzzle(self, init, targ):
        ''' 求解8数码问题
        参数:
        init - 初始状态 例如'123046758'
        targ - 目标状态 例如'012345678'
        返回值:
        clf - 由udlr组成的移动路径字符串
        ''' 
        def open_min(opl): # 找到open列表中最小的一个
            k = opl[0]
            d = -1
            t = 0
            f_min = opl[0]['g'] + opl[0]['h']
            for i in opl:
                d = d + 1
                if i['g'] + i['h'] < f_min:
                    f_min = i['g'] + i['h']
                    k = i
                    t = d
            return k, t
         
            
        def find_fat(cll, f): # 寻找父亲节点
            for i in cll:
                if i['ind'] == f:
                    return i
                
                
        def find_near(oc, node): # 寻找邻近节点
            d = -1
            for i in oc:
                d = d + 1
                if i['val'] == node:
                    return d
            return -1        
            
            
        start_node = init # 开始节点
        
        end_node = targ # 结束节点
        
        open_list = [] # open表
        
        close_list = [] # close表
        
        hx = self.calcDistH(start_node, end_node) # h(x)
        
        # ind: 序号,当前点的序号 | g表示g(x)代数 | h表示h(x) | val表示值 | fat表示father指针 | pos表示(上u 下d 左l 右r)
        set1 = {'ind': 0, 'g': 0, 'h': hx, 'val': start_node, 'fat': -1, 'pos': ''}
        
        n = 0 # ind索引
        
        path_list = []  # 打印路径
        
        open_list.append(set1)  # 将开始节点添加到open_list
        
        best_set = {} # 最好的字典
        
        best_node = '' # 最好的节点
        
        index = 0 # 索引
        while n < 100000:
            
            best_set, index = open_min(open_list) # 找到open_list最优的字典和索引
            
            best_node = best_set['val']  # 找到改最小字典的节点值
            
            if best_node == end_node: # 如果最好的节点等于当前节点
                while True: # 从终点开始逐步追踪父亲节点,一直找到开始节点
                    node_fat = best_set['fat'] # 找到父亲索引
                    
                    path_list.append(best_set['pos']) # 添加到路径列表中
                    
                    if node_fat == -1: # 一直找到开始节点
                        break
                        
                    best_set = find_fat(close_list, node_fat)
            
            if path_list != []: # 返回找到的结果路径,算法结束
                break
            
            del open_list[index] # 将最小节点从open_list中删除
            
            close_list.append(best_set) # 将最小节点从添加到close_list中
            
            ###  产生所有邻近节点
            fx = ['u', 'd', 'l', 'r']
            
            for fxw in fx: # 遍历最小节点所有的邻近节点
                index_0 = best_node.find('0')   #找到0处的索引值
                
                index_row =  int(index_0 / 3)   # 行
                indx_col = index_0 % 3          # 列
                
                if fxw == 'u': # 上移
                    index_row = index_row - 1
                if fxw == 'd': # 下移
                    index_row = index_row + 1
                if fxw == 'l': # 左移
                    indx_col = indx_col - 1
                if fxw == 'r': # 右移
                    indx_col = indx_col + 1
                    
               # print('移动到的索引:',jh_index_0, '移动方向:',fxw, '行:', int(index_row), '列:', indx_col )
                
                if int(index_row) > 2 or int(index_row) < 0 or indx_col > 2 or indx_col < 0: # 判断是否越界,即是否超出九宫格
                    continue
                    
                jh_index_0 = 3 * int(index_row) + indx_col # 移动之后的0的索引值
                
                near_node = self.moveMap(best_node, int(index_0), int(jh_index_0)) #移动之后的邻近节点
                
                # g(n)是从开始结点到结点n的路径代价
                set2 = {'ind': 0, 'g': best_set['g'] + 1, 'h': 0, 'val': '', 'fat': best_set['ind'], 'pos': fxw}  
                
                '''
                # 下面注释的代码本应当要实现以下功能:
                    + 如果该邻近节点在close_list中可以找到
                        + 因为节点相同,所以h(x)一样,则判断g(x)的大小进行操作 
                        + 这里的操作就是修改该节点的属性,g(x)和指向父亲的['fat'],再continue
                    + 如果该邻近节点在open_list中可以找到
                        + 因为节点相同,所以h(x)一样,则判断g(x)的大小进行操作 
                        + 这里的操作就是修改该节点的属性,g(x)和指向父亲的['fat'],再continue
                # 但是本人实力有限,实现不了。
                ''' 

                near_index = find_near(close_list, near_node) # 在close_list找near_node的索引值
                
                if near_index != -1:  #如果找到
                    '''
                    if set2['g'] < close_list[near_index]['g']:
                        close_list[near_index]['g'] = set2['g']
                        close_list[near_index]['fat'] = best_set['ind']
                    '''
                    continue
                    
                
                
                near_index = find_near(open_list, near_node) #在open_list找near_node的索引值
                
                if near_index != -1:  #如果找到
                    '''
                    if set2['g'] < open_list[near_index]['g']:
                        open_list[near_index]['g'] = set2['g']
                        open_list[near_index]['fat'] = best_set['ind']
                    ''' 
                    continue

                near_h =  self.calcDistH(near_node, end_node) # 计算h(x)
                n = n + 1
                set2['ind'] = n
                set2['h'] = near_h
                set2['val'] = near_node
                open_list.append(set2) # 将节点m加入open_list中
            
        path_list.reverse()   # 逆序
        return ''.join(path_list)     
    

    
    def calcDistH(self, src_map, dest_map):
        '''     启发式函数h(n):是从结点n到目标结点的最小路径代价的估计值
        参数:
            src_map  - 当前8数码状态
            dest_map - 目标8数码状态
        返回值:
            clf - 当前状态到目标状态的启发式函数值
        '''
        sum = 0
        src_i = 0
        for i in src_map:  # 检索i
            src_i = src_i + 1
            dest_j = 0 
            for j in dest_map:
                if i == '0':
                    break
                dest_j = dest_j + 1
                if i == j:
                    sum = sum + abs(src_i - dest_j)
        return sum


    def moveMap(self, cur_map, i, j):
        '''     状态转换(交换位置i和j)
        参数:
            cur_map - 当前8数码状态
            i - 当前8数码状态中空格0的位置索引
            j - 将空格0的位置i移动到位置j,位置j移动到位置i
        返回值:
            clf - 新的8数码状态
        '''
        i = int(i)
        j = int(j)
        cur_map = cur_map[:i] + cur_map[j] + cur_map[i + 1:]
        cur_map = cur_map[:j] + '0' + cur_map[j + 1:]
        return cur_map


solution = Solution()
# init - 初始状态
# targ - 目标状态
# path_list - 移动路径 例如 'lurddlurrulldrrdllurruldlu'
init = "724506831"
targ = "012345678"
path_list = solution.salvePuzzle(init, targ)
print(path_list)
  • 9
    点赞
  • 55
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
A*算法求解数码问题 1、A*算法基本思想: 1)建立一个队列,计算初始结点的估价函数f,并将初始结点入队,设置队列头和尾指针。 2)取出队列头(队列头指针所指)的结点,如果该结点是目标结点,则输出路径,程序结束。否则对结点进行扩展。 3)检查扩展出的新结点是否与队列中的结点重复,若与不能再扩展的结点重复(位于队列头指针之前),则将它抛弃;若新结点与待扩展的结点重复(位于队列头指针之后),则比较两个结点的估价函数中g的大小,保留较小g值的结点。跳至第五步。 4)如果扩展出的新结点与队列中的结点不重复,则按照它的估价函数f大小将它插入队列中的头结点后待扩展结点的适当位置,使它们按从小到大的顺序排列,最后更新队列尾指针。 5)如果队列头的结点还可以扩展,直接返回第二步。否则将队列头指针指向下一结点,再返回第二步。 2、程序运行基本环境: 源程序所使用编程语言:C# 编译环境:VS2010,.net framework 4.0 运行环境:.net framework 4.0 3、程序运行界面 可使用程序中的test来随机生成源状态与目标状态 此停顿过程中按Enter即可使程序开始运行W(n)部分; 此停顿部分按Enter后程序退出; 4、无解问题运行情况 这里源程序中是先计算源状态与目标状态的逆序对的奇偶性是否一致来判断是否有解的。下面是无解时的运行画面: 输入无解的一组源状态到目标状态,例如: 1 2 3 4 5 6 7 8 0 1 2 3 4 5 6 8 7 0 运行画面如下: 5、性能比较 对于任一给定可解初始状态,状态空间有9!/2=181440个状态;当采用不在位棋子数作为启发函数时,深度超过20时,算法求解速度较慢; 其中启发函数P(n)与W(n)的含义如下: P(n): 任意节点与目标结点之间的距离; W(n): 不在位的将牌数; 源状态 目标状态 P(n) 生成节点数 W(n) 生成节点数 P(n) 扩展节点数 W(n) 扩展节点数 2 8 3 1 6 4 7 0 5 1 2 3 8 0 4 7 6 5 11 13 5 6 1 2 3 8 0 4 7 6 5 0 1 3 8 2 4 7 6 5 6 6 2 2 4 8 2 5 1 6 7 0 3 7 4 2 8 5 6 1 3 0 41 79 22 46 6 2 5 8 7 0 3 1 4 0 3 6 7 1 8 4 5 2 359 10530 220 6769 7 6 3 1 0 4 8 5 2 2 8 7 1 3 4 6 5 0 486 8138 312 5295 下图是解决随机生成的100中状态中,P(n)生成函数的生成节点与扩展节点统计图: 由上图可知,P(n)作为启发函数,平均生成节点数大约在1000左右,平均扩展节点数大约在600左右; 下图是解决随机生成的100中状态中,W(n)生成函数的生成节点与扩展节点统计图: 由上图可知,W (n)作为启发函数,平均生成节点数大约在15000左右,是P(n)作为启发函数时的平均生成节点的15倍;W (n)作为启发函数,平均扩展节点数大约在10000左右,是P(n)作为启发函数时的平均扩展节点的15倍; 下图是解决随机生成的100中状态中,两个生成函数的生成节点与扩展节点统计图: 由上述图表可以看到,将P(n)作为启发函数比将W(n)作为启发函数时,生成节点数与扩展节点数更稳定,相比较来说,采用P(n)作为启发函数的性能比采用W(n)作为启发函数的性能好。 6、源代码说明 1)AStar-EightDigital-Statistics文件夹:用来随机生成100个状态,并对这100个状态分别用P(n)与W(n)分别作为启发函数算出生成节点以及扩展节点,以供生成图表使用;运行界面如下: 2)Test文件夹:将0-8这9个数字随机排序,用来随机生成源状态以及目标状态的;运行界面如下: 3)AStar-EightDigital文件夹:输入源状态和目标状态,程序搜索出P(n)与W(n)分别作为启发函数时的生成节点数以及扩展节点数,并给出从源状态到目标状态的移动步骤;运行界面如下: 提高了运行速度的几处编码思想: 1、 在维护open以及close列表的同时,也维护一个类型为hashtable的open以及close列表,主要用来提高判断当前节点是否在open列表以及close列表中出现时的性能; 2、 对于每个状态,按照从左到右,从上到下,依次将数字拼接起来,形成一个唯一标识identify,通过该标识,可以直接判断两个状态是否是同一个状态,而不需要循环判断每个位置上的数字是否相等 3、 在生成每个状态的唯一标识identify时,同时计算了该状态的空格所在位置,通过空格所在位置,可以直接判断能否进行上移、下移、左移、右移等动作; 4、 只计算初始节点的h值,其它生成的节点的h值是根据当前状态的h值、移动的操作等计算后得出的,规则如下: a) 采用W(n)这种方式,不在位置的将牌数,共有以下3中情况: i. 该数字原不在最终位置上,移动后,在其最终位置上 这种情况下,生成的子节点的h值= 父节点的h值-1 ii. 该数字原在最终位置上,移动后,不在其最终位置上 这种情况下,生成的子节点的h值= 父节点的h值 +1 iii. 该数字原不在最终位置上,移动后,还是不在其最终位置上 这种情况下,生成的子节点的h值= 父节点的h值 iv. 该数字原在最终位置上,移动后,还在其最终位置 这种情况不存在 b) 采用P(n)这种方式,节点与目标距离,可通过下面3步完成 i. 首先计算在原位置时,与目标位置的距离,命名为Distance1 ii. 移动后,计算当前位置与目标位置的距离,命名为Distance2 iii. 计算子节点的h值: 子节点的h值 = 父节点的h值- Distance1+ Distance2 5、 在任意状态中的每个数字和目标状态中同一数字的相对距离就有9*9种,可以先将这些相对距离算出来,用一个矩阵存储,这样只要知道两个状态中同一个数字的位置,就可查出它们的相对距离,也就是该数字的偏移距离;例如在一个状态中,数字8的位置是3,在另一状态中位置是7,那么从矩阵的3行7列可找到2,它就是8在两个状态中的偏移距离。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值