【网络资料】Astar算法详解
关于A*算法,很早就想写点什么,可是貌似天天在忙活着什么,可事实又没有做什么,真是浮躁啊!所以今晚还是来写一下总结吧!
A*算法是很经典的只能启发式搜索算法,关于只能搜索算法和一般的搜索算法(例如DFS,BFS之类),在语言描述上的区别,我觉得用《代码大全》中的一句话描述的非常好:“驾驶汽车达到某人家,写成算法是:沿167号高速往南行至Puyallup,从XX出口后往山上开4.5英里,在一个杂货店旁边的红绿灯右转,接着在第一个路口左转,从左边的褐色大房子的车道进去就是;而启发式是:找出上一次我们给你寄的信,照着信上地址开车到这个镇,到了那里你问一下我们房子在那里,这里的每一个人都认识我们,肯定会有人愿意帮助你,如果你找不到人,就找一个电话亭打电话给我们,我们出来接你”。听上去还是有点抽象。那么下面我们具体看看A*算法!
注:本文的图形以及一些思想借助于http://www.policyalmanac.org/games/aStarTutorial.htm,非常感谢经典文 章!
我们要想从绿色的点到红色的点,需要启发式得去找一条路径,到底怎么去找呢,开始没有办法,只能从开始点慢慢尝试!我们需要定义一个OPEN表,OPEN表中放的是什么呢?就是当前考虑的点,及其周边的点需要添加进来,作为可能的路径上的点。这样说可能有点抽象,那么先看下面:
我们从起始点开始搜索:
1:从点A开始,并且把它作为待处理点存入一个OPEN表。你的路径可能会通过它包含的方格,也可能不会。基本上,这是一个待检查方格的列表。
2:寻找起点周围(邻居)所有可到达或者可通过的方格,如果有障碍物方格。那么就不需要考虑他们,对于其他的吕剧节点也把他们加入OPEN表。这些邻居节点认当前点为父节点。(父节点的保存是必须的!因为当我们找到最后一个点的时候,需要通过父节点回溯,找到所有点直到开始节点!那么就是一个完整的路径!)
3:从开启列表中删除点A,把它加入到一个CLOSE列表,列表中保存所有不需要再次检查的方格。
在这一点,你应该形成如图的结构。在图中,暗绿色方格是你起始方格的中心。它被用浅蓝色描边,以表示它被加入到关闭列表中了。所有的相邻格现在都在开启列表中,它们被用浅绿色描边。每个方格都有一个灰色指针反指他们的父方格,也就是开始的方格。
结下来我们应该选择哪一个点继续考虑呢!我想大家应该知道所谓的启发函数,所谓权值之类(此处所谓的权值就是路劲的长度)。YES,我们需要OPEN表中权值F最小的那个点!为什么呢,当然是权值越小,越靠近目标点咯!
对于权值我们设为F,那么F怎么计算到的!我们有两个项!G和H,
G = 从起点A,沿着产生的路径,移动到网格上指定方格的路径耗费。
H = 从网格上那个方格移动到终点B的预估移动耗费。这经常被称为启发式的。这样叫的原因是因为它只是个猜测。我们没办法事先知道路径的长度。(但是我们需要知道:虽然只是猜测,但是只要是基于一个统一的标准,相对远近的趋势是不变的!这一点是很重要的! )
例如:H值的估计采用“曼哈顿”法,也就是当前的点,到目标点,横向和纵向的格子数相加,就是H值!
( 注意:对于障碍物我们是不考虑是否跳过的问题!即,障碍物也当做正常的一个格子!这也是H值仅仅是预测的而已的原因!即所谓启发式! )
那么对于第一幅图,起始点离终点的H值是:横向相差4个格子,纵向相差0个格子,那么H=4+0=4;
当然也有其他的办法,例如使用直线距离,sqrt( pow( des.x - src.x ) + pow( des.y - src.y ) )也是可以的~
对于G值!在这个例子,令水平或者垂直移动的耗费为10,对角线方向耗费为14。我们取这些值是因为沿对角线的距离是沿水平或垂直移动耗费的的根号2约1.414倍。为了简化,我们用10和14近似。有时候简化是很重要的~
( 其实距离只要反应了基本的倍数关系就可以了! )
对于起始点以及周围点的G值,H值和F值,下图可以很清晰的看到!( 左上角是F,左下角是G,右下角是H )
对于G值,只要是横向和竖向的邻居,设为+10,斜向的邻居+14就可以了~计算真的很easy吧~呵呵~
对于H值,就是数格子就是咯~
F = G + H
注意上面的邻居节点都加入OPEN表了哦~~~ 起点从OPEN中删除,加入CLOSE中~
接着计算:
然后从OPEN表中选择一个F值最小的然后考虑其周围邻居,再循环处理!直到终点加入CLOSE中,寻路结束!或者OPEN空了,没找到路径!
( 至于我们怎么选出最小的那个点呢!?我们使用堆排序处理的,对于选出最小值是很快的~ )
可以看到,F最小的是起始点右边的那个点,下面框框表示的~
然后再考虑其邻居:
此时对于其邻居有几种可能性!
1:邻居已经在CLOSE表中了,那么不需要考虑了~
2:邻居是障碍物,不需要考虑了e
3:邻居不在OPEN表中,那么将邻居加入OPEN,并将次邻居的父节点赋值为当前节点
4:邻居在OPEN中,那么需要看看此邻居点的G值与当前点的(G值+当前点到邻居点的距离(10或者14))的大小,如果从此点走更近(即G值更小),那么将此点的父节点换成当前点!(注意:对于同一个点,G值下,那么F必须小!因为H是相同的!)
下面是进一步的图示:
那么我们按照上面的思路,知道终点加入CLOSE表!( 终极图示如下 )
最终我们可以得到路径:( 之前我们说过,由父节点往前回溯就很easy的得到路径了~ )
说了这么多,也不知道说的行不行,还是需要总结一下!
总结:
1:将起始点加入OPEN表中
2:循环直到OPEN为空或者终点加入CLOSE表中
否则:找到OPEN表中F值最小的节点(使用堆排序得到小值点),将此点从OPEN删除,加入CLOSE!
(此时最小点已经取出,那么需要从新排序OPEN表,是的第一个点是最小F的点!)
对8个邻居进行处理:
若:1:邻居已经在CLOSE表中了,那么不需要考虑了~
2:邻居是障碍物,不需要考虑了e
3:邻居不在OPEN表中,那么将邻居加入OPEN,并将次邻居的父节点赋值为当前节点
4:邻居在OPEN中,那么需要看看此邻居点的G值与当前点的(G值+当前点到邻居点的距
离 (10或者14))的大小,如果从此点走更近(即G值更小),那么将此点的父节点换成当前 点! (注意:对于同一个点,G值下,那么F必须小!因为H是相同的!)
注意:当父节点改变后,OPEN表中的最小点可能会变化,那么需要再次排序得到最
小点!
3:结束,根据退出循环的条件可以知道到底有没有找到路径!所以下面的工作就easy了~
基本的原理就是这样的~
下面给一个简单的C语言的演示代码,只是为了演示原理,没有注重其他问题,所以大家莫怪啊~
注意:数组中1代表起点,2代表终点,0代表可以通过,3代表障碍物
#include <stdio.h>
#include <stdlib.h> #define STARTNODE 1
#define ENDNODE 2
#define BARRIER 3 typedef struct AStarNode
{
int s_x; // 坐标(最终输出路径需要)
int s_y;
int s_g; // 起点到此点的距离( 由g和h可以得到f,此处f省略,f=g+h )
int s_h; // 启发函数预测的此点到终点的距离
int s_style;// 结点类型:起始点,终点,障碍物
struct AStarNode * s_parent; // 父节点
int s_is_in_closetable; // 是否在close表中
int s_is_in_opentable; // 是否在open表中
}AStarNode, *pAStarNode; AStarNode map_maze[][]; // 结点数组
pAStarNode open_table[]; // open表
pAStarNode close_table[]; // close表
int<span style="white-space:pre"> </span> open_node_count; <span style="white-space:pre"> </span>// open表中节点数量
int close_node_count; <span style="white-space:pre"> </span>// close表中结点数量
pAStarNode path_stack[]; // 保存路径的栈
int top = -; // 栈顶 // 交换两个元素
//
void swap( int idx1, int idx2 )
{
pAStarNode tmp = open_table[idx1];
open_table[idx1] = open_table[idx2];
open_table[idx2] = tmp;
} // 堆调整
//
void adjust_heap( int /*i*/nIndex )
{
int curr = nIndex;
int child = curr * + ; // 得到左孩子idx( 下标从0开始,所有做孩子是curr*2+1 )
int parent = ( curr - ) / ; // 得到双亲idx if (nIndex < || nIndex >= open_node_count)
{
return;
} // 往下调整( 要比较左右孩子和cuur parent )
//
while ( child < open_node_count )
{
// 小根堆是双亲值小于孩子值
//
if ( child + < open_node_count && open_table[child]->s_g + open_table[child]->s_h > open_table[child+]->s_g + open_table[child+]->s_h )
{
++child;<span style="white-space:pre"> </span>// 判断左右孩子大小
} if (open_table[curr]->s_g + open_table[curr]->s_h <= open_table[child]->s_g + open_table[child]->s_h)
{
break;
}
else
{
swap( child, curr ); // 交换节点
curr = child; // 再判断当前孩子节点
child = curr * + ; // 再判断左孩子
}
} if (curr != nIndex)
{
return;
} // 往上调整( 只需要比较cuur child和parent )
//
while (curr != )
{
if (open_table[curr]->s_g + open_table[curr]->s_h >= open_table[parent]->s_g + open_table[parent]->s_h)
{
break;
}
else
{
swap( curr, parent );
curr = parent;
parent = (curr-)/;
}
}
} // 判断邻居点是否可以进入open表
//
void insert_to_opentable( int x, int y, pAStarNode curr_node, pAStarNode end_node, int w )
{
int i; if ( map_maze[x][y].s_style != BARRIER ) // 不是障碍物
{
if ( !map_maze[x][y].s_is_in_closetable ) // 不在闭表中
{
if ( map_maze[x][y].s_is_in_opentable ) // 在open表中
{
// 需要判断是否是一条更优化的路径
//
if ( map_maze[x][y].s_g > curr_node->s_g + w ) // 如果更优化
{
map_maze[x][y].s_g = curr_node->s_g + w;
map_maze[x][y].s_parent = curr_node; for ( i = ; i < open_node_count; ++i )
{
if ( open_table[i]->s_x == map_maze[x][y].s_x && open_table[i]->s_y == map_maze[x][y].s_y )
{
break;
}
} adjust_heap( i ); // 下面调整点
}
}
else // 不在open中
{
map_maze[x][y].s_g = curr_node->s_g + w;
map_maze[x][y].s_h = abs(end_node->s_x - x ) + abs(end_node->s_y - y);
map_maze[x][y].s_parent = curr_node;
map_maze[x][y].s_is_in_opentable = ;
open_table[open_node_count++] = &(map_maze[x][y]);
}
}
}
} // 查找邻居
// 对上下左右8个邻居进行查找
//
void get_neighbors( pAStarNode curr_node, pAStarNode end_node )
{
int x = curr_node->s_x;
int y = curr_node->s_y; // 下面对于8个邻居进行处理!
//
if ( ( x + ) >= && ( x + ) < && y >= && y < )
{
insert_to_opentable( x+, y, curr_node, end_node, );
} if ( ( x - ) >= && ( x - ) < && y >= && y < )
{
insert_to_opentable( x-, y, curr_node, end_node, );
} if ( x >= && x < && ( y + ) >= && ( y + ) < )
{
insert_to_opentable( x, y+, curr_node, end_node, );
} if ( x >= && x < && ( y - ) >= && ( y - ) < )
{
insert_to_opentable( x, y-, curr_node, end_node, );
} if ( ( x + ) >= && ( x + ) < && ( y + ) >= && ( y + ) < )
{
insert_to_opentable( x+, y+, curr_node, end_node, );
} if ( ( x + ) >= && ( x + ) < && ( y - ) >= && ( y - ) < )
{
insert_to_opentable( x+, y-, curr_node, end_node, );
} if ( ( x - ) >= && ( x - ) < && ( y + ) >= && ( y + ) < )
{
insert_to_opentable( x-, y+, curr_node, end_node, );
} if ( ( x - ) >= && ( x - ) < && ( y - ) >= && ( y - ) < )
{
insert_to_opentable( x-, y-, curr_node, end_node, );
}
} int main()
{
// 地图数组的定义
//
AStarNode *start_node; // 起始点
AStarNode *end_node; // 结束点
AStarNode *curr_node; // 当前点
int is_found; // 是否找到路径
int maze[][] ={ // 仅仅为了好赋值给map_maze
{ ,,,,,,,,, },
{ ,,,,,,,,, },
{ ,,,,,,,,, },
{ ,,,,,,,,, },
{ ,,,,,,,,, },
{ ,,,,,,,,, },
{ ,,,,,,,,, },
{ ,,,,,,,,, },
{ ,,,,,,,,, },
{ ,,,,,,,,, },
};
int i,j,x; // 下面准备点
//
for( i = ; i < ; ++i )
{
for ( j = ; j < ; ++j )
{
map_maze[i][j].s_g = ;
map_maze[i][j].s_h = ;
map_maze[i][j].s_is_in_closetable = ;
map_maze[i][j].s_is_in_opentable = ;
map_maze[i][j].s_style = maze[i][j];
map_maze[i][j].s_x = i;
map_maze[i][j].s_y = j;
map_maze[i][j].s_parent = NULL; if ( map_maze[i][j].s_style == STARTNODE ) // 起点
{
start_node = &(map_maze[i][j]);
}
else if( map_maze[i][j].s_style == ENDNODE ) // 终点
{
end_node = &(map_maze[i][j]);
} printf("%d ", maze[i][j]);
} printf("\n");
} // 下面使用A*算法得到路径
//
open_table[open_node_count++] = start_node; // 起始点加入open表 start_node->s_is_in_opentable = ; // 加入open表
start_node->s_g = ;
start_node->s_h = abs(end_node->s_x - start_node->s_x) + abs(end_node->s_y - start_node->s_y);
start_node->s_parent = NULL; if ( start_node->s_x == end_node->s_x && start_node->s_y == end_node->s_y )
{
printf("起点==终点!\n");
return ;
} is_found = ; while( )
{
// for test
//
/* for ( x = 0; x < open_node_count; ++x )
{
printf("(%d,%d):%d ", open_table[x]->s_x, open_table[x]->s_y, open_table[x]->s_g+open_table[x]->s_h);
}
printf("\n\n");
*/
curr_node = open_table[]; // open表的第一个点一定是f值最小的点(通过堆排序得到的)
open_table[] = open_table[--open_node_count]; // 最后一个点放到第一个点,然后进行堆调整
adjust_heap( ); // 调整堆 close_table[close_node_count++] = curr_node; // 当前点加入close表
curr_node->s_is_in_closetable = ; // 已经在close表中了 if ( curr_node->s_x == end_node->s_x && curr_node->s_y == end_node->s_y )// 终点在close中,结束
{
is_found = ;
break;
} get_neighbors( curr_node, end_node ); // 对邻居的处理 if ( open_node_count == ) // 没有路径到达
{
is_found = ;
break;
}
} if ( is_found )
{
curr_node = end_node; while( curr_node )
{
path_stack[++top] = curr_node;
curr_node = curr_node->s_parent;
} while( top >= ) // 下面是输出路径看看~
{
if ( top > )
{
printf("(%d,%d)-->", path_stack[top]->s_x, path_stack[top--]->s_y);
}
else
{
printf("(%d,%d)", path_stack[top]->s_x, path_stack[top--]->s_y);
}
}
}
else
{
printf("么有找到路径");
} puts(""); return ;
}
【网络资料】Astar算法详解的更多相关文章
- [转] KMP算法详解
转载自:http://www.matrix67.com/blog/archives/115 KMP算法详解 如果机房马上要关门了,或者你急着要和MM约会,请直接跳到第六个自然段. 我们这里说的K ...
- KMP算法详解(转自中学生OI写的。。ORZ!)
KMP算法详解 如果机房马上要关门了,或者你急着要和MM约会,请直接跳到第六个自然段. 我们这里说的KMP不是拿来放电影的(虽然我很喜欢这个软件),而是一种算法.KMP算法是拿来处理字符串匹配的.换句 ...
- Tarjan算法详解
Tarjan算法详解 今天偶然发现了这个算法,看了好久,终于明白了一些表层的知识....在这里和大家分享一下... Tarjan算法是一个求解极大强联通子图的算法,相信这些东西大家都在网络上百度过了, ...
- [Spark内核] 第36课:TaskScheduler内幕天机解密:Spark shell案例运行日志详解、TaskScheduler和SchedulerBackend、FIFO与FAIR、Task运行时本地性算法详解等
本課主題 通过 Spark-shell 窥探程序运行时的状况 TaskScheduler 与 SchedulerBackend 之间的关系 FIFO 与 FAIR 两种调度模式彻底解密 Task 数据 ...
- 第三十一节,目标检测算法之 Faster R-CNN算法详解
Ren, Shaoqing, et al. “Faster R-CNN: Towards real-time object detection with region proposal network ...
- 第三十节,目标检测算法之Fast R-CNN算法详解
Girshick, Ross. “Fast r-cnn.” Proceedings of the IEEE International Conference on Computer Vision. 2 ...
- 第二十九节,目标检测算法之R-CNN算法详解
Girshick, Ross, et al. “Rich feature hierarchies for accurate object detection and semantic segmenta ...
- fabric网络环境启动过程详解
这篇文章对fabric的网络环境启动过程进行讲解,也就是我们上节讲到的启动测试fabric网络环境时运行network_setup.sh这个文件的执行流程 fabric网络环境启动过程详解 上一节我们 ...
- Java网络编程和NIO详解开篇:Java网络编程基础
Java网络编程和NIO详解开篇:Java网络编程基础 计算机网络编程基础 转自:https://mp.weixin.qq.com/s/XXMz5uAFSsPdg38bth2jAA 我们是幸运的,因为 ...
随机推荐
- BZOJ 3942: [Usaco2015 Feb]Censoring
Description 有两个字符串,每次用一个中取出下一位,放在一个字符串中,如果当前字符串的后缀是另一个字符串就删除. Sol KMP+栈. 用一个栈来维护新加的字符串就可以了.. 一开始我非常的 ...
- Python文件操作题
1.如何用Python删除一个文件 os.remove(filename)或者os.unlink(filename). 2.Python如何copy一个文件 shutil模块里有一个copyfile函 ...
- 【Java】嵌套For循环性能优化案例
参考资料:http://cgs1999.iteye.com/blog/1596671 1 案例描述 某日,在JavaEye上看到一道面试题,题目是这样的:请对以下的代码进行优化 for (int i ...
- listener.ora/sqlnet.ora/tnsnames.ora配置文件详解
oracle网络配置 三个配置文件 listener.ora.sqlnet.ora.tnsnames.ora ,都是放在$ORACLE_HOME/network/admin目录下. 英文说明: The ...
- ACM/ICPC 之 Bellman Ford练习题(ZOJ1791(POJ1613))
这道题稍复杂一些,需要掌握字符串输入的处理+限制了可以行走的时间. ZOJ1791(POJ1613)-Cave Raider //限制行走时间的最短路 //POJ1613-ZOJ1791 //Time ...
- 【Git】笔记5 分支管理2
来源:廖雪峰 通常,合并分支时,如果可能,Git会用Fast forward模式,但这种模式下,删除分支后,会丢掉分支信息. 如果要强制禁用Fast forward模式,Git就会在merge时生成一 ...
- Java抽象类接口、内部类题库
一. 选择题 1. Person类和Test类的代码如下所示,则代码中的错误语句是( C ).(选择一项) public class Person { public String nam ...
- 单击双击手势(UITapGestureRecognizer)
- (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typica ...
- [Android Pro] Android 4.1 使用 Accessibility实现免Root自动批量安装功能
reference to : http://www.infoq.com/cn/articles/android-accessibility-installing?utm_campaign=info ...
- 多线程编程1 - NSThread
每个iOS应用程序都有个专门用来更新显示UI界面.处理用户的触摸事件的主线程,因此不能将其他太耗时的操作放在主线程中执行,不然会造成主线程堵塞(出现卡机现象),带来极坏的用户体验.一般的解决方案就是将 ...