A*算法及相关拓展算法

1.Dijkstra算法与最佳优先搜索算法


  Dijkstra算法是一种典型的单源最短路径算法,属于广度优先算法。
  Dijkstra算法从物体所在的初始点开始,访问图中的结点。它迭代检查待检查结点集中的结点,并把和该结点最靠近的尚未检查的结点加入待检查结点集。
  该结点集从初始结点向外扩展,直到到达目标结点。
  Dijkstra算法保证能找到一条从初始点到目标点的最短路径,只要所有的边都有一个非负的代价值。(我说“最短路径”是因为经常会出现许多差不多短的路径。)
 
  下图是 Dijkstra算法在无障碍情况下的搜索过程。

1.1 无障碍情况

在这里插入图片描述
  最佳优先搜索(BFS)是一种启发式的算法,通过计算任意点到目标点的代价值。能够更快的找到一条路径,但不能保证是最短路径。
  下图是无障碍情况下,越黄的结点代表越高的启发式值(移动到目标的代价高),而越黑的结点代表越低的启发式值(移动到目标的代价低)。
在这里插入图片描述

1.2 有障碍情况

这表明了与Dijkstra 算法相比,BFS运行得更快。在无障碍的情况下,可以达到非常不错的效果。但是在有障碍情况下,BFS算法虽然能找到一条有效路径,却无法保证路径最短。
在这里插入图片描述
在这里插入图片描述
BFS基于的是一种贪心策略,仅仅考虑到达目标的距离代价,而忽略了当前已花费的代价。
D算法运行速度相对较慢,但能保证找到一条最短路径。

2. A*算法

A算法正是将两个算法的特点进行融合,是一种启发式的广度搜素算法。
  把Dijkstra算法(靠近初始点的结点)和BFS算法(靠近目标点的结点)的信息块结合起来。.g(n)表示从初始结点到任意结点n的代价,h(n)表示从结点n到目标点的启发式评估代价。当从初始点向目标点移动时,A权衡这两者。每次进行主循环时,它检查f(n)最小的结点n,其中f(n) = g(n) + h(n)
  在这里插入图片描述
  在这里插入图片描述

2.1 启发h(n)
  • 一种极端情况,如果h(n)是0,则只有g(n)起作用,此时A*演变成Dijkstra算法,这保证能找到最短路径。
  • 另一种极端情况,如果h(n)比g(n)大很多,则只有h(n)起作用,A*演变成BFS算法

如何调整h(n)需要参考D算法和BSF算法的特点进行尝试。我们想最快地得到最短路径。如果我们的目标代价太低,我们仍会得到最短路径,不过速度变慢了;如果我们的目标代价太高,那我们就放弃了最短路径,但A*运行得更快。
构造精确启发函数的一种方法是预先计算任意一对结点之间最短路径的长度。如果在2D栅格地图中,直接计算任意点距目标点的实际距离值作为代价值即可得到较好的结果,如果是3D地图,需要考虑到机器人在各地形的移动能力以确定合适的目标代价值。

3. 网格地图的距离

3.1 欧式距离

在一个N维度的空间里,求两个点的距离,这个距离肯定是一个大于等于零的数字,那么这个距离需要用两个点在各自维度上的坐标相减,平方后加和再开方。
在这里插入图片描述
在这里插入图片描述

3.2 曼哈顿距离

曼哈顿距离也叫”曼哈顿街区距离”。想象你在曼哈顿街道上,从一个十字路口开车到另一个十字路口,驾驶距离就是这个“曼哈顿距离”。
在这里插入图片描述
在这里插入图片描述

3.3夹角余弦(向量部分)

也叫余弦相似度,是用向量空间中两个向量夹角的余弦值作为衡量两个个体间差异的大小的度量。如果两个向量的方向一致,即夹角接近零,那么这两个向量就越相近。要确定两个向量方向是否一致,要用到余弦定理计算向量的夹角。
在这里插入图片描述

3.4 切比雪夫距离(对角线距离)

切比雪夫距离公式简单理解为就是各坐标数值差的最大值
在这里插入图片描述
在这里插入图片描述

