基于STM32的A*(A星)寻路算法实现

STM32 + LED点阵屏 实现寻路

算法介绍

A算法最初发表于1968年。它可以被认为是Dijkstra算法的扩展。
由于借助启发函数的引导,A
算法通常拥有更好的性能。

Dijkstra算法

在Dijkstra算法中,需要计算每一个节点距离起点的总移动代价。同时,还需要一个优先队列结构。对于所有待遍历的节点,放入优先队列中会按照代价进行排序。

在算法运行的过程中,每次都从优先队列中选出代价最小的作为下一个遍历的节点。直到到达终点为止。

在这里插入图片描述

启发式算法

与Dijkstra算法类似,我们也使用一个优先队列,但此时以每个节点到达终点的距离作为优先级,每次始终选取到终点移动代价最小(离终点最近)的节点作为下一个遍历的节点。
在这里插入图片描述
虽然启发式搜索比Dijkstra算法更快得出结果,但它所生成的路径并不是最优的,其中出现了一些绕弯路的状况。
在这里插入图片描述
一方面,我们需要算法有方向地进行扩散(启发式),另一方面我们需要得到尽可能最短的路径,因此A*就诞生了, 它结合了Dijkstra和启发式算法的优点。

A*算法

A*(A-Star)算法是一种静态路网中求解最短路径最有效的直接搜索方法。

原理

算法通过函数F = G + H 来计算每个节点的优先级
这里,
G= 从起点 A移动到当前方格的实际代价。
H = 从指定的方格移动到终点 B 的估算成本。使用 Manhattan 方法

在这里插入图片描述

步骤

  1. 把起点加入 open list 。

  2. 重复如下过程:
    a. 遍历 open list ,查找 F 值最小的节点,把它作为当前要处理的节点。
    b. 把这个节点移到 close list 。
    c. 对当前方格的 8 个相邻方格的每一个方格做如下操作
         ◆ 如果它是不可抵达的或者它在 close list 中,忽略它。否则,做如下操作。
         ◆ 如果它不在open list 中,把它加入 open list ,并且把当前方格设置为它的父亲,记录该方格的 F , G 和 H 值。
         ◆ 如果它已经在open list 中,检查这条路径 ( 即经由当前方格到达它那里 ) 是否更好,用 G 值作参考。更小的 G 值表示这是更好的路径。如果G 值更小,把它的父亲设置为当前方格,并重新计算它的 G 和 F 值。(如果你的 open list 是按 F 值排序的话,改变后你可能需要重新排序。)
    d. 停止,当你把终点加入到了 open list 中,此时路径已经找到了。或者查找终点失败,并且 open list 是空的,此时没有路径。

  3. 保存路径。从终点开始,每个方格沿着父节点移动直至起点,这就是你的路径。

代码实现

代码参考A*搜寻算法百度百科。
在这里插入图片描述
在本例中,横向和纵向的移动代价为 10 ,对角线的移动代价为 14 。以避免浮点运算

//节点属性
#define Reachable   	0		//可以到达的节点
#define Bar        		1		//障碍物
#define Pass	        2		//
#define Source         	3		//起点
#define Destination 	4		//终点

//8bits分别代表相邻8个节点是否可到达
#define North			(1 << 0)
#define North_West  	(1 << 1)
#define West			(1 << 2)
#define South_West  	(1 << 3)
#define South			(1 << 4)
#define South_East   	(1 << 5)
#define East			(1 << 6)
#define North_East   	(1 << 7)


const Point dir[8] =                	//上北下南左西右东
{
    {-1, 0},   	// North 				//向北移动即x-1
    {-1, -1},   // North_West			//向西移动即y-1
    {0, -1},   	// West
    {1, -1},  	// South_West
    {1, 0},  	// South
    {1, 1}, 	// South_East
    {0, 1},  	// East
    {-1, 1}   	// North_East
};
typedef struct		//坐标
{
    int x, y;
}Point;

typedef struct		//节点
{
    int x, y;              			//节点坐标
    uint16_t reachable;				//节点属性
    uint16_t sur;					//相邻节点的方位
    uint16_t value;
}MapNode;

