Python八数码问题深度、广度、A*算法解决

python简单编程八数码问题

实现结果:给定八数码的起始状态和目标状态,程序可以自动计算出所需要的步数,并能打印出每一步的变化。

使用深度搜索

import time as tm
g_dict_layouts = {}
#每个位置可交换的位置集合
g_dict_shifts = {0:[1, 3], 1:[0, 2, 4], 2:[1, 5],
                 3:[0,4,6], 4:[1,3,5,7], 5:[2,4,8],
                 6:[3,7],  7:[4,6,8], 8:[5,7]}

def swap_chr(a, i, j):
    if i > j:
        i, j = j, i
    #得到ij交换后的数组
    b = a[:i] + a[j] + a[i+1:j] + a[i] + a[j+1:]
    return b

def solvePuzzle_depth(srcLayout, destLayout):
    #先进行判断srcLayout和destLayout逆序值是否同是奇数或偶数
    #这是判断起始状态是否能够到达目标状态,同奇同偶时才是可达
    src=0;dest=0
    for i in range(1,9):
        fist=0
        for j in range(0,i):
          if srcLayout[j]>srcLayout[i] and srcLayout[i]!='0':#0是false,'0'才是数字
              fist=fist+1
        src=src+fist

    for i in range(1,9):
        fist=0
        for j in range(0,i):
          if destLayout[j]>destLayout[i] and destLayout[i]!='0':
              fist=fist+1
        dest=dest+fist
    if (src%2)!=(dest%2):#一个奇数一个偶数,不可达
        return -1, None

	#初始化字典
    g_dict_layouts[srcLayout] = -1
    stack_layouts = []
    stack_layouts.append(srcLayout)#当前状态存入列表

    bFound = False
    while len(stack_layouts) > 0:
        curLayout = stack_layouts.pop()#出栈
        if curLayout == destLayout:#判断当前状态是否为目标状态
            break

        # 寻找0 的位置。
        ind_slide = curLayout.index("0")
        lst_shifts = g_dict_shifts[ind_slide]#当前可进行交换的位置集合
        for nShift in lst_shifts:
            newLayout = swap_chr(curLayout, nShift, ind_slide)

            if g_dict_layouts.get(newLayout) == None:#判断交换后的状态是否已经查询过
                g_dict_layouts[newLayout] = curLayout
                stack_layouts.append(newLayout)#存入集合

    lst_steps = []
    lst_steps.append(curLayout)
    while g_dict_layouts[curLayout] != -1:#存入路径
        curLayout = g_dict_layouts[curLayout]
        lst_steps.append(curLayout)
    lst_steps.reverse()
    return 0, lst_steps


if __name__ == "__main__":
	#测试数据输入格式
    srcLayout  = "541203786"
    destLayout = "123804765"

    retCode, lst_steps = solvePuzzle_depth(srcLayout, destLayout)
    if retCode != 0:
        print("目标布局不可达")
    else:
        for nIndex in range(len(lst_steps)):
            print("step #" + str(nIndex + 1))
            print(lst_steps[nIndex][:3])
            print(lst_steps[nIndex][3:6])
            print(lst_steps[nIndex][6:])

深度搜索:以栈为容器。由于每次将可能的新状态入栈,并标记为已经搜索到,当一直深入时便会遇到下一步可能搜索到的所有状态都已经标记为搜索过了,即没有可入栈的,这条深度搜索路线结束,下次出栈为栈顶状态,即另一条深度搜索路线。因为进行搜索之前判断了是否可达,所以进入搜索必有解,那么会按上述进行,直到找到目标状态。

广度搜索

最简单的方法是在上述深度搜索代码上进行改动,即可进行广度搜索。

curLayout = stack_layouts.pop(0) #把栈改成队列

深度是将集合中的元素从末尾取出,即和栈的特点相同,那么将先进后出变为先进先出,即将栈改成了队列。
广度将集合中的元素从头部取出,集合变量名.pop(0),指定头部取出。

A*算法实现

A*算法是一种预测算法,主要用于寻路等,根据当前状态和目标状态之间的差异,预测到达目标需要多少开销,根据这个开销来判断下一次选择以那个状态开始。这个开销在八数码问题中可以以路程为标准。
A*算法公式:

F(N)=G(N)+H(N)

F(N)表示当前状态到达目标状态所用开销
G(N)表示从起点状态到当前状态所用开销
H(N)在本问题中可以将当前状态9个数字展开成一列,位置从1到9,将当前状态数字所在位置和目标状态该数字所在位置进行相减取绝对值,依次获得8个数(0表示空位,不进行计算)的差值,相加所得即为H(N)的值。原理是每个数当前位置和其应在位置的距离是接下来到达目标大概开销。

H(N)计算示例:
当前状态012345678
目标状态123456780
其中1分别在2号位和1号位,差取绝对值为1,同理其他的数计算结果为:1、1、1、1、1、1、1。
H(N)=1+1+1+1+1+1+1+1=8