从实际的A*算法运行效果来看,使用曼哈顿距离作为距离测量值使得算法能有较好的效果。

4.算法实现

如果不考虑具体实现代码,A*算法是相当简单的。有两个集合,OPEN集和CLOSED集。其中OPEN集保存待考查的结点。开始时,OPEN集只包含一个元素:初始结点。CLOSED集保存已考查过的结点。开始时,CLOSED集是空的。如果绘成图,OPEN集就是被访问区域的边境(frontier)而CLOSED集则是被访问区域的内部(interior)。每个结点同时保存其父结点的指针因此我们可以知道它是如何被找到的。

在主循环中重复地从OPEN集中取出最好的结点n(f值最小的结点)并检查之。如果n是目标结点,则我们的任务完成了。否则,结点n被从OPEN集中删除并加入CLOSED集。然后检查它的邻居n’。如果邻居n’在CLOSED集中,那么它是已经被检查过的,所以我们不需要考虑它*;如果n’在OPEN集中,那么它是以后肯定会被检查的,所以我们现在不考虑它*。否则,把它加入OPEN集,把它的父结点设为n。到达n’的路径的代价g(n’),设定为g(n) + movementcost(n, n’)。

(*)这里我忽略了一个小细节。你确实需要检查结点的g值是否更小了,如果是的话,需要重新打开(re-open)它。

使用优化队列实现了A*算法,可以直接运行

