基于Python的A*算法求解八数码问题

问题描述:
在一个3*3的九宫中有1-8这8个数字以及一个空格随机摆放在其中的格子里。将该九宫格的初始状态调整到目标状态。
规则:每次只能将与空格(上、下、左、右)相邻的一个数字移动到空格中。试编程实现这一问题的求解。为了程序中表示方便,用0代替空格。
在这里插入图片描述

如:

要点:
A算法的核心在于估价函数f(n) = g(n) + h(n) 。 g(n)为初始结点到当前结点n的代价;
h(n)称为启发函数,表示节点 n 到目标节点的估计代价,这里取与目标状态相比错位的数目。
A
算法在运算过程中,每次从队列中选取f(n)值最小(优先级最高)的节点作为下一个待遍历的节点。
A算法使用两个集合来表示待遍历的节点,与已经遍历过的节点,可以称之为opened和closed。
A
算法主体思路:

  • 初始化opened和closed,将起点加入opened中;
  • 如果opened不为空,则从opened中选取优先级最高的节点n:
    • 如果节点n为终点(目标状态),则:
      • 从终点开始逐步追踪parent节点,一直达到起点;
      • 返回找到的结果路径,算法结束;
    • 如果节点n不是终点,则:
      • 将节点n从opened中删除,并加入closed中;
      • 遍历节点n所有的邻近节点:
        • 如果邻近节点m不在opened中也不在closed中,则:
          • 设置节点m的parent为节点n
          • 计算节点m的优先级
          • 将节点m加入opened中
        • 如果邻近节点m在opened中,则:
          • 更新节点m的优先级
          • 设置节点m的parent为节点n

查找过程图示:
在这里插入图片描述

A*算法实现代码

def reversenum(node):
    """计算状态对应的逆序数,奇偶性一致则有解"""
    Sum = 0
    for i in range(1,9):
        num = 0
        for j in range(0,i):
          if node[j]>node[i] and node[i] != '0':
              num = num + 1
        Sum += num
    return Sum

def Hn(node):
    """h(n)函数,用于计算估价函数f(n),这里的h(n)选择的是与目标状态相比错位的数目"""
    global goal
    hn = 0
    for i in range(0,9):
        if node[i] != goal[i]:
            hn += 1
    return hn
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           
def expand_node(node):
    """拓展node状态对应的子结点"""
    global expand
    tnode = []
    state = node.index("0")  # 返回"0"的位置
    elist = expand[state]  # 得知0可以移动的情况
    j = state
    for i in elist:
        j = state
        if i>j:
            i,j  =  j,i
        re =  node[:i] + node[j] + node[i+1:j] + node[i] + node[j+1:]  # 用切片拼出子节点
        tnode.append(re)
#     print(tnode)
    return tnode

def print_step(result):
    """将最后的结果按格式输出"""
    for i in range(len(result)):
            print("step--" + str(i+ 1))
            print(result[i][:3])
            print(result[i][3:6])
            print(result[i][6:])

def select_min(opened):
    """选择opened表中的最小的估价函数值对应的状态"""
    fn_dict = {}  # 字典
    for node in opened:
        fn = Fn[node]  
        fn_dict[node] = fn
    min_node = min(fn_dict, key = fn_dict.get) # 获得字典fn_dict中value的最小值所对应的键
    return min_node

def a_star(start, goal):
    if start == goal:
        print("初始状态和目标状态一致!")
        
    # 判断从初始状态是否可以达到目标状态
    if (reversenum(start)%2) != (reversenum(goal)%2):
        print("该目标状态不可达!")
        return None
        
    else:
        parent[start] = -1                # 初始结点的父结点存储为-1
        Gn[start] = 0                     # 初始结点的g(n)为0
        Fn[start] = Gn[start] + Hn(start)  # 计算初始结点的估价函数值 f(n)  =  g(n) + h(n)

        while opened:
            current = select_min(opened)  # 选择估价函数值最小的状态
            del Fn[current]              # 对代价清零
            opened.remove(current)       # 将要遍历的结点取出opened表

            if current == goal:
                break
                
            if current not in closed:
                closed.append(current)     # 存入closed表 
                Tnode = expand_node(current)    # 扩展子结点.用来遍历节点n所有的邻近节点
                for node in Tnode:
                    # 如果子结点在opened和closed表中都未出现,则存入opened表
                    # 并求出对应的估价函数值
                    if node not in opened and node not in closed:
                        Gn[node] = Gn[current]+1
                        Fn[node] = Gn[node]+Hn(node)
                        parent[node] = current
                        opened.append(node)
                    else:
                        # 若子结点已经在opened表中,则判断估价函数值更小的一个路径
                        # 同时改变parent字典和Fn字典中的值
                        if node in opened:
                            fn = Gn[current] + 1 + Hn(node)
                            if fn < Fn[node]:
                                Fn[node] = fn
                                parent[node] = current

        result = []  # 用来存放路径
        result.append(current)
        
        while parent[current] != -1:  # 根据parent字典中存储的父结点提取路径中的结点
            current  = parent[current]
            result.append(current)
        result.reverse()  # 逆序即为运行时的过程
        return result

