迷宫问题求解之“A*搜索”(二)
摘要:在迷宫问题求解之“穷举+回溯”(一)这篇文章中采用“穷举+回溯”的思想,虽然能从迷宫的入口到出口找出一条简单路径,但是找出来的不是最优路径。因此本文采用A*搜索算法,求解迷宫问题的最优路径。
1 A*搜索算法简介
A*搜索算法是一种启发式搜索算法。所谓启发式搜索算法,就是在盲目搜索算法中加入一个启发函数,在当前节点搜索完毕后,通过这个启发函数来进行计算,选择代价最少的节点作为下一步搜索的节点。通过这样的方式就能够找到最优解。
DFS,BFS这两种搜索方式都属于盲目的搜索方式,它不会在选择下一个节点的时候进行代价计算,而是按照一个固定的方式选择,这样在运气不好的情况,会对所有节点进行遍历。
A*搜索算法的核心就在于如何设计一个好的启发函数,启发函数的表达形式一般如下:
$$
f(n)=g(n)+h(n)
$$
其中$f(n)$是每个节点可能的估值或者代价值,它由两部分组成。
- $g(n)$:它表示从起点到搜索点的代价。
- $h(n)$:它表示从搜索点到目标点的代价,$h(n)$设计的好坏,直接影响到该A*算法的效率。
一种具有$f(n)=g(n)+h(n)$策略的启发式算法能成为A*算法的充分条件是:
搜索树上存在着从起始点到终了点的最优路径。
问题域是有限的。
所有结点的子结点的搜索代价值都大于零。
h(n)=<h*(n) (h*(n)为实际问题的代价值)。
很显然,1,2,3点都很容易满足的。关键是设计一个好的$h(n)$函数。
2 A*搜索算法描述
A*算法的流程如下:
A*搜索算法需要两个额外的存储空间,OPEN表和CLOSE表。
初始化OPEN和CLOSE表,将开始节点(开始节点的H和G的值都视为0)放入OPEN表中。
重复下面步骤:
(循环)
2.1 在开始列表中查找具有最小F值的节点,并把查找到的节点作为当前节点; 2.2 把当前节点从OPEN列表中删除,并加入到CLOSE列表中; 2.3 对当前节点相邻的每一个节点依次执行以下步骤:`(遍历)` **i **如果相邻节点不可通行或者该节点已经在CLOSE列表中,则什么操作也不执行; **ii **如果该相邻节点不在OPEN列表中,则将该节点添加到OPEN列表中,`并将该相邻节点的父节点设置当前节点`,同时计算保存相邻节点的F值;**iii **如果该相邻节点在OPEN表中, 则判断若经由当前节点到达该相邻节点的F值是否小于原来保存的F值,若小于,则将该相邻节点的父节点设为当前节点,并重新设置该相邻节点的F值. 2.4 若当终点节点被加入到开发列表作为待检验节点时,表示路径已找到,此时应终止循环;若当开放列表为空,表示没有中开始节点到终点节点的路径,此时循环结束;
循环终止后,从终点节点开始沿着父节点往前遍历,从后到前输出搜索的路径。
其中,步骤2.3 ii中的红色部分尤为重要,设置相邻节点的父节点为当前节点是为了记录从起始节点到终止节点路径,方便搜索到了终点节点后进行路径输出。
在采用A* 算法对迷宫路径求解中,$g(n)$和$h(n)$函数都采用曼哈顿距离
,曼哈顿距离代表两个点在标准坐标系上的绝对轴距总和。
$$
d(i,j)=|x1-x2|+|y1-y2|
$$
即每次获取的当前通道块,都会对其到入口通道快和出口通道块的曼哈顿距离进行计算。求算当前通道块的F值进行保存。该函数满足启发式算法成为A*算法的充分条件的1,2,3,4。
还需要注意的一点是:使用A*求解迷宫路径算法中,需要把2.3步骤下面的遍历操作进行更改,如下:
i 如果相邻节点不可通行或者该节点已经在CLOSE或者OPEN列表中,则什么操作也不执行。
ii 如果该相邻节点不在OPEN和CLOSE列表中,则将该节点添加到OPEN列表中,
并将该相邻节点的父节点设置当前节点
,同时计算保存相邻节点的F值。
在迷宫求解中使用A*搜索算法不需要去更新OPEN表中的已存在节点的F值,因为每个节点的位置都是确定的,所以曼哈顿距离就固定的。如果是带权值的路网求解最短路径,那么就需要去更新OPEN表中节点的F值,如Dijkstra算法。
3 程序实现
下面使用使用c#语言,实现A*算法求解迷宫最优的路径。关键代码如下:
3.1 节点实体类
class PathNode
{
public PathNode(int row, int col)
{
this.Row = row;
this.Col = col;
}
public int Row;
public int Col;
public int F=int.MaxValue;
public PathNode Father=null;//该节点的前继节点
}
3.2 A*搜索类
class AStartSolution
{
int [,]map;
int startX;
int startY;
int endX;
int endY;
public AStartSolution (int [,]map,int startX,int startY,int endX,int endY)
{
this.map=map;
this.startX =startX;
this.startY=startY;
this.endX=endX;
this.endY=endY ;
}
/*路径寻找*/
public PathNode FindPath()
{
PathNode endNode = null;//终点
List<PathNode> openList = new List<PathNode>();
List<PathNode> closeList = new List<PathNode>();
//把起点放入open列表中
PathNode startNode = new PathNode(startX, startY);
openList.Add(startNode);
int tryCount = 0;
while (openList.Count != 0)
{
tryCount++;
PathNode curMinFNode = GetMinFPathNode(openList);
openList.Remove (curMinFNode );//从open列表中移除
closeList .Add (curMinFNode );//加入到close列表中
if (curMinFNode.Row == endX && curMinFNode.Col == endY)//当前节点是目标节点
{
endNode = curMinFNode;
break;
}
else//搜索4个方向并进行处理
{
//东
HandleChildNode(curMinFNode, curMinFNode.Row, curMinFNode.Col + 1, openList, closeList);
//南
HandleChildNode(curMinFNode, curMinFNode.Row+1, curMinFNode.Col, openList, closeList);
//西
HandleChildNode(curMinFNode, curMinFNode.Row, curMinFNode.Col-1, openList, closeList);
//北
HandleChildNode(curMinFNode, curMinFNode.Row-1, curMinFNode.Col, openList, closeList);
}
}
return endNode;
}
/*处理每个方向的子节点*/
private void HandleChildNode(PathNode curMinFNode,int row, int col, List<PathNode> openList, List<PathNode> closeList)
{
PathNode child = null;
if (Pass(row, col))//能通过
{
child = new PathNode(row, col);
if (!IsExistList(child, closeList) && !IsExistList(child, openList))//
{
child.F = GetF(row,col);
child.Father = curMinFNode;
openList.Add(child);
}
}
}
private PathNode GetMinFPathNode(List<PathNode> openList)
{
int minF= openList.Min(node =>node.F);
foreach (PathNode node in openList)
{
if (node.F == minF)
{
return node;
}
}
return null;
}
/*该通道块是否可通行*/
private bool Pass(int row,int col)
{
int rowCount = map.GetLength(0);
int colCount = map.GetLength(1);
//边界判断
if (row >= 0 && row < rowCount && col >= 0 && col < colCount)
{
//障碍判断
if (map[row, col] == 2)
{
return false;
}
else
{
return true;
}
}
else
{
return false;
}
}
/*当前通道块是否在OPEN或者CLOSE表中*/
private bool IsExistList(PathNode childNode, List<PathNode> list)
{
foreach (PathNode node in list)
{
if (node.Row == childNode.Row && node.Col == childNode.Col)
{
return true;
}
}
return false;
}
/*计算当前节点的F值*/
private int GetF(int curRow, int curCol)
{
int g = Math.Abs(startX - curRow) + Math.Abs(startY - curCol);
int h = Math.Abs(endX - curRow) + Math.Abs(endY - curCol);
return g + h;
}
}
3.3 与“穷举+回溯”算法的效果比较
采用同样的迷宫地图比较两种算法查找的路径,上结果图代表“穷举+回溯,下结果图代表本程序
。
4 总结
通过上面的比较可以发现,A*搜索算法寻找的路径比“穷举+回溯”算法的要短很多,和我设计迷宫地图时所想的路径是一致,是一条最优的路径。
5 资源和参考资料
参考资料:
莫水千流的博文A星寻路算法介绍
西芒xiaoP的博文A*搜索算法
yueming的博文A*路径寻找算法入门
源码地址:
http://download.csdn.net/download/mingge38/9655373
迷宫问题求解之“A*搜索”(二)的更多相关文章
- lintcode:搜索二维矩阵II
题目 搜索二维矩阵 II 写出一个高效的算法来搜索m×n矩阵中的值,返回这个值出现的次数. 这个矩阵具有以下特性: 每行中的整数从左到右是排序的. 每一列的整数从上到下是排序的. 在每一行或每一列中没 ...
- lintcode :搜索二维矩阵
题目: 搜索二维矩阵 写出一个高效的算法来搜索 m × n矩阵中的值. 这个矩阵具有以下特性: 每行中的整数从左到右是排序的. 每行的第一个数大于上一行的最后一个整数. 样例 考虑下列矩阵: [ [1 ...
- 算法进阶面试题05——树形dp解决步骤、返回最大搜索二叉子树的大小、二叉树最远两节点的距离、晚会最大活跃度、手撕缓存结构LRU
接着第四课的内容,加入部分第五课的内容,主要介绍树形dp和LRU 第一题: 给定一棵二叉树的头节点head,请返回最大搜索二叉子树的大小 二叉树的套路 统一处理逻辑:假设以每个节点为头的这棵树,他的最 ...
- LintCode-38.搜索二维矩阵 II
搜索二维矩阵 II 写出一个高效的算法来搜索m×n矩阵中的值,返回这个值出现的次数. 这个矩阵具有以下特性: 每行中的整数从左到右是排序的. 每一列的整数从上到下是排序的. 在每一行或每一列中没有重复 ...
- LeetCode74.搜索二维矩阵
74.搜索二维矩阵 描述 编写一个高效的算法来判断 m x n 矩阵中,是否存在一个目标值.该矩阵具有如下特性: 每行中的整数从左到右按升序排列. 每行的第一个整数大于前一行的最后一个整数. 示例 示 ...
- LeetCode:搜索二维矩阵【74】
LeetCode:搜索二维矩阵[74] 题目描述 编写一个高效的算法来判断 m x n 矩阵中,是否存在一个目标值.该矩阵具有如下特性: 每行中的整数从左到右按升序排列. 每行的第一个整数大于前一行的 ...
- Leetcode 240.搜索二维矩阵II
搜索二维矩阵II 编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target.该矩阵具有以下特性: 每行的元素从左到右升序排列. 每列的元素从上到下升序排列. 示例: 现有 ...
- leetcode-240搜索二维矩阵II
搜索二维矩阵II class Solution: def searchMatrix(self, matrix, target): """ :type matrix: Li ...
- Leetcode之二分法专题-240. 搜索二维矩阵 II(Search a 2D Matrix II)
Leetcode之二分法专题-240. 搜索二维矩阵 II(Search a 2D Matrix II) 编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target.该矩阵 ...
随机推荐
- 设计模式之Iterator模式(2)
这篇文章比较简单,作一个笔记. 模拟Iterator. Iterator接口: package cn.asto.Interator; public interface Iterator { publi ...
- js-对象-2
对象: 对象是一组具有属性和方法的经过组织的数据. 默认对象: 日期对象:(日期基线:1970年1月1日00:00:00) 建立日期对象(实例): 格式:日期对象名称=new Date([日期参数]) ...
- java switch语句注意的事项
1.switch语句使用的变量只能是byte.char.short.string数据类型. 2.case后面gender数据必须是一个常量. 3.switch的停止条件: switch语句一旦比配上了 ...
- sprint3 总结
sprint3 本次的主要任务是找项目中的bug,并与客户不断地沟通以满足客户的要求.队友主要负责找项目中的bug或提出一些建议.我主要是负责与客户沟通和修复bug.总的来说进展还算顺利. 团队贡献分 ...
- spring日记------部署环境、写demo
一.安装jdk1.7 祥见http://zhinan.sogou.com/guide/detail/?id=1610006590 二.创建web项目 略 三.配置ssm环境 3.1添加spring.m ...
- Codeforces Round #313 (Div. 1) B. Equivalent Strings
Equivalent Strings Problem's Link: http://codeforces.com/contest/559/problem/B Mean: 给定两个等长串s1,s2,判断 ...
- 自定义ConfigurationSection,创建多个嵌套的ConfigurationElementCollection节点
由于接口地址都是固定的,所以想到使用自定义节点,来将接口都配置到web.config中. 很快,v1.0版本出炉: public class RequestConfigSection : Config ...
- C#基础--之数据类型
C#基础--之数据类型 摘自:http://www.cnblogs.com/tonney/archive/2011/03/18/1987577.html 在第一章我们了解了C#的输入.输出语句后,我这 ...
- ref与out的区别
若要使用 ref 参数,方法定义和调用方法均必须显式使用 ref 关键字,如下面的示例所示. class RefExample { static void Method(ref int i) { // ...
- FL2440驱动添加(2): RTC(Real time clock)
一,Linux下的时间分为两种,系统时间与硬件时间(RTC芯片): 1,系统时间就是运行系统能够直接看到的时间: 2,硬件时间就是RTC芯片中的时间,断电任然有电池供电: linux系统开机时,会从R ...