#include <cstdio>
#include <iostream>
#include <queue>
#include <vector>
#include <time.h>
#include <algorithm>
using namespace std;
//地图
int raw[15][15] = {
    {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
    {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
    {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
    {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
    {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
    {1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1},
    {1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1},
    {1, 0, 0, 0, 0, 2, 0, 0, 0, 1, 0, 3, 0, 0, 1},
    {1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1},
    {1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1},
    {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
    {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
    {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
    {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
    {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}};
enum Type
{
    Road,
    Wall,
    Start,
    End
    //路,墙,起点,终点
};
int endX = 0, endY = 0;
struct node
{
    int x, y;
    int f, g, h; // f = g + h;
    int type;
    bool is_open;
    bool is_close;
    node *parent = NULL;
    void print()
    {
        printf("%d   %d: f: %d  g: %d  h: %d   type: %d\n", x, y, f, g, h, type);
    }
    bool operator<(const node &cmp) const
    {
        //这里是给优先队列排序用的,优先队列默认大的在前面,所以我们写<的时候判断用>号
        return f > cmp.f;
    }
} map[15][15];
priority_queue<node> open; //优化队列
/*判断是否为边界*/
bool inside(int x, int y, int n, int m)
{
    return x >= 0 && y >= 0 && x < n && y < m;
}
void A_()
{
    /* 遍历open队列 */
    while (!open.empty())
    {
        /* 取出队首元素进行处理 */
        node cur = open.top();
        open.pop();
        /* 将当前点加入close队列 */
        map[cur.x][cur.y].is_close = true;
        /* 访问当前点四周的8个点 */
        for (int i = -1; i <= 1; i++)
            for (int j = -1; j <= 1; j++)
            {
                if (i == j && i == 0) //i=j=0 是当前点
                    continue;
                /* 判断当前点是否为边界点 */
                if (!inside(cur.x + i, cur.y + j, 15, 15))
                    continue;
                /* 不访问open和close中的点 */
                if (map[cur.x + i][cur.y + j].is_close || map[cur.x + i][cur.y + j].is_open)
                    continue;
                /* 不访问障碍物点 */
                if (map[cur.x + i][cur.y + j].type == Wall)
                    continue;

                node &u = map[cur.x + i][cur.y + j];
                /* 设置当前点的父节点 */
                u.parent = &(map[cur.x][cur.y]);
                /* 更新到起点的距离 */
                u.g = map[cur.x][cur.y].g + ((i && j) ? 14 : 10);
                /* 曼哈顿距离更新到终点距离 */
                u.h = abs(u.x - map[endX][endY].x) + abs(u.y - map[endX][endY].y);
                /* 更新f的值 */
                u.f = u.g + u.f;
                /* 加入open表 */
                u.is_open = true;
                open.push(u);
            }
    }
}
void CreateMap()
{
    for (int i = 0; i < 15; i++)
        for (int j = 0; j < 15; j++)
        {
            map[i][j].x = i;
            map[i][j].y = j;
            map[i][j].type = raw[i][j];
            map[i][j].f = map[i][j].g = map[i][j].h = 0;
            map[i][j].is_open = map[i][j].is_close = false;
            if (map[i][j].type == Start)
            {
                /* 将初始点加入open */
                map[i][j].is_open = true;
                open.push(map[i][j]);
            }
            if (map[i][j].type == End)
            {
                endX = i;
                endY = j;
            }
        }
}
int main()
{
    /* 创建地图 */
    CreateMap();
    /* A*算法 */
    A_();
    if (map[endX][endY].is_close)
    { //从终点走回起点
        int nx = endX, ny = endY;
        while (map[nx][ny].type != Start)
        {
            node *x = map[nx][ny].parent;
            nx = x->x;
            ny = x->y;
            if (map[nx][ny].type != Start)
                g[nx][ny] = 6;
        }
    }
    /* 输出结果地图 */
    for (int i = 0; i < 15; i++)
    {
        for (int j = 0; j < 15; j++)
        {
            printf("%d ", g[i][j]);
        }
        puts("");
    }
    return 0;
}

### A*算法的二层拓展实现与优化 A*算法是一种基于启发式的最佳优先搜索策略,其核心在于通过估价函数指导搜索方向[^2]。为了进一步提升性能并减少计算开销,可以通过多层次扩展和优化手段来改进传统单层A*算法的表现。 #### 1. 双向A*算法 双向A*算法是从起点和终点分别进行搜索的一种方法。相比于传统的单向A*算法,这种方法可以显著减少搜索空间。具体而言,在每次迭代过程中,不仅从起点出发寻找目标节点,还同时从目标节点反向寻找起点。当两个方向的搜索路径相遇时,即可终止搜索过程[^4]。 ```python def bidirectional_a_star(start, goal, heuristic): open_set_start = {start} open_set_goal = {goal} g_score_start = {start: 0} g_score_goal = {goal: 0} f_score_start = {start: heuristic(start, goal)} f_score_goal = {goal: heuristic(goal, start)} while open_set_start and open_set_goal: current_start = min(open_set_start, key=lambda node: f_score_start[node]) current_goal = min(open_set_goal, key=lambda node: f_score_goal[node]) if current_start in g_score_goal or current_goal in g_score_start: return reconstruct_path(current_start, current_goal) # Expand from the start side neighbors = get_neighbors(current_start) for neighbor in neighbors: tentative_g_score = g_score_start[current_start] + distance_between(current_start, neighbor) if neighbor not in g_score_start or tentative_g_score < g_score_start[neighbor]: g_score_start[neighbor] = tentative_g_score f_score_start[neighbor] = tentative_g_score + heuristic(neighbor, goal) if neighbor not in open_set_start: open_set_start.add(neighbor) # Expand from the goal side (similar logic as above) ... return None ``` 上述代码展示了如何构建一个简单的双向A*算法框架。该方法能够有效降低搜索复杂度,并提高求解速度。 #### 2. 层次化地图表示 对于大规模环境中的路径规划问题,层次化地图表示提供了一种有效的解决方案。通过对地图分层处理,高层级负责粗粒度导航,低层级用于精确定位。这种结构允许快速排除不可能区域,从而加速全局最优路径的发现[^1]。 例如,在城市交通网络中,可先利用高速公路连接主要地点作为高层次图;再细化到街道级别完成最终定位。此技术特别适用于具有明显分区特征的大规模场景。 #### 3. 改进估价函数设计 合理选择估价函数f(n)=g(n)+h(n),其中g(n)代表实际代价,h(n)为估计剩余距离至目标点的成本。如果h(n)设置得当,则能保证找到最短路径的同时保持较高的运行效率[^3]。通常采用曼哈顿距离或者欧几里德距离作为基础估算方式,但在特定条件下也可以考虑更复杂的模型以适应特殊需求。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值