if __name__ == "__main__":  
    # expand中存储的是九宫格中每个位置对应的可以移动的情况
    # 当定位了0的位置就可以得知可以移动的情况
    expand = {0:[1, 3],
              1:[0, 2, 4],
              2:[1, 5],
              3:[0,4,6], 
              4:[3,1,5,7], 
              5:[4,2,8],
              6:[3,7],  
              7:[6,4,8], 
              8:[7,5]}

    start = input("请输入初始状态(从左至右,从上到下,如:153246708):")
    goal  = input("请输入目标状态(从左至右,从上到下,如:123456780):")
    
    # 初始化
    opened = [start]
    closed = []
    Fn = {}  # 状态对应的估价函数值 f(n)  =  g(n) + h(n)
    Gn = {}  # 初始结点到当前结点n的实际代价,即路径长度
    parent = {}  # 用来存储状态对应的父结点
    
    result = a_star(start, goal)
    if result != None:
        print_step(result)# 按格式输出结果


运行结果
在这里插入图片描述

呜呼,一个算法死磕了三个小时,太不容易了,人麻了~~~

  • 7
    点赞
  • 67
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
八数码问题是一种经典的搜索问题,要求把一个3x3的棋盘上的数字1-8排列成特定的顺序。本文将使用A*算法求解八数码问题。 A*算法是一种启发式搜索算法,它使用估价函数来评估每个节点的价值,并选择最有希望的节点进行扩展。估价函数是一个启发式函数,它可以预测从当前节点到目标节点的最小代价。在八数码问题中,我们可以使用曼哈顿距离作为估价函数。 以下是使用Python语言实现A*算法求解八数码问题的代码: ```python import heapq # 计算曼哈顿距离 def manhattan_distance(state): distance = 0 for i in range(3): for j in range(3): if state[i][j] != 0: row = (state[i][j] - 1) // 3 col = (state[i][j] - 1) % 3 distance += abs(row - i) + abs(col - j) return distance # 判断状态是否合法 def is_valid(state): nums = set() for row in state: for num in row: if num != 0: if num in nums: return False nums.add(num) return True # 获取空格位置 def get_blank(state): for i in range(3): for j in range(3): if state[i][j] == 0: return (i, j) # 获取下一步可能的状态 def get_next_states(state): blank = get_blank(state) row, col = blank next_states = [] for i, j in [(row-1, col), (row+1, col), (row, col-1), (row, col+1)]: if i >= 0 and i < 3 and j >= 0 and j < 3: next_state = [row[:] for row in state] next_state[row][col], next_state[i][j] = next_state[i][j], next_state[row][col] next_states.append(next_state) return next_states # A*算法求解八数码问题 def solve_puzzle(start_state, goal_state): if not is_valid(start_state) or not is_valid(goal_state): return None # 初始化起始状态 start_node = {'state': start_state, 'g': 0, 'h': manhattan_distance(start_state), 'parent': None} open_list = [start_node] closed_list = set() while len(open_list) > 0: # 选择最小估价函数值的节点进行扩展 current_node = heapq.heappop(open_list) # 判断是否到达目标状态 if current_node['state'] == goal_state: path = [] while current_node is not None: path.append(current_node['state']) current_node = current_node['parent'] path.reverse() return path # 将当前节点加入关闭列表 closed_list.add(str(current_node['state'])) # 扩展下一步可能的状态 for next_state in get_next_states(current_node['state']): if str(next_state) not in closed_list: # 计算估价函数值,并加入开放列表 next_node = {'state': next_state, 'g': current_node['g']+1, 'h': manhattan_distance(next_state), 'parent': current_node} heapq.heappush(open_list, next_node) # 无解 return None # 测试 start_state = [[2, 8, 3], [1, 6, 4], [7, 0, 5]] goal_state = [[1, 2, 3], [8, 0, 4], [7, 6, 5]] path = solve_puzzle(start_state, goal_state) if path is not None: for state in path: print(state) else: print("无解") ``` 以上代码中,`manhattan_distance`函数计算曼哈顿距离;`is_valid`函数判断状态是否合法;`get_blank`函数获取空格位置;`get_next_states`函数获取下一步可能的状态;`solve_puzzle`函数使用A*算法求解八数码问题。在测试中,我们设置初始状态为`[[2, 8, 3], [1, 6, 4], [7, 0, 5]]`,目标状态为`[[1, 2, 3], [8, 0, 4], [7, 6, 5]]`,并输出求解路径。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值