typedef struct Close	//list链表成员
{
    MapNode *cur;                	//节点
    char vis;                 		//有效值,是否已被放入close list
    struct Close *from;            	//父节点 链表成员
    uint16_t F, G;
    uint16_t H;
}Close;

typedef struct 		//优先队列(Open表)
{
    int length;        				//当前队列的长度
    Close* Array[MaxLength];    	//评价结点的指针
}Open;
MapNode  graph[Height][Width];       	//地图节点
Close  close[Height][Width] = {0};       //地图成员

/* 导入一张地图,设置地图各节点属性,及其可移动方向*/
void initGraph(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;
                    }
                }
            }
        }
    }
}


//地图节点成员初始化操作
//导入起点及终点
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 = 10*abs(dx - i) + 10*abs(dy - j);    // 扩大十倍,避免浮点运算
        }
    }
    cls[sx][sy].F = cls[sx][sy].H;            //起始点评价初始值
    cls[sy][sy].G = 0;                        //移步花费代价值
    //cls[dx][dy].G = Infinity;
}


// Open表初始化
void initOpen(Open *q)    //优先队列初始化
{
    q->length = 0;        // 队内成员数初始为0
}


/*向优先队列(Open表)中添加成员
   并排序*/
void push(Open *q, Close cls[Height][Width], uint16_t x, uint16_t y, uint16_t g)			
{    
    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++)		//确认最F值最小的节点
    {
        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;    				//将F值最小节点置于表头
}

//取出Open list表中的F值最小的成员
Close* shift(Open *q)
{
    return q->Array[--q->length];
}

百度百科源代码中,对已存在Open List中的成员,并未重新判断G值。

Open q;                //Open表
Close *p;              //list表成员
int astar()
{    // A*算法遍历
    int i, curX, curY, surX, surY;    //当前节点坐标,目标节点坐标
    uint16_t surG;
  
    initOpen(&q);
    initClose(close, srcX, srcY, dstX, dstY);  //导入起点 和 终点,并将起点设置为成员
    close[srcX][srcY].vis = 1;            
    push(&q, close, srcX, srcY, 0);       //起点放入Close list,设置为不可访问
  
    while (q.length)
    {    
        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;
			
			//surG = p->G + sqrt((curX - surX) * (curX - surX) + (curY - surY) * (curY - surY));
			//surG = p->G + abs(curX - surX) + abs(curY - surY);
			//将距离扩大十倍,避免浮点运算
			if(abs(dir[i].x)>0 && abs(dir[i].y)>0)
			    surG = p->G + 14;            //1.414
			else
			    surG = p->G + 10;
			if (!close[surX][surY].vis)             //当前节点成员不在Openlist中
			{						
			    close[surX][surY].vis = 1;          //放入
			    close[surX][surY].from = p;
			    push(&q, close, surX, surY, surG);
			}
			else
			{
				if(surG < close[surX][surY].G)   //Openlist中已经存在时,如果G更小时,更新当前节点成员
				{
					close[surX][surY].vis = 1;
					close[surX][surY].from = p;    
			        push(&q, close, surX, surY, surG);
			   }
			}
        }
    }
    return NoSolution; //无结果
}

算法遍历后,根据终点节点close[dstX][dstY],遍历父节点,即可获得路径。

测试结果

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
STM32 + LED屏
地图规格40*40
红色为起点和终点 蓝色是路径 白色是墙
靠着墙不能走对角,类似象棋中的“蹩马腿”

参考链接

  • https://blog.csdn.net/hitwhylz/article/details/23089415
  • http://www.gamedev.net/reference/articles/article2003.asp
  • https://baike.baidu.com/item/A%2A%E6%90%9C%E5%AF%BB%E7%AE%97%E6%B3%95/6773036?fr=aladdin
  • https://zhuanlan.zhihu.com/p/54510444
  • https://blog.csdn.net/yuxuan20062007/article/details/80914210?utm_medium=distribute.pc_relevant.none-task-blog-title-2&spm=1001.2101.3001.4242
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值