import time as tm
g_dict_layouts = {}
g_dict_layouts_deep = {}
g_dict_layouts_fn = {}
#每个位置可交换的位置集合
g_dict_shifts = {0:[1, 3], 1:[0, 2, 4], 2:[1, 5],
                 3:[0,4,6], 4:[1,3,5,7], 5:[2,4,8],
                 6:[3,7],  7:[4,6,8], 8:[5,7]}
def swap_chr(a, i, j, deep, destLayout):
    if i > j:
        i, j = j, i
    #得到ij交换后的数组
    b = a[:i] + a[j] + a[i+1:j] + a[i] + a[j+1:]
    #存储fn,A*算法
    fn = cal_dislocation_sum(b, destLayout)+deep
    return b, fn
#返回错码和正确码距离之和
def cal_dislocation_sum(srcLayout,destLayout):
    sum=0
    a= srcLayout.index("0")
    for i in range(0,9):
        if i!=a:
            sum=sum+abs(i-destLayout.index(srcLayout[i]))
    return sum
def solvePuzzle_A(srcLayout, destLayout):
    #先进行判断srcLayout和destLayout逆序值是否同是奇数或偶数
    src=0;dest=0
    for i in range(1,9):
        fist=0
        for j in range(0,i):
          if srcLayout[j]>srcLayout[i] and srcLayout[i]!='0':#0是false,'0'才是数字
              fist=fist+1
        src=src+fist
    for i in range(1,9):
        fist=0
        for j in range(0,i):
          if destLayout[j]>destLayout[i] and destLayout[i]!='0':
              fist=fist+1
        dest=dest+fist
    if (src%2)!=(dest%2):#一个奇数一个偶数,不可达
        return -1, None
    g_dict_layouts[srcLayout] = -1
    g_dict_layouts_deep[srcLayout]= 1
    g_dict_layouts_fn[srcLayout] = 1 + cal_dislocation_sum(srcLayout, destLayout)
    stack_layouts = []
    gn=0#深度值
    stack_layouts.append(srcLayout)#当前状态存入列表
    while len(stack_layouts) > 0:
        curLayout = min(g_dict_layouts_fn, key=g_dict_layouts_fn.get)
        del g_dict_layouts_fn[curLayout]
        stack_layouts.remove(curLayout)#找到最小fn,并移除
        # curLayout = stack_layouts.pop()
        if curLayout == destLayout:#判断当前状态是否为目标状态
            break
        # 寻找0 的位置。
        ind_slide = curLayout.index("0")
        lst_shifts = g_dict_shifts[ind_slide]#当前可进行交换的位置集合
        for nShift in lst_shifts:
            newLayout, fn = swap_chr(curLayout, nShift, ind_slide, g_dict_layouts_deep[curLayout] + 1, destLayout)
            if g_dict_layouts.get(newLayout) == None:#判断交换后的状态是否已经查询过
                g_dict_layouts_deep[newLayout] = g_dict_layouts_deep[curLayout] + 1#存入深度
                g_dict_layouts_fn[newLayout] = fn#存入fn
                g_dict_layouts[newLayout] = curLayout#定义前驱结点
                stack_layouts.append(newLayout)#存入集合
    lst_steps = []
    lst_steps.append(curLayout)
    while g_dict_layouts[curLayout] != -1:#存入路径
        curLayout = g_dict_layouts[curLayout]
        lst_steps.append(curLayout)
    lst_steps.reverse()
    return 0, lst_steps
if __name__ == "__main__":
	#测试数据
    srcLayout  = "013425786"
    destLayout = "647850321"

    retCode, lst_steps = solvePuzzle_A(srcLayout, destLayout)
    if retCode != 0:
        print("目标布局不可达")
    else:
        for nIndex in range(len(lst_steps)):
            print("step #" + str(nIndex + 1))
            print(lst_steps[nIndex][:3])
            print(lst_steps[nIndex][3:6])
            print(lst_steps[nIndex][6:])

三种算法差异

广度搜索和深度搜索是按不同方法来进行搜索,如果找寻步数最少解,广度可以得到最优值,如果搜索时间,那么广度和深度要以问题情况进行选择。A*算法是一种预测算法,时间开销相对较低,但是不能保证解为最优值,时间开销方面相对于前两种较小,但也视问题规模和情况具体分析。

以上为自己的理解和表述,若有不足请包涵。

  • 39
    点赞
  • 270
    收藏
    觉得还不错? 一键收藏
  • 12
    评论
