A*算法解决八数码难题

基于状态空间表示法的搜索算法解决八数码难题

本文的pdf文件链接:link

一、问题重述

1.1 背景介绍

       如今处于人工智能和大数据时代,每天都有成千上万的数据产生,而我们在获取数据的时候需要用到搜索引擎,而互联网中的数据就相当于一颗巨大的树的结点,我们搜索信息的过程实际上就是遍历树的过程,那么搜索算法的好坏将直接决定了我们获取信息的快慢。搜索算法是利用计算机的高性能来有目的的穷举一个问题解空间的部分或所有的可能情况,从而求出问题的解的一种方法。现阶段一般有枚举算法、深度优先搜索、广度优先搜索、A*算法、回溯算法、蒙特卡洛树搜索、散列函数等算法。在大规模实验环境中,通常通过在搜索前,根据条件降低搜索规模;根据问题的约束条件进行剪枝;利用搜索过程中的中间解,避免重复计算这几种方法进行优化。
       而八数码难题的求解就是一个搜索的过程,所以我们首先根据求解八数码难题的过程来体会搜索算法的应用。

1.2 实验目的

       考察和培养对图搜索知识点的综合理解和应用。体验搜索的具体实现过程以及各种搜索算法的实现及算法之间的优缺点和差异,从而为以后可以运用搜索算法打下基础。

二、实验原理

2.1 状态空间表示法

       人工智能的多个研究领域从求解现实问题的过程来看,都可以抽象为一个:问题求解“过程,问题求解实际上就是一个搜索过程。在搜索过程开始之前,必须先用某种方法或某几种方法的混合来表示问题。问题的求解技术主要涉及两个方面:问题的表示和求解的方法。
       状态空间表示法法:用来表示问题及其搜索过程的一种方法。基于解答空间的问题表示和求解方法,它是以状态和算符为基础来表示和求解问题的。主要包括:状态,算符,状态空间。
1,状态
       表示问题求解过程中每一步问题状况的数据结构,一般用一组数据表示:
S k = [ S k ] 0 , [ S k ] 1 , … … S_k= {[Sk]_0,[Sk]_1,……} Sk=[Sk]0,[Sk]1,
       式中每个元素为集合的分量,称为状态变量:每一个分量给予确定的值时,得到了一个具体的状态;任何一种类型的数据结构都可以用来描述状态,只要它有利于问题求解;在程序中用字符,数字,记录,数组,结构,对象等表示状态。
2,算符
       当对一个问题状态使用某个可用操作时,他将引起该状态中某些分量值的变化,从而使问题从一个具体状态变为另一个具体状态。
       算符可以理解为状态集合上的一个函数,它描述了状态之间的关系,算符可以是某种操作,规则,行为,变换,算子,函数,过程等,算符也称为操作,问题的状态也只能经定义在其上的这种操作而改变。
3,状态空间
       用来描述一个问题的全部状态以及这些状态之间的相互关系,状态空间常用一个三元组表示:(S,F,G),其中S为问题的所有初始状态的集合,F为算符的集合,G为目标状态的集合。

2.2 八数码难题

       八数码难题:又称九宫格问题。
在这里插入图片描述
       首先给定一个九宫格的初始状态,只能将其中的空格进行移动,直到外面的八个数字达到目标状态。

2.3 图搜索算法

2.3.1 盲目式搜索

在这里插入图片描述
1、宽度优先
       对上图进行宽度优先搜索,并列出其Open表和Close表。
在这里插入图片描述
       其结点的先后访问顺序为:A->B->C->D->E->F->G->H->I->J->K
2,深度优先
       对上图进行深度优先搜索,并列出其Open表和Close表。在这里插入图片描述
       其结点的先后访问顺序为:A->B->D->H->K->E->C->F->I->G->J

2.3.2启发式搜索

       启发式搜索:启发性信息是指那种与具体问题求解过程有关的,并可指导搜索过程朝着最优希望方向前进的控制信息。
       启发式搜索是利用与问题有关的启发性信息,并以这些启发性信息指导的搜索的问题求解过程。
        A ∗ A^* A算法:启发式搜索,是一种尽可能基于现有信息的搜索策略,也就是说搜索过程中尽量利用目前已知的诸如迭代步数,以及从初始状态和当前状态到目标状态估计所需的费用等信息。
        A ∗ A^* A算法可以选择下一个被检查的节点时引入了已知的全局信息,对当前结点距离终点的距离作出估计,作为评价该节点处于最优路线上的可能性的量度,这样可以首先搜索可能性大的节点,从而提高了搜索过程的效率。
        A ∗ A^* A算法的基本思想如下:引入当前节点j的估计函数f*,当前节点j的估计函数定义为:
