算法笔记:使用A*算法解决八数码问题

coursera上普林斯顿大学算法课中第四周的作业,使用A*算法解决八数码问题。

作业的具体要求如下:https://coursera.cs.princeton.edu/algs4/assignments/8puzzle/specification.php

我提交的作业(90分):https://github.com/caozixuan/AlgorithmLearning/tree/master/Exercise/8Puzzle/src

题目要求

八数码问题是一个十分经典的问题,我相信大家小时候肯定都玩过这个游戏。在这个问题中,我们需要设计一个算法,能够快速的找到通向答案的路径。把所有的标签放在应该放置的位子上(题目并不一定是九宫格)。

辅助知识

优先权队列

优先权队列的知识具体可见我的这篇博文 算法笔记:优先权队列

在本次作业中,我们利用优先权队列去储存每一个状态节点极其对应的权值。

A*搜索算法

提到搜索算法,我们首先要明白,搜索算法是干什么的?比较简单的说,搜索算法就像导航软件,是帮助我们找路的。当你站在街上茫然四顾,你就需要你的导航软件,告诉你,前面的几条路,你应该走哪一条。搜索算法也是如此,告诉你应该做什么决策,进入什么状态。

回想一下,我们之前学过哪些搜索算法,比如BFS,DFS,这两种算法告诉你基于宽度优先或者深度优先的原则进行搜索,A*算法要相对复杂一些,它是根据一个评估函数来进行决策的。在评估过程中,我们不断通过将与当前状态相邻的节点加入队列,每次取出评估函数最小的点,继续加入其相邻节点,直到找到其目标为止。下图展示了上述的过程:

下面我们来聊一聊这个评估函数。在A*算法中,这个评估函数被分成了两部分,我们目前已经付出的代价G和我们预计从当前点到目标点还要付出的代价H。已经付出的代价在这个问题中,我们可以认为G = the number of moves,走了一步G就是几,对于H,原链接中给了两种方案,一个是目前有几个数码块还不在它应该在的位置上,另一个是所有数码块与其应该在的位置的曼哈顿距离的和。

需要注意的地方

排除冗余状态

这个地方在原链接中讲的很清楚,大家可以思考一下,什么情况下会出现将相同的状态加入队列的情况。

无解的情况

这个地方困扰了我好久,资料中说到了一个定理,一个有界的状态随意交换两个数码,一定会变成无解的状态。无解的状态,随意交换两个数码,也就一定会变成有解的状态。我们要怎么根据这个定理去判断当前的状态可不可解呢?这个地方的难点是A*算法也没有给你一个范围,告诉你某个规模的问题在多少步内可以求解。后来发现其实很简单,在求解某个问题前,把这个问题变成两个问题,一个是旧问题,一个就是把旧问题中任意两个数码交换。两个问题同时求解。旧问题先得到答案,说明这个问题有解,如果新问题得到答案,就说明旧问题无解。KO!

关键代码

public Solver(Board initial){
        GameTreeNode root = new GameTreeNode(initial, null);
        GameTreeNode rootTwin = new GameTreeNode(initial.twin(),null);
        MinPQ<GameTreeNode> q = new MinPQ<>();
        MinPQ<GameTreeNode> qTwin = new MinPQ<>();
        q.insert(root);
        qTwin.insert(rootTwin);
        while(true)
        {
            GameTreeNode curNode = q.delMin();
            if(curNode.board.isGoal())
            {
                canSolve = true;
                solveNode = curNode;
                break;
            }
            else
            {
                for(Board b:curNode.board.neighbors())
                {
                    if (curNode.father == null || !b.equals(curNode.father.board)) {
                        GameTreeNode node = new GameTreeNode(b, curNode);
                        q.insert(node);
                    }
                }
            }

            GameTreeNode curNodeTwin = qTwin.delMin();
            if(curNodeTwin.board.isGoal())
            {
                canSolve = false;
                solveNode = null;
                break;
            }
            else
            {
                for(Board b:curNodeTwin.board.neighbors())
                {
                    if(curNodeTwin.father==null||!b.equals(curNodeTwin.father.board))
                    {
                        GameTreeNode node = new GameTreeNode(b,curNodeTwin);
                        qTwin.insert(node);
                    }
                }
            }

        }



    }

 

  • 5
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
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在两个状态中的偏移距离。
A*算法(A* Search Algorithm)是一种启发式搜索算法,常用于求解路径finding和游戏AI中的最优解问题,包括经典的八数码游戏(也称作15 puzzle)。在八数码游戏中,A*算法能有效地找到从初始状态到目标状态的最短路径。 关于A*算法八数码问题上的应用,有很多研究论文和开源代码库可供参考。以下是一些经典的参考文献: 1. "A* Search" by Hart, Nilsson, and Raphael: 这篇1968年的论文是A*算法的原始发表,虽然是针对一般路径寻找问题,但概念适用于八数码游戏的求解。 2. "Efficient solution of the 15-puzzle using A*" by J. H. van den Heuvel: 这篇论文专门探讨了如何利用A*算法优化八数码游戏的求解策略。 3. "A* search with domain-specific heuristics for solving the 15-puzzle" by R. E. Korf: Korf在论文中讨论了针对八数码游戏设计特定启发函数的重要性。 4. "Path Finding in the 15 Puzzle Using A*" by M. P. de Freitas, T. F. Gomes, and A. C. Costa: 这篇文章深入分析了A*在解决复杂状态空间下的表现。 5. "An Improved A* Algorithm for Solving the 15 Puzzle" by B. S. Chakraborty and S. N. Chakraborty: 提出了一些改进A*算法的具体技巧,以提升八数码游戏的求解效率。 相关问题: 1. A*算法的关键组成部分是什么? 2. 八数码游戏中如何定义启发式函数来评估节点的优先级? 3. 除了经典A*,还有哪些优化方法可以用于加速八数码游戏的求解

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值