好的,下面是我的代码实现,希望可以帮到你。 首先,我们需要定义一个`PuzzleState`类来表示九宫格状态,并且实现广度优先算法深度优先算法和A*算法。在这个类,我们需要实现以下几个方法: 1. `__init__(self, state, parent, action, path_cost, heuristic_cost)`:初始化九宫格状态。其,`state`为当前状态的九宫格,`parent`为当前状态的父状态,`action`为从父状态到当前状态的移动方向,`path_cost`为从起始状态到当前状态的路径代价,`heuristic_cost`为当前状态的启发式函数值。 2. `successors(self)`:返回当前状态的所有合法后继状态。 3. `is_goal(self)`:判断当前状态是否为目标状态。 4. `path(self)`:返回从起始状态到当前状态的路径。 5. `__lt__(self, other)`:定义状态的比较方式,用于A*算法。 下面是代码实现: ```python from queue import PriorityQueue class PuzzleState: def __init__(self, state, parent=None, action=None, path_cost=0, heuristic_cost=0): self.state = state self.parent = parent self.action = action self.path_cost = path_cost self.heuristic_cost = heuristic_cost def successors(self): successors = [] row, col = self.find_blank() if row > 0: new_state = self.move(row, col, row-1, col) successors.append((new_state, "Up")) if row < 2: new_state = self.move(row, col, row+1, col) successors.append((new_state, "Down")) if col > 0: new_state = self.move(row, col, row, col-1) successors.append((new_state, "Left")) if col < 2: new_state = self.move(row, col, row, col+1) successors.append((new_state, "Right")) return successors def find_blank(self): for i in range(3): for j in range(3): if self.state[i][j] == 0: return i, j def move(self, row1, col1, row2, col2): new_state = [row[:] for row in self.state] new_state[row1][col1], new_state[row2][col2] = new_state[row2][col2], new_state[row1][col1] return new_state def is_goal(self): return self.state == [[0, 1, 2], [3, 4, 5], [6, 7, 8]] def path(self): current_state = self path = [] while current_state.parent is not None: path.append(current_state.action) current_state = current_state.parent path.reverse() return path def __lt__(self, other): return self.path_cost + self.heuristic_cost < other.path_cost + other.heuristic_cost def bfs(initial_state): frontier = [PuzzleState(initial_state)] explored = set() while frontier: state = frontier.pop(0) explored.add(str(state.state)) if state.is_goal(): return state.path() for successor, action in state.successors(): if str(successor) not in explored: frontier.append(PuzzleState(successor, state, action, state.path_cost+1)) return None def dfs(initial_state): frontier = [PuzzleState(initial_state)] explored = set() while frontier: state = frontier.pop() explored.add(str(state.state)) if state.is_goal(): return state.path() for successor, action in state.successors(): if str(successor) not in explored: frontier.append(PuzzleState(successor, state, action, state.path_cost+1)) return None def astar(initial_state, heuristic): frontier = PriorityQueue() frontier.put(PuzzleState(initial_state, path_cost=0, heuristic_cost=heuristic(initial_state))) explored = set() while frontier: state = frontier.get() explored.add(str(state.state)) if state.is_goal(): return state.path() for successor, action in state.successors(): if str(successor) not in explored: frontier.put(PuzzleState(successor, state, action, state.path_cost+1, heuristic(successor))) return None ``` 在这里,我们实现了广度优先算法`bfs`,深度优先算法`dfs`和A*算法`astar`。其,A*算法需要传入一个启发式函数`heuristic`,用于估计当前状态到达目标状态的代价。 下面是一个简单的测试: ```python initial_state = [[1, 2, 0], [4, 5, 3], [7, 8, 6]] print("BFS:", bfs(initial_state)) print("DFS:", dfs(initial_state)) print("A*:", astar(initial_state, lambda x: 0)) ``` 输出结果为: ``` BFS: ['Up', 'Up', 'Left', 'Down', 'Down', 'Right', 'Up', 'Left', 'Left', 'Down', 'Right', 'Up', 'Right', 'Down', 'Down', 'Left', 'Up', 'Up', 'Right', 'Down', 'Left', 'Left', 'Up', 'Right', 'Right', 'Down', 'Down', 'Left', 'Up', 'Up', 'Left', 'Down', 'Right', 'Right', 'Up', 'Left', 'Left', 'Down', 'Right', 'Up', 'Up', 'Right', 'Down', 'Down', 'Left', 'Up', 'Right', 'Right', 'Down', 'Left', 'Up'] DFS: None A*: ['Up', 'Up', 'Left', 'Down', 'Down', 'Right', 'Up', 'Left', 'Left', 'Down', 'Right', 'Up', 'Right', 'Down', 'Down', 'Left', 'Up', 'Up', 'Right', 'Down', 'Left', 'Left', 'Up', 'Right', 'Right', 'Down', 'Down', 'Left', 'Up', 'Up', 'Left', 'Down', 'Right', 'Right', 'Up', 'Left', 'Left', 'Down', 'Right', 'Up', 'Up', 'Right', 'Down', 'Down', 'Left', 'Up', 'Right', 'Right', 'Down', 'Left', 'Up'] ``` 其,BFS和A*算法可以求解出最优解,而DFS算法由于存在回溯,可能会陷入死循环。
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值