f ( j ) = g ( j ) + h ( j ) f ( j ) = g ( j ) + h ( j ) f(j)=g(j)+h(j)
       其中g(j)是从起点到当前节点j的实际费用的量度,h*(j)是从节点j到终点的最小费用的估计,可以依据实际情况,选择h*(j)的具体形式,h*(j)要满足一个要求:不能高于节点j到终点的实际最小费用。从起始节点点向目的节点搜索时,每次都搜索f*(j)最小的节点,直到发现目的节点。
       A*算法的核心是设计估价函数,设计估价函数h(j)有很多方法,例如欧几里得距离和曼哈顿距离等。
算法步骤:
       1:生成空的Open表、Close表,将起点S放入Open表
       2:如果Open表为空,则失败退出。
       3:从Open表中,找出头节点U,作为当前节点,将它从OPEN表中移除。
       4:判断U是否为终点,如果是,则通过节点U的父指针,一直遍历到起点,找到到最优路径,算法结束。否则,转步5.
       5:扩展当前节点U,找到其扩展的后继节点集合V,遍历集合V中的节点,如果节点即不在Open表也不在Close表,计算该节点的估计值,将节点加入到OPEN表,设置父节点为U.如果节点在Open表、Close表,比较当前节点的代价g(v)和Open表、Close表中代价,如果当前节点的代价g(v)小,更新表中的代价和父节点指针。
       6:按估价值递增的顺序,对Open表中所有节点进行排序。
       7:转步2.

三、实验步骤

1,首先给定任一初始状态和目标状态;
2,分别使用宽度优先搜索,广度优先搜索和A算法求解问题;
3,分析使用不同方法求解问题所需要的步骤。
在这里插入图片描述
       宽度优先:A->B->C->D->E->F->G->H->I->J->K->L->M
       深度优先:A->B->F->G->C->H->I->D->J->K->E->L->M
在这里插入图片描述
       如上图所示,不同算法的搜索过程不同,对于宽度和广度优先搜索两种盲目式搜索算法来说,不同的起始状态和终止状态在两种搜索策略下的结果差异可能非常大,而A
算法作为启发式搜索,可以根据每一步的结果来决定下一步的走向,所以在通常情况下更容易找到目标结果。

四、实验结果

4.1 宽度优先

在这里插入图片描述

4.2 广度优先

在这里插入图片描述

4.3 A*算法

在这里插入图片描述

       根据三种不同的算法,选择相同的起始状态和目标状态,三种算法都得到了最终答案,但是发现,宽度优先搜索和A算法都只用了5步就得到了结果,但是广度优先搜索却用了1579步才得到了结果,而宽度优先和A算法的路径则完全相同。

五、实验分析

       经过实验可得,如果所给八数码问题可解,那么深度,广度和A*搜索算法都可以得到解,只是不同的算法收敛的速度不尽相同,因此针对深度优先和广度优先两种盲目式搜素算法做一些改进得到了有界深度优先搜索算法,但是其不一定可以得到结果,所以在一些复杂的网页搜索中大多数采用的是启发式搜索,而在一些数据库的搜素算法中则采用跳表,B+树,红黑树等数据结构来升高数据的维度达到减少搜索时间的目的。

六、心得体会

       针对不同的问题,各种算法的表现不尽相同,所以明白各种算法的特点及适用条件才可以正确的选择可用的算法。

七、python代码

7.1 宽度深度优先搜索

G = {}
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
    b = a[:i] + a[j] + a[i+1:j] + a[i] + a[j+1:]
    return b
def solvePuzzle_depth(start, des):
    src = 0
    dest = 0
    for i in range(1,9):
        fist=0
        for j in range(0,i):
          if start[j]>start[i] and start[i]!='0':
              fist = fist+1
        src = src+fist
    for i in range(1, 9):
        fist=0
        for j in range(0, i):
          if des[j] > des[i] and des[i] != '0':
              fist = fist+1
        dest = dest+fist
    if (src%2)!=(dest%2):
        return -1, None
    G[start] = -1
    stack_layouts = []
    stack_layouts.append(start)
    bFound = False
    while len(stack_layouts) > 0:
        #curLayout = stack_layouts.pop()  # 出栈,深度优先
        curLayout = stack_layouts.pop(0)  # 把栈改成队列,广度优先
        if curLayout == des:
            break
        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.get(newLayout) == None:
                G[newLayout] = curLayout
                stack_layouts.append(newLayout)
    lst_steps = []
    lst_steps.append(curLayout)
    while G[curLayout] != -1:
        curLayout = G[curLayout]
        lst_steps.append(curLayout)
    lst_steps.reverse()
    return 0, lst_steps

