[测试平台] Celeron 2.4GHZ + 1G 内存
[演示]
(1) 地图大小(size),假设地图都是正方形size <= 1000
(2) 障碍百分比(rate):用于设置障碍点数,障碍数=size*size*rate/100
(3) 测试软件的目标是用A*算法在地图中找到最短路径
(4) 图示中size = 1000的地图用了624ms,这是比较好的情况,如果在比较差的情况下size=1000时可能需要3s的时间;当一般情况size=300时,一般需要100ms左右的时间,应该还是属于高效的。
(5) 起点坐标(0,0),终点坐标(size-1,size-1)
(6) 地图文件map.in,寻路输出文件map.out,最好用写字版打开.
(7) 本文的目标在于介绍A*的具体实现方法,不打算介绍A*的原理,也不会对h(n)进行过多的探讨,h(n)使用最简单的dx+dy形式。从算法效率角度来说针对总体效率的把握,而没有对程序本身进行过多无谓的优化,否则如果用汇编实现一些关键运算的话效率就可以提高N多。
[寻路结果示例] size=30,rate = 60,‘.’表示可以通行,‘*’表示障碍
→↘ . * . . . * * * . * * * * * . . . . * . . * . . . * . *
. *↘ . * . . * * * . . * * * . . . . * . * . . . . . * * *
. * *↓ * * * . * . . . . * * . * . * * * . * . . . * . * .
* * .↓ . * . * . * . * * * . . . * . . . * . . * . . * . *
. . .↘ * * * * . * . * . . . * * . * . . . * . . . . * * .
. . . *↘ * . . . . * * . * . * * . * * * . . * . . . * . .
. . . . .→→→↘ * . . * * . * * . . . * * . . * . * . . *
* * . . * . . * .↘ . * . . * * * * . . * * * . . * * . * *
. . * . * . * . * *↓ . * . * * * * * . * . . . . . * * . .
. . . * . * . . * *↘ * * . * . . * * . * . . * . * . . * *
. * * * * . . . * * .↘ * * . . . . . * * * . * . . . . . .
. * * * * * . . . . * *↓ * * * * . . * * * * . . * . . * .
* . . . * . . * . * * *↘ . . * * * * . . . * . * . . . * .
. . * . . . * * * . * . *↙ * * * * * . . . * . . * . . * .
. * * * * * . . . . * *↘ * * * . * * . . . * . . * . . * .
* * . * . * * * . * * . .→→↘ * . * * . * . * * * . * . .
. . . . * * . . . * * * . * * *→↘ * * . * . * * * * . . .
* * . . . . * * * . . . * . * . . .↘ * . * . . . * * * . .
. * . * . . . . . * * . . . * . * * .→↘ * * . * . . * * *
. * * . . . . . . * * . * * * . * . . . *↓ * * . * . * . *
. * . * * * . . . * . * . * . . * . . * *↘ . * . * . * * *
. * * * . * . . * . . * . . * * * . . . . .↘ * . * * * . *
. * * * * . * * . . . . . * . . . * . * * . .↘ * . . * * *
* . . . * * * * . . . . . . . * . * . * * . * *↘ . * * * .
. * . . . . . * . * * . * . . . * * . * * * * . *↘ . * . *
* . * * * . * * . . . . . . . . * . . * . * . * . *↘ . * *
. * . . . . . * . * . * * . * * . . * . * . * . . * .↘ . *
* . . . * . . * . * * * . . * * . * . * . . . * . . * .↘ *
. . . . . . . * * . * * . * * . * * . * . * . . * * * * *↓
. . * . * . . . . . . . . . * . . . . . . * . . * * * . * .
一、A*算法实现过程中的存储结构
(1) 八个方向
int dir[8][2] = {{-1,0},{-1,1},{0,1},{1,1},{-1,-1},{0,-1},{1,-1},{1,0}} ;
char dir_ch[8][3] = { "↑","↗","→","↘","↖","←","↙","↓" } ;
这样存储8个方向的好处是,当知道AàB的方向为x时,就可以直接得到BàA的方向为(7-x)
(2) 地图储存结构MAP
typedef struct _MAP {
int type ; // 0:可行 1:障碍
int status ; // 0:未访问1:OPEN 2:CLOSE
int dir_parent ; // 指向前一结点dir ( 0—7)
int g ; // g(n),起点到当前结点的代价
int h ; // h(n),当前结点到终点的估价值
int index ; // 在heap中的结点索引号
} MAP ;
着重介绍2个参数:
<1>status:当前结点的状态,由于这个参数的存在可以节约CLOSE列表
<2>index:A*中需要对结点进行不断的更新,这个参数可以直接定位到当前结点在OPEN队列中的位置,不需要额外的查找。
(3) OPEN列表的结点
typedef struct _NODE {
int x, y ; // 结点坐标
int value ; // f = g + h
} NODE, *PNODE ;
二、A*算法实现
{
int lx = CUR_LEN, ly = CUR_LEN ;
int nStartX = 0, nStartY = 0 ;
int nEndX = lx-1, nEndY = ly-1 ;
OPEN.BHAddNew ( nStartX, nStartY, 3 ) ;
map[nStartX][nStartY].status = 1 ;
map[nStartX][nStartY].index = 0 ;
while ( OPEN.BHGetNum() > 0 )
{
NODE TopNode = OPEN.BHGetTop () ;
// 如果堆顶元素为终点时,直接返回true
if ( TopNode.x == nEndX && TopNode.y == nEndY )
return true ;
// 修改结点状态,OPEN-->CLOSE
map[TopNode.x][TopNode.y].status = 2 ;
OPEN.BHDelTop () ;
for ( int i = 0; i < 8; i++ )
{
int a = TopNode.x + dir[i][0 ] ;
int b = TopNode.y + dir[i][1 ] ;
// 检测坐标是否有效,且该结点是否为障碍
if ( IsValidPos(a,b) == false || map[a][b].type == 0 )
continue ;
// 计算当前消费g(n)
int cur_cost = map[TopNode.x][TopNode.y].g + cost[i] ;
// 若该结点首次被访问
if ( map[a][b].status == 0 )
{
map[a][b].status = 1 ;
map[a][b].dir_parent = 7 - i ;
map[a][b].g = cur_cost ;
map[a][b].h = ( nEndX - a ) + ( nEndY - b ) ; // 只在首次访问时估价h(n)
OPEN.BHAddNew ( a, b, map[a][b].g + map[a][b].h ) ;
}
// 如果该结点需要更新
else if ( cur_cost < map[a][b].g )
{
map[a][b].dir_parent = 7- i ;
map[a][b].g = cur_cost ;
// 如果该结点已经在OPEN,那么只需要BinaryHeap进行修改操作
if ( map[a][b].status == 1 )
{
OPEN.BHAdjustByIndex ( map[a][b].index, map[a][b].g + map[a][b].h ) ;
}
// 如果该结点在CLOSE中,那么只需要BinaryHeap进行添加操作
else if ( map[a][b].status == 2 )
{
OPEN.BHAddNew ( a, b, map[a][b].g + map[a][b].h ) ;
}
map[a][b].status = 1 ;
}
} // for
} // while
return false ;
}
三、二插堆(Binary Heap)在A*中的应用
用BinaryHeap的来实现OPEN,有这么几个优点:
(1) 快速定位OPEN中最小元素,O(1)
(2) 能够实现对结点的快速插入/删除/修改,O(long(n))
但BinaryHeap本身对结点的修改操作支持不好,因为除了已知堆顶元素最小/大之外,对其他元素一无所知,因而一般情况下非暴力搜索不可行。在地图存储结构MAP中有index项,就可以直接定位当前结点在OPEN中的位置。
BinaryHeap的3种操作方式:插入/删除/修改
{
NODE node ( x, y, value ) ;
this -> pHeap[ this -> count ++ ] = node ;
this -> BHFilterUp ( this -> count - 1 ) ;
return true ;
}
bool BHDelTop ()
{
if ( this -> count <= 0 )
return false ;
this -> count -- ;
this -> pHeap[ 0 ] = this -> pHeap[ this -> count] ;
this -> BHFilterDown ( 0 ) ;
return true ;
}
bool BHAdjustByIndex ( int index, int nNewValue )
{
this -> pHeap[index].value = nNewValue ;
if ( this -> BHFilterDown ( index ) == index )
this -> BHFilterUp ( index ) ;
return true ;
}
BinaryHeap的2种更新方式:向下/向上更新
{
NODE TempNode = this-> pHeap[index] ;
int cur = index ;
int son = 2 * cur + 1 ;
while ( son < this-> count )
{
if ( son+1 < this->count && this->pHeap[son].value > this->pHeap[son+1 ].value )
son++ ;
if ( TempNode.value < this-> pHeap[son].value )
break ;
else
{
this->pHeap[cur] = this-> pHeap[son] ;
this->BHUpdatePos ( this->pHeap[son].x, this-> pHeap[son].y, cur ) ;
cur = son ;
son = 2 * cur + 1 ;
}
}
this->pHeap[cur] = TempNode ;
this-> BHUpdatePos ( TempNode.x, TempNode.y, son ) ;
return son ;
}
int BHFilterUp ( int index )
{
NODE TempNode = this-> pHeap[index] ;
int cur = index ;
int parent = ( cur - 1 ) / 2 ;
while ( cur != 0 )
{
if ( this->pHeap[parent].value <= TempNode.value )
break ;
this->pHeap[cur] = this-> pHeap[parent] ;
this->BHUpdatePos ( this->pHeap[parent].x, this-> pHeap[parent].y, cur ) ;
cur = parent ;
parent = ( cur - 1 ) / 2 ;
}
this->pHeap[cur] = TempNode ;
this-> BHUpdatePos ( TempNode.x, TempNode.y, cur ) ;
return cur ;
}
当node在堆中改变位置时需要修改MAP中所对应[node.x,node.y]中的index
void BHUpdatePos ( int x, int y, int pos )
{
map[x][y].index = pos ;
}