上一篇文章中提到了走迷宫问题《编程练习:走迷宫问》题 是使用广度优先算法解出的,最后给出了A*算法,此算法是一种静态路网中求解最短路最有效的算法。最后扯点别的:在人工智能,游戏制作中好像很有用,对于参考文献《A* Pathfinding for Beginners》 中最后讨论的例如游戏设计中避免碰撞,不同地形损耗,路径平滑等,联想到“三国志11”中的操作,感觉醍醐灌顶,有时间参与此类工作要再多研究了。这篇文章主要介绍相应的实现。
问题描述:
实现给定迷宫,给出你认为最快的走出迷宫步数和策略;若不存在,请输出0。
解决思路:
A* (A-Star)算法是一种静态路网中求解最短路最有效的直接搜索方法,也是许多其他问题的常用启发式算法。
注意是最有效的直接搜索算法。之后涌现了很多预处理算法(ALT,CH,HL等等),在线查询效率是A*算法的数千甚至上万倍。
公式表示为: f(n)=g(n)+h(n),
其中 f(n) 是从初始状态经由状态n到目标状态的代价估计,g(n) 是在状态空间中从初始状态到状态n的实际代价,h(n) 是从状态n到目标状态的最佳路径的估计代价。(对于路径搜索问题,状态就是图中的节点,代价就是距离)百度百科《A*算法》
上述链接还给出了可运行的C语言程序(已运行没问题),如果做嵌入式设计是否能修改移植呢?嗯。。。
代码实现:
此处尝试编写A*算法,但是google发现了类似实现,现记录如下:
/*
//转自:http://baike.baidu.com/link?url=j_pHMECOXPa_eidbnJhdVPCQDoKsk1EjtEkPhsfXRG0xCGSCyoTNOIVWZrBt5NuLmzVgZPzAczicC9rRJBSsn0u8x_0cDSBtbUzCSvBhsW7
#include <stdio.h>
#include <math.h>
#define _CRT_SECURE_NO_WARNINGS
#define MaxLength 100 //用于优先队列(Open表)的数组
#define Height 15 //地图高度
#define Width 20 //地图宽度
#define Reachable 0 //可以到达的结点
#define Bar 1 //障碍物
#define Pass 2 //需要走的步数
#define Source 3 //起点
#define Destination 4 //终点
#define Sequential 0 //顺序遍历
#define NoSolution 2 //无解决方案
#define Infinity 0xfffffff
#define East (1 << 0)
#define South_East (1 << 1)
#define South (1 << 2)
#define South_West (1 << 3)
#define West (1 << 4)
#define North_West (1 << 5)
#define North (1 << 6)
#define North_East (1 << 7)
typedef struct
{
signed char x, y;
} Point;
const Point dir[8] =
{
{ 0, 1 }, // East
{ 1, 1 }, // South_East
{ 1, 0 }, // South
{ 1, -1 }, // South_West
{ 0, -1 }, // West
{ -1, -1 }, // North_West
{ -1, 0 }, // North
{ -1, 1 } // North_East
};
unsigned char within(int x, int y)
{
return (x >= 0 && y >= 0
&& x < Height && y < Width);
}
typedef struct
{
int x, y;
unsigned char reachable, sur, value;
} MapNode;
typedef struct Close
{
MapNode *cur;
char vis;
struct Close *from;
float F, G;
int H;
} Close;
typedef struct //优先队列(Open表)
{
int length; //当前队列的长度
Close* Array[MaxLength]; //评价结点的指针
} Open;
static MapNode graph[Height][Width];
static int srcX, srcY, dstX, dstY; //起始点、终点
static Close close[Height][Width];
// 优先队列基本操作
void initOpen(Open *q) //优先队列初始化
{
q->length = 0; // 队内元素数初始为0
}
void push(Open *q, Close cls[Height][Width], int x, int y, float g)
{ //向优先队列(Open表)中添加元素
Close *t;
int i, mintag;
cls[x][y].G = g; //所添加节点的坐标
cls[x][y].F = cls[x][y].G + cls[x][y].H;
q->Array[q->length++] = &(cls[x][y]);
mintag = q->length - 1;
for (i = 0; i < q->length - 1; i++)
{
if (q->Array[i]->F < q->Array[mintag]->F)
{
mintag = i;
}
}
t = q->Array[q->length - 1];
q->Array[q->length - 1] = q->Array[mintag];
q->Array[mintag] = t; //将评价函数值最小节点置于队头
}
Close* shift(Open *q)
{
return q->Array[--q->length];
}
// 地图初始化操作
void initClose(Close cls[Height][Width], int sx, int sy, int dx, int dy)
{ // 地图Close表初始化配置
int i, j;
for (i = 0; i < Height; i++)
{
for (j = 0; j < Width; j++)
{
cls[i][j].cur = &graph[i][j]; // Close表所指节点
cls[i][j].vis = !graph[i][j].reachable; // 是否被访问
cls[i][j].from = NULL; // 所来节点
cls[i][j].G = cls[i][j].F = 0;
cls[i][j].H = abs(dx - i) + abs(dy - j); // 评价函数值
}
}
cls[sx][sy].F = cls[sx][sy].H; //起始点评价初始值
// cls[sy][sy].G = 0; //移步花费代价值
cls[dx][dy].G = Infinity;
}
void initGraph(const int map[Height][Width], int sx, int sy, int dx, int dy)
{ //地图发生变化时重新构造地
int i, j;
srcX = sx; //起点X坐标
srcY = sy; //起点Y坐标
dstX = dx; //终点X坐标
dstY = dy; //终点Y坐标
for (i = 0; i < Height; i++)
{
for (j = 0; j < Width; j++)
{
graph[i][j].x = i; //地图坐标X
graph[i][j].y = j; //地图坐标Y
graph[i][j].value = map[i][j];
graph[i][j].reachable = (graph[i][j].value == Reachable); // 节点可到达性
graph[i][j].sur = 0; //邻接节点个数
if (!graph[i][j].reachable)
{
continue;
}
if (j > 0)
{
if (graph[i][j - 1].reachable) // left节点可以到达
{
graph[i][j].sur |= West;
graph[i][j - 1].sur |= East;
}
if (i > 0)
{
if (graph[i - 1][j - 1].reachable
&& graph[i - 1][j].reachable
&& graph[i][j - 1].reachable) // up-left节点可以到达
{
graph[i][j].sur |= North_West;
graph[i - 1][j - 1].sur |= South_East;
}
}
}
if (i > 0)
{
if (graph[i - 1][j].reachable) // up节点可以到达
{
graph[i][j].sur |= North;
graph[i - 1][j].sur |= South;
}
if (j < Width - 1)
{
if (graph[i - 1][j + 1].reachable
&& graph[i - 1][j].reachable
&& map[i][j + 1] == Reachable) // up-right节点可以到达
{
graph[i][j].sur |= North_East;
graph[i - 1][j + 1].sur |= South_West;
}
}
}
}
}
}
int bfs()
{
int times = 0;
int i, curX, curY, surX, surY;
unsigned char f = 0, r = 1;
Close *p;
Close* q[MaxLength] = { &close[srcX][srcY] };
initClose(close, srcX, srcY, dstX, dstY);
close[srcX][srcY].vis = 1;
while (r != f)
{
p = q[f];
f = (f + 1) % MaxLength;
curX = p->cur->x;
curY = p->cur->y;
for (i = 0; i < 8; i++)
{
if (!(p->cur->sur & (1 << i)))
{
continue;
}
surX = curX + dir[i].x;
surY = curY + dir[i].y;
if (!close[surX][surY].vis)
{
close[surX][surY].from = p;
close[surX][surY].vis = 1;
close[surX][surY].G = p->G + 1;
q[r] = &close[surX][surY];
r = (r + 1) % MaxLength;
}
}
times++;
}
return times;
}
int astar()
{ // A*算法遍历
//int times = 0;
int i, curX, curY, surX, surY;
float surG;
Open q; //Open表
Close *p;
initOpen(&q);
initClose(close, srcX, srcY, dstX, dstY);
close[srcX][srcY].vis = 1;
push(&q, close, srcX, srcY, 0);
while (q.length)
{ //times++;
p = shift(&q);
curX = p->cur->x;
curY = p->cur->y;
if (!p->H)
{
return Sequential;
}
for (i = 0; i < 8; i++)
{
if (!(p->cur->sur & (1 << i)))
{
continue;
}
surX = curX + dir[i].x;
surY = curY + dir[i].y;
if (!close[surX][surY].vis)
{
close[surX][surY].vis = 1;
close[surX][surY].from = p;
surG = p->G + sqrt((double)((curX - surX) * (curX - surX) + (curY - surY) * (curY - surY)));
push(&q, close, surX, surY, surG);
}
}
}
//printf("times: %d\n", times);
return NoSolution; //无结果
}
const int map[Height][Width] = {
{ 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1 },
{ 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1 },
{ 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1 },
{ 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0 },
{ 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1 },
{ 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0 }
};
const char Symbol[5][3] = { "□", "▓", "▽", "☆", "◎" };
void printMap()
{
int i, j;
for (i = 0; i < Height; i++)
{
for (j = 0; j < Width; j++)
{
printf("%s", Symbol[graph[i][j].value]);
}
puts("");
}
puts("");
}
Close* getShortest()
{ // 获取最短路径
int result = astar();
Close *p, *t, *q = NULL;
switch (result)
{
case Sequential: //顺序最近
p = &(close[dstX][dstY]);
while (p) //转置路径
{
t = p->from;
p->from = q;
q = p;
p = t;
}
close[srcX][srcY].from = q->from;
return &(close[srcX][srcY]);
case NoSolution:
return NULL;
}
return NULL;
}
static Close *start;
static int shortestep;
int printShortest()
{
Close *p;
int step = 0;
p = getShortest();
start = p;
if (!p)
{
return 0;
}
else
{
while (p->from)
{
graph[p->cur->x][p->cur->y].value = Pass;
printf("(%d,%d)→\n", p->cur->x, p->cur->y);
p = p->from;
step++;
}
printf("(%d,%d)\n", p->cur->x, p->cur->y);
graph[srcX][srcY].value = Source;
graph[dstX][dstY].value = Destination;
return step;
}
}
void clearMap()
{ // Clear Map Marks of Steps
Close *p = start;
while (p)
{
graph[p->cur->x][p->cur->y].value = Reachable;
p = p->from;
}
graph[srcX][srcY].value = map[srcX][srcY];
graph[dstX][dstY].value = map[dstX][dstY];
}
void printDepth()
{
int i, j;
for (i = 0; i < Height; i++)
{
for (j = 0; j < Width; j++)
{
if (map[i][j])
{
printf("%s ", Symbol[graph[i][j].value]);
}
else
{
printf("%2.0lf ", close[i][j].G);
}
}
puts("");
}
puts("");
}
void printSur()
{
int i, j;
for (i = 0; i < Height; i++)
{
for (j = 0; j < Width; j++)
{
printf("%02x ", graph[i][j].sur);
}
puts("");
}
puts("");
}
void printH()
{
int i, j;
for (i = 0; i < Height; i++)
{
for (j = 0; j < Width; j++)
{
printf("%02d ", close[i][j].H);
}
puts("");
}
puts("");
}
int main(int argc, const char **argv)
{
initGraph(map, 0, 0, 0, 0);
printMap();
while (scanf("%d %d %d %d", &srcX, &srcY, &dstX, &dstY) != EOF)
{
if (within(srcX, srcY) && within(dstX, dstY))
{
if (shortestep = printShortest())
{
printf("从(%d,%d)到(%d,%d)的最短步数是: %d\n",
srcX, srcY, dstX, dstY, shortestep);
printMap();
clearMap();
bfs();
printDepth();
puts((shortestep == close[dstX][dstY].G) ? "正确" : "错误");
clearMap();
}
else
{
printf("从(%d,%d)不可到达(%d,%d)\n",
srcX, srcY, dstX, dstY);
}
}
else
{
puts("输入错误!");
}
}
return (0);
}*/
//中文注释自己添加的理解,详细学习
//英文版:http://www.gamedev.net/page/resources/_/technical/artificial-intelligence/a-pathfinding-for-beginners-r2003
//中文翻译版:http://zhyt710.iteye.com/blog/739803
#include <iostream>
#include <iomanip>
#include <queue>
#include <string>
#include <math.h>
#include <ctime>
using namespace std;
//定义地图尺寸
const int n = 60; // horizontal size of the map
const int m = 60; // vertical size size of the map
static int map[n][m];
//定义开,闭二维表
static int closed_nodes_map[n][m]; // map of closed (tried-out) nodes
static int open_nodes_map[n][m]; // map of open (not-yet-tried) nodes
static int dir_map[n][m]; // map of directions
//定义邻域大小,默认四邻域
const int dir = 8; // number of possible directions to go at any position
// if dir==4
//static int dx[dir]={1, 0, -1, 0};
//static int dy[dir]={0, 1, 0, -1};
// if dir==8
static int dx[dir] = { 1, 1, 0, -1, -1, -1, 0, 1 };
static int dy[dir] = { 0, 1, 1, 1, 0, -1, -1, -1 };
//节点类
class node
{
// current position
int xPos;
int yPos;
// total distance already travelled to reach the node
//此处的level相当于参考文献中的G,表示地理距离,priority相当于F,即F=G+H,H是不考虑不可达区域的待测点到终点距离函数度量,此处有,欧氏距离,街区距离,切比雪夫距离。
int level;
// priority=level+remaining distance estimate
int priority; // smaller: higher priority
public:
node(int xp, int yp, int d, int p)
{
xPos = xp; yPos = yp; level = d; priority = p;
}
//获取位置及各式权重方法
int getxPos() const { return xPos; }
int getyPos() const { return yPos; }
int getLevel() const { return level; }
int getPriority() const { return priority; }
//更新权重方法
void updatePriority(const int & xDest, const int & yDest)
{
priority = level + estimate(xDest, yDest) * 10; //A*
}
// give better priority to going strait instead of diagonally
//注意此处的方向标号,考虑8邻域方向,垂直向下为2,顺时针方向标记
//|5|6|7|
//|4| |8|
//|3|2|1|
//所有水平垂直方向测度均为10,对角线距离测度为14(sqrt(2)*10)
void nextLevel(const int & i) // i: direction
{
level += (dir == 8 ? (i % 2 == 0 ? 10 : 14) : 10);
}
// Estimation function for the remaining distance to the goal.
const int & estimate(const int & xDest, const int & yDest) const
{
static int xd, yd, d;
xd = xDest - xPos;
yd = yDest - yPos;
// Euclidian Distance
//d = static_cast<int>(sqrt(xd*xd + yd*yd));
// Manhattan distance
d=abs(xd)+abs(yd);
// Chebyshev distance
//d=max(abs(xd), abs(yd));
return(d);
}
};
//为了算法中比较新考虑节点与open list中节点那个距离更近
// Determine priority (in the priority queue)
bool operator<(const node & a, const node & b)
{
return a.getPriority() > b.getPriority();
}
// A-star algorithm.
// The route returned is a string of direction digits.
string pathFind(const int & xStart, const int & yStart,
const int & xFinish, const int & yFinish)
{
//此处作者开辟了两个优先队列,用于处理当新增节点不如open list中继续寻路情况,需要改动父节点并删除,并入close list中
static priority_queue<node> pq[2]; // list of open (not-yet-tried) nodes
static int pqi; // pq index
static node* n0;
static node* m0;
static int i, j, x, y, xdx, ydy;
static char c;
pqi = 0;
//初始化
// reset the node maps
for (y = 0; y<m; y++)
{
for (x = 0; x<n; x++)
{
closed_nodes_map[x][y] = 0;
open_nodes_map[x][y] = 0;
}
}
// create the start node and push into list of open nodes
//开始点的位置,距离度量初始化
n0 = new node(xStart, yStart, 0, 0);
//计算F=G+H中的H(理想情况下移动到终点的耗费)即可
n0->updatePriority(xFinish, yFinish);
pq[pqi].push(*n0);
open_nodes_map[x][y] = n0->getPriority(); // mark it on the open nodes map
// A* search
while (!pq[pqi].empty())
{
// get the current node w/ the highest priority
// from the list of open nodes
n0 = new node(pq[pqi].top().getxPos(), pq[pqi].top().getyPos(),
pq[pqi].top().getLevel(), pq[pqi].top().getPriority());
x = n0->getxPos(); y = n0->getyPos();
pq[pqi].pop(); // remove the node from the open list
open_nodes_map[x][y] = 0;
// mark it on the closed nodes map
closed_nodes_map[x][y] = 1;
// quit searching when the goal state is reached
//if((*n0).estimate(xFinish, yFinish) == 0)
//首先考虑了当寻找到终点并输出策略的情况
if (x == xFinish && y == yFinish)
{
// generate the path from finish to start
// by following the directions
string path = "";
while (!(x == xStart && y == yStart))
{
//此处将距离变量登记到dir_map中,当最终找到终点时,应用此方向记录可回溯至父节点
j = dir_map[x][y];
c = '0' + (j + dir / 2) % dir;
path = c + path;
x += dx[j];
y += dy[j];
}
// garbage collection
delete n0;
// empty the leftover nodes
//当已经找到终点时,后续open list都不需要考虑了
while (!pq[pqi].empty()) pq[pqi].pop();
return path;
}
// generate moves (child nodes) in all possible directions
for (i = 0; i<dir; i++)
{
xdx = x + dx[i]; ydy = y + dy[i];
//考虑8邻域方向,需要满足合法位置,且不再close list中,且原始地图中标记可行进时
if (!(xdx<0 || xdx>n - 1 || ydy<0 || ydy>m - 1 || map[xdx][ydy] == 1
|| closed_nodes_map[xdx][ydy] == 1))
{
// generate a child node
m0 = new node(xdx, ydy, n0->getLevel(),
n0->getPriority());
//移动到邻域时耗费
m0->nextLevel(i);
//移动到终点时耗费
//Ps,此处与参考文献中的计算方法不同,参考文献中H只计算一次存储就好,而这里每考虑一次就重新计算,速度必然慢,优化可考虑这里
m0->updatePriority(xFinish, yFinish);
// if it is not in the open list then add into that
if (open_nodes_map[xdx][ydy] == 0)
{
//若第一遍历到此
open_nodes_map[xdx][ydy] = m0->getPriority();
pq[pqi].push(*m0);
// mark its parent node direction
//更新方向记录表(参考文献是记录父指针,相应的记录空间会少,但这里也可以输出策略,可后续修改)
dir_map[xdx][ydy] = (i + dir / 2) % dir;
}
//若已经在open list中,需要考虑那种方式耗费更低
else if (open_nodes_map[xdx][ydy]>m0->getPriority())
{
//以下是当open list中耗费更低时修改父指针(异曲同工)
// update the priority info
open_nodes_map[xdx][ydy] = m0->getPriority();
// update the parent direction info
dir_map[xdx][ydy] = (i + dir / 2) % dir;
// replace the node
// by emptying one pq to the other one
// except the node to be replaced will be ignored
// and the new node will be pushed in instead
//修改后需要删除open list中相应的项,此处开辟了两个priority_queue,有两个就可以有空间操作了,其实完全可以用一个+erase完成吧,后需优化再说吧
while (!(pq[pqi].top().getxPos() == xdx &&
pq[pqi].top().getyPos() == ydy))
{
pq[1 - pqi].push(pq[pqi].top());
pq[pqi].pop();
}
pq[pqi].pop(); // remove the wanted node
//此处就是考虑了当移动后多于一般或少于一半时,通过交换指针减少操作次数
// empty the larger size pq to the smaller one
if (pq[pqi].size()>pq[1 - pqi].size()) pqi = 1 - pqi;
while (!pq[pqi].empty())
{
pq[1 - pqi].push(pq[pqi].top());
pq[pqi].pop();
}
pqi = 1 - pqi;
pq[pqi].push(*m0); // add the better node instead
}
else delete m0; // garbage collection
}
}
delete n0; // garbage collection
}
return ""; // no route found
}
int main()
{
srand(time(NULL));
// create empty map
for (int y = 0; y<m; y++)
{
for (int x = 0; x<n; x++) map[x][y] = 0;
}
// fillout the map matrix with a '+' pattern
for (int x = n / 8; x<n * 7 / 8; x++)
{
map[x][m / 2] = 1;
}
for (int y = m / 8; y<m * 7 / 8; y++)
{
map[n / 2][y] = 1;
}
// randomly select start and finish locations
int xA, yA, xB, yB;
switch (rand() % 8)
{
case 0: xA = 0; yA = 0; xB = n - 1; yB = m - 1; break;
case 1: xA = 0; yA = m - 1; xB = n - 1; yB = 0; break;
case 2: xA = n / 2 - 1; yA = m / 2 - 1; xB = n / 2 + 1; yB = m / 2 + 1; break;
case 3: xA = n / 2 - 1; yA = m / 2 + 1; xB = n / 2 + 1; yB = m / 2 - 1; break;
case 4: xA = n / 2 - 1; yA = 0; xB = n / 2 + 1; yB = m - 1; break;
case 5: xA = n / 2 + 1; yA = m - 1; xB = n / 2 - 1; yB = 0; break;
case 6: xA = 0; yA = m / 2 - 1; xB = n - 1; yB = m / 2 + 1; break;
case 7: xA = n - 1; yA = m / 2 + 1; xB = 0; yB = m / 2 - 1; break;
}
cout << "Map Size (X,Y): " << n << "," << m << endl;
cout << "Start: " << xA << "," << yA << endl;
cout << "Finish: " << xB << "," << yB << endl;
// get the route
clock_t start = clock();
string route = pathFind(xA, yA, xB, yB);
if (route == "") cout << "An empty route generated!" << endl;
clock_t end = clock();
double time_elapsed = double(end - start);
cout << "Time to calculate the route (ms): " << time_elapsed << endl;
cout << "Route:" << endl;
cout << route << endl << endl;
// follow the route on the map and display it
if (route.length()>0)
{
int j; char c;
int x = xA;
int y = yA;
//对首尾及路径标记
map[x][y] = 2;
for (int i = 0; i<route.length(); i++)
{
c = route.at(i);
j = atoi(&c);
x = x + dx[j];
y = y + dy[j];
map[x][y] = 3;
}
map[x][y] = 4;
//输出可视化美观
// display the map with the route
for (int y = 0; y<m; y++)
{
for (int x = 0; x<n; x++)
if (map[x][y] == 0)
cout << ".";
else if (map[x][y] == 1)
cout << "O"; //obstacle
else if (map[x][y] == 2)
cout << "S"; //start
else if (map[x][y] == 3)
cout << "R"; //route
else if (map[x][y] == 4)
cout << "F"; //finish
cout << endl;
}
}
getchar(); // wait for a (Enter) keypress
return(0);
}