摘要:在迷宫问题求解之“穷举+回溯”(一)这篇文章中采用“穷举+回溯”的思想,虽然能从迷宫的入口到出口找出一条简单路径,但是找出来的不是最优路径。因此本文采用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*算法的充分条件是:

  1. 搜索树上存在着从起始点到终了点的最优路径。

  2. 问题域是有限的。

  3. 所有结点的子结点的搜索代价值都大于零。

  4. h(n)=<h*(n) (h*(n)为实际问题的代价值)。

很显然,1,2,3点都很容易满足的。关键是设计一个好的$h(n)$函数。

2 A*搜索算法描述

A*算法的流程如下:

A*搜索算法需要两个额外的存储空间,OPEN表和CLOSE表。

  1. 初始化OPEN和CLOSE表,将开始节点(开始节点的H和G的值都视为0)放入OPEN表中。

  2. 重复下面步骤:(循环)

    2.1 在开始列表中查找具有最小F值的节点,并把查找到的节点作为当前节点;
    
    2.2 把当前节点从OPEN列表中删除,并加入到CLOSE列表中;
    
    2.3 对当前节点相邻的每一个节点依次执行以下步骤:`(遍历)` **i      **如果相邻节点不可通行或者该节点已经在CLOSE列表中,则什么操作也不执行; **ii      **如果该相邻节点不在OPEN列表中,则将该节点添加到OPEN列表中,`并将该相邻节点的父节点设置当前节点`,同时计算保存相邻节点的F值;**iii      **如果该相邻节点在OPEN表中, 则判断若经由当前节点到达该相邻节点的F值是否小于原来保存的F值,若小于,则将该相邻节点的父节点设为当前节点,并重新设置该相邻节点的F值.
    
    2.4 若当终点节点被加入到开发列表作为待检验节点时,表示路径已找到,此时应终止循环;若当开放列表为空,表示没有中开始节点到终点节点的路径,此时循环结束;
  3. 循环终止后,从终点节点开始沿着父节点往前遍历,从后到前输出搜索的路径。

其中,步骤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*搜索”(二)的更多相关文章

  1. lintcode:搜索二维矩阵II

    题目 搜索二维矩阵 II 写出一个高效的算法来搜索m×n矩阵中的值,返回这个值出现的次数. 这个矩阵具有以下特性: 每行中的整数从左到右是排序的. 每一列的整数从上到下是排序的. 在每一行或每一列中没 ...

  2. lintcode :搜索二维矩阵

    题目: 搜索二维矩阵 写出一个高效的算法来搜索 m × n矩阵中的值. 这个矩阵具有以下特性: 每行中的整数从左到右是排序的. 每行的第一个数大于上一行的最后一个整数. 样例 考虑下列矩阵: [ [1 ...

  3. 算法进阶面试题05——树形dp解决步骤、返回最大搜索二叉子树的大小、二叉树最远两节点的距离、晚会最大活跃度、手撕缓存结构LRU

    接着第四课的内容,加入部分第五课的内容,主要介绍树形dp和LRU 第一题: 给定一棵二叉树的头节点head,请返回最大搜索二叉子树的大小 二叉树的套路 统一处理逻辑:假设以每个节点为头的这棵树,他的最 ...

  4. LintCode-38.搜索二维矩阵 II

    搜索二维矩阵 II 写出一个高效的算法来搜索m×n矩阵中的值,返回这个值出现的次数. 这个矩阵具有以下特性: 每行中的整数从左到右是排序的. 每一列的整数从上到下是排序的. 在每一行或每一列中没有重复 ...

  5. LeetCode74.搜索二维矩阵

    74.搜索二维矩阵 描述 编写一个高效的算法来判断 m x n 矩阵中,是否存在一个目标值.该矩阵具有如下特性: 每行中的整数从左到右按升序排列. 每行的第一个整数大于前一行的最后一个整数. 示例 示 ...

  6. LeetCode:搜索二维矩阵【74】

    LeetCode:搜索二维矩阵[74] 题目描述 编写一个高效的算法来判断 m x n 矩阵中,是否存在一个目标值.该矩阵具有如下特性: 每行中的整数从左到右按升序排列. 每行的第一个整数大于前一行的 ...

  7. Leetcode 240.搜索二维矩阵II

    搜索二维矩阵II 编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target.该矩阵具有以下特性: 每行的元素从左到右升序排列. 每列的元素从上到下升序排列. 示例: 现有 ...

  8. leetcode-240搜索二维矩阵II

    搜索二维矩阵II class Solution: def searchMatrix(self, matrix, target): """ :type matrix: Li ...

  9. Leetcode之二分法专题-240. 搜索二维矩阵 II(Search a 2D Matrix II)

    Leetcode之二分法专题-240. 搜索二维矩阵 II(Search a 2D Matrix II) 编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target.该矩阵 ...

随机推荐

  1. HTML5 语义元素

    返回目录 http://hovertree.com/h/bjaf/html5zixueji.htm 一个语义元素能够清楚的描述其意义给浏览器和开发者.无语义 元素实例: <div> 和 & ...

  2. 以对象的方式来访问xml数据表(三)

    怎样以对象的方式来访问xml数据表? 在讲如何具体实现(二)中所说的专门用于访问xml文件的动态链接库之前,我们先来看看这个动态链接库具体要实现什么功能. 动态链接库IXmlDB.dll的功能: 1. ...

  3. 【C#进阶系列】00 序

    老早就被各种推荐<CLR via C#>这本书了,然而一直没去学. 因为工作中所需要的.NET功底目前算是足以应付了,而前端却不熟,所以跑去学了一段时间前端的知识. 终于算是把前端方面的基 ...

  4. C#的回调方法

    C# 里面回调方法一般指某个委托.也可以说是接口. using System; using System.Collections.Generic; using System.Linq; using S ...

  5. 世界上不存在什么RedBSD,SuseBSD或者ArchBSD,Turb...

    世界上不存在什么RedBSD,SuseBSD或者ArchBSD,TurboBSD之类的东西.

  6. 泛函编程(22)-泛函数据类型-Monoid In Action

    在上一节我们讨论了Monoid的结合性和恒等值的作用以及Monoid如何与串类元素折叠算法相匹配.不过我们只示范了一下基础类型(primitive type)Monoid实例的应用,所以上一节的讨论目 ...

  7. 【iOS】Quartz2D简单介绍

    一.什么是Quartz2D Quartz 2D是⼀个二维绘图引擎,同时支持iOS和Mac系统 Quartz 2D能完成的工作: 绘制图形 : 线条\三角形\矩形\圆\弧等 绘制文字 绘制\生成图片(图 ...

  8. 设计模式总结篇系列:命令模式(Command)

    在程序设计中,经常会遇到一个对象需要调用另外一个对象的某个方法以达到某种目的,在此场景中,存在两个角色:请求发出者和请求接收者.发出者发出请求,接收者接收请求并进行相应处理.有时候,当需要对请求发出者 ...

  9. JavaScript调Java

    1.映射Java对象到JavaScript对象上 MainActivity.java package com.example.jsdemo; import android.os.Bundle; imp ...

  10. 从" ThinkPHP 开发规范 "看 PHP 的命名规范和开发建议

    稍稍水一篇博客,摘抄自Think PHP 的开发规范,很有引导性,我们可以将这些规范实践到原生 PHP 中. 命名规范 使用ThinkPHP开发的过程中应该尽量遵循下列命名规范: 类文件都是以.cla ...