if __name__ == "__main__":
    start = "283104765"
    des = "123804765"
    retCode, lst_steps = solvePuzzle_depth(start, des)
    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:])

7.3 A*算法

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
    b = a[:i] + a[j] + a[i+1:j] + a[i] + a[j+1:]
    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):
    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':
              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)
        # 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
                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 = "283104765"
    destLayout = "123804765"
    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:])
  • 17
    点赞
  • 205
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
好的,以下是使用Java实现A*算法解决数码问题的示例代码。 ```java import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.PriorityQueue; public class EightPuzzleSolver { // 定义目标状态 private static final int[] GOAL_STATE = {1, 2, 3, 4, 5, 6, 7, 8, 0}; // 定义空白格子的移动方向 private static final int[] dx = {0, 0, -1, 1}; private static final int[] dy = {-1, 1, 0, 0}; // 定义节点类 private static class Node { int[] state; // 当前状态 int zeroPos; // 空白格子的位置 int h; // 启发式函数的值 int g; // 到达当前状态的代价 Node parent; // 父节点 int move; // 移动方向 Node(int[] state, int zeroPos, int g, Node parent, int move) { this.state = Arrays.copyOf(state, state.length); this.zeroPos = zeroPos; this.g = g; this.parent = parent; this.move = move; this.h = calcHeuristicValue(); } // 计算启发式函数的值 private int calcHeuristicValue() { int sum = 0; for (int i = 0; i < 9; i++) { if (state[i] == 0) continue; int x = (state[i] - 1) / 3; int y = (state[i] - 1) % 3; sum += Math.abs(x - i / 3) + Math.abs(y - i % 3); } return sum; } // 判断当前状态是否是目标状态 boolean isGoal() { return Arrays.equals(state, GOAL_STATE); } // 列出所有可能的下一步状态 ArrayList<Node> expand() { ArrayList<Node> nextNodes = new ArrayList<>(); int x = zeroPos / 3, y = zeroPos % 3; for (int d = 0; d < 4; d++) { int nx = x + dx[d], ny = y + dy[d]; if (nx < 0 || nx >= 3 || ny < 0 || ny >= 3) continue; int newPos = nx * 3 + ny; int[] newState = Arrays.copyOf(state, state.length); newState[zeroPos] = newState[newPos]; newState[newPos] = 0; nextNodes.add(new Node(newState, newPos, g + 1, this, d)); } return nextNodes; } } // A*算法 public static int[] solve(int[] startState) { PriorityQueue<Node> pq = new PriorityQueue<>(Comparator.comparingInt(node -> node.h + node.g)); pq.offer(new Node(startState, getZeroPos(startState), 0, null, -1)); while (!pq.isEmpty()) { Node node = pq.poll(); if (node.isGoal()) { return getResult(node); } for (Node next : node.expand()) { pq.offer(next); } } return null; } // 获取空白格子的位置 private static int getZeroPos(int[] state) { for (int i = 0; i < 9; i++) { if (state[i] == 0) return i; } return -1; } // 获取结果路径 private static int[] getResult(Node node) { int[] result = new int[node.g]; int i = node.g - 1; while (node.parent != null) { result[i--] = node.move; node = node.parent; } return result; } public static void main(String[] args) { int[] startState = {2, 8, 3, 1, 6, 4, 7, 0, 5}; int[] result = EightPuzzleSolver.solve(startState); if (result != null) { System.out.println("Steps: "); for (int move : result) { switch (move) { case 0: System.out.println("Up"); break; case 1: System.out.println("Down"); break; case 2: System.out.println("Left"); break; case 3: System.out.println("Right"); break; } } } else { System.out.println("No solution found."); } } } ``` 该代码使用了优先队列来实现A*算法,并且定义了节点类来表示八数码问题中的一个状态。在节点类中,使用了启发式函数来评估当前状态的优劣,并且列出了所有可能的下一步状态。在主函数中,可以通过调用`solve`方法来解决数码问题,并且获取结果路径。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值