A星寻路算法详解(C++实现 完整代码+图片演示 )

article/2025/10/2 22:01:39

文章目录

    • 三种寻路算法
  • A星寻路算法
    • A星寻路算法思想
    • A星寻路准备
    • A星寻路过程(图例)
    • A星寻路代码(完整)

三种寻路算法

  • 深度寻路算法:不一定能找到最佳路径,但是寻路快速,只能走直线。
  • 广度寻路算法:一定能找到最短路径,但是开销大,时间慢,只能走直线。
  • A星寻路算法(常用):一定能找到最短路径,可以走直线和斜线,而且开销较小,常用于大型地图的寻路

A星寻路算法

A星寻路算法思想

引入: 狼吃羊模型。

狼捕猎羊:如果抓到了就加100分;如果狼不动,每分钟减2分;如果狼抓捕时会跑,跑步每分钟减5分;

​ 狼会饿 ,饿的时候每分钟减10分。 有一个积分的概念在这里面。结果会发现狼会站在原地不动

​ 因为狼直到,抓住羊很困难,跑步时会扣分,饿时会扣分,不动时也会扣分。但是人工智能狼计算出了站着不动时扣分的代价最低,而干其他事代价都高,因此狼会自动选择代价最低的方式,一动不动

​ 之后又加了设定:原地不动每分钟也扣分,而且是线性扣分。结果你会发现狼从一开始就会自杀

同理,自杀是代价最小的选择(即分数最高,如果你干其他的事,则可能会负分,所以狼会选择自杀)。

A星寻路算法也引入了这一概念,即通过计算和量化行走的各个方向的代价,来选择最优路径

  • 公式: f = g + h
  • f: 设定其为最终评估代价
  • g:当前点走到下一点的付出的代价
  • h:当前点到终点的预期代价
  • 通过比较各条路线的最终代价,选择最小代价,即为合适的路径,也为最短路径

A星寻路准备

地图行列数,方向枚举,地图,辅助地图的设计等在此不描述,具体请看之前我写的前两种寻路算法的博客。
广度寻路算法
深度寻路算法

  • 记录坐标点的类型,GetH和GetF函数即为计算各种代价的函数,稍后会介绍。一个重载用来比较当前点是否到达终点

h表示当前点到终点的预期代价,因此我们每次移动一步,都需要求出 h,而h的计算我们可以直接通过数格子来获得,即水平,竖直个有几个格子,这便是预期的代价
g表示走到每一点的代价,因此每走一个方向,记录这个方向的代价, 最后选择代价最小的方向即可,g可以通过遍历八个方向来记录
f =g + h

//点类型
struct Mypoint
{int row;int col;int f, g, h;bool operator==(const Mypoint& pos){return (pos.row == row && pos.col == col);}void GetH(const Mypoint& Begpos, const Mypoint& Endpos){int x = abs(Begpos.col - Endpos.col);//计算水平差距int y = abs(Begpos.row - Endpos.row);//计算垂直差距h = x + y;//计算总的差距}inline void GetF(){f = g + h;//计算f}
};
  • 存储位置节点的树结构,含有构造函数用来构建树节点,vector数组存储多个节点:因为一个父亲会有多个孩子的情况。
//树结构存储节点
struct TreeNode
{Mypoint pos;//当前点坐标TreeNode* pParent;//当前点的父节点vector<TreeNode*> pChild;	//存储当前点的所有孩子节点//构造函数TreeNode(const Mypoint& pos){this->pos = pos;pParent = nullptr;}
};
  • 判断是否能走的函数,用于判断地图某个点是否能走,即不为墙,没越界,没走过,则能走。
//判断某个点能否走
bool CanWalk(int map[ROW][COL], bool vis[ROW][COL], const Mypoint& pos)
{//如果越界,不能走if (pos.row < 0 || pos.col < 0 || pos.row >= ROW || pos.col >= COL){return false;}//如果是墙,不能走if (map[pos.row][pos.col]){return false;}//如果已经走过,不能走if (vis[pos.row][pos.col]){return false;}return true;//否则能走
}
  • 数据的准备
  1. 起点与终点的坐标
  2. 树根节点,用于保存寻路的树结构
  3. buff数组来记录每一个孩子节点,用来确定下一步该走的点
  4. vis标记数组,不能重复走
  5. 当前点与试探点
void init()
{//地图,1表示墙,0表示路径int map[ROW][COL] ={{0,0,0,0,1,0,0,0,0,0},{0,0,0,0,1,0,0,0,0,0},{0,0,0,1,1,0,1,0,0,0},{0,0,0,0,1,0,1,0,0,0},{0,0,0,0,1,0,1,0,0,0},{0,0,1,0,1,0,0,0,0,0},{0,0,0,0,1,0,0,0,0,0},{0,0,0,0,0,0,1,0,0,0},{0,0,0,0,1,1,0,0,0,0},{0,0,0,0,1,0,0,0,0,0},};//起始点和终点Mypoint Begpos = { 1,1 };Mypoint Endpos = { 6,5 };//标记有没有走过bool vis[ROW][COL] = { false };//创建树根,即根节点TreeNode* pRoot = new TreeNode(Begpos);vector<TreeNode*> buff;	//存储孩子节点的数组TreeNode* pCurrent = pRoot;	//记录当前点TreeNode* pTemp = nullptr;	//试探节点,用于试探下一个位置的点bool isFindEnd = false;//终点标记
}

A星寻路过程(图例)

假定直着走的代价为10,斜着走的代价为14

  • 首先计算起点位置周围八个方向付出代价(蓝色),此代价为付出的代价 g
    在这里插入图片描述

  • 然后再计算起点到终点的代价(如何计算:数格子即可,某个点到终点的格子数,只能行列,不能斜着),此代价为预期代价h,可以发现 最终代价=付出+预期,可以得到一个最小的代价点,即右下角的斜着的点

    这个点即是我们下一步要走的点依次类推,在下个点上,再次计算周围代价最小的点,然后再次移动


upd: 2023. 2.22 新增一个图
在这里插入图片描述


  • 注意:标记起始点和每个移动到的点为已经走过点,即下一次不会重复移动到这个点。

  • 在移动到的点处(代价最小点),继续遍历八个方向,除了墙壁已经走过点,继续计算最终代价,找到最终代价小的点,移动。
    在这里插入图片描述

  • 注意:如果你移动到了一个死胡同,则必须回退,如何回退?
    我们事先准备了一个容器vector名字叫做 buff ,来存储我们每次遍历的方向的节点,即我们把每一个方向都创建一个节点,然后节点入树节点再入容器,当我们走到死胡同时,通过找到容器内的最小元素(即是代价最小点,但是这个点是死胡同),然后把他删除,则 再次找一个代价最小点然后移动到它那里去
    如果地图没有终点,则可以想到,容器会一直删除,然后为空,此时则退出,没有终点。


A星寻路代码(完整)

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;const int ROW = 10;
const int COL = 10;
const int ZXDJ = 10;	//直线代价
const int XXDJ = 14;	//斜线代价
enum Dir { p_up, p_down, p_left, p_right, p_lup, p_ldown, p_rup, p_rdown };
struct Mypoint
{int row;int col;int f, g, h;bool operator==(const Mypoint& pos){return (pos.row == row && pos.col == col);}void GetH(const Mypoint& Begpos, const Mypoint& Endpos){int x = abs(Begpos.col - Endpos.col);//计算水平差距int y = abs(Begpos.row - Endpos.row);//计算垂直差距h = x + y;//计算总的差距}inline void GetF(){f = g + h;//计算f}
};//树结构存储节点
struct TreeNode
{Mypoint pos;//当前点坐标TreeNode* pParent;//当前点的父节点vector<TreeNode*> pChild;	//存储当前点的所有孩子节点//构造函数TreeNode(const Mypoint& pos){this->pos = pos;pParent = nullptr;}
};//判断某个点能否走
bool CanWalk(int map[ROW][COL], bool vis[ROW][COL], const Mypoint& pos)
{//如果越界,不能走if (pos.row < 0 || pos.col < 0 || pos.row >= ROW || pos.col >= COL){return false;}//如果是墙,不能走if (map[pos.row][pos.col]){return false;}//如果已经走过,不能走if (vis[pos.row][pos.col]){return false;}return true;//否则能走
}int main()
{//地图,1表示墙,0表示路径int map[ROW][COL] ={{0,0,0,0,1,0,0,0,0,0},{0,0,0,0,1,0,0,0,0,0},{0,0,0,1,1,0,1,0,0,0},{0,0,0,0,1,0,1,0,0,0},{0,0,0,0,1,0,1,0,0,0},{0,0,1,0,1,0,0,0,0,0},{0,0,0,0,1,0,0,0,0,0},{0,0,0,0,0,0,1,0,0,0},{0,0,0,0,1,1,0,0,0,0},{0,0,0,0,1,0,0,0,0,0},};//起始点和终点Mypoint Begpos = { 1,1 };Mypoint Endpos = { 6,5 };//标记有没有走过bool vis[ROW][COL] = { false };//创建树根,即根节点TreeNode* pRoot = new TreeNode(Begpos);vector<TreeNode*> buff;	//存储孩子节点的数组TreeNode* pCurrent = pRoot;	//记录当前点TreeNode* pTemp = nullptr;	//试探节点,用于试探下一个位置的点bool isFindEnd = false;//终点标记//开始寻路while (1){//1. 某个点八个方向依次遍历 计算g代价for (int i = 0; i < 8; ++i){//确定试探点的属性pTemp = new TreeNode(pCurrent->pos);//八个方向进行试探!switch (i){//直线代价case p_up://上pTemp->pos.row--;pTemp->pos.g += ZXDJ;break;case p_down://下pTemp->pos.row++;pTemp->pos.g += ZXDJ;break;case p_left://左pTemp->pos.col--;pTemp->pos.g += ZXDJ;break;case p_right://右pTemp->pos.col++;pTemp->pos.g += ZXDJ;break;//斜线代价case p_lup://左上pTemp->pos.row--;pTemp->pos.col--;pTemp->pos.g += XXDJ;break;case p_ldown://左下pTemp->pos.row++;pTemp->pos.col--;pTemp->pos.g += XXDJ;break;case p_rup://右上pTemp->pos.row--;pTemp->pos.col++;pTemp->pos.g += XXDJ;break;case p_rdown://右下pTemp->pos.row++;pTemp->pos.col++;pTemp->pos.g += XXDJ;break;}//判断他们能不能走,能走的计算h及f 入树  存储在buff数组if (CanWalk(map, vis, pTemp->pos)){	//能走//计算代价pTemp->pos.GetH(pTemp->pos, Endpos);//计算h代价pTemp->pos.GetF();//得到最后的f代价,f=g+h //把能走的这个点存入树中pCurrent->pChild.push_back(pTemp);//pTemp表示的就是下一个能走的点pTemp->pParent = pCurrent;//父子关系确定//存入数组buff.push_back(pTemp);//标记这个点走过vis[pTemp->pos.row][pTemp->pos.col] = true;}else{//不能走则删除pTemp,继续遍历下一个方向的点delete pTemp;pTemp = nullptr;}}/*遍历完八个方向后,找到最小代价点,并且移动,然后删除*/auto itMin =  min_element(buff.begin(), buff.end(), [&](TreeNode* p1, TreeNode* p2){return p1->pos.f < p2->pos.f;});//当前点移动到这个最小代价点pCurrent = *itMin;//删除最小代价节点buff.erase(itMin);//有没有到达终点if (pCurrent->pos == Endpos){isFindEnd = true;break;}//没有终点,自然一直删除节点,则buff为空if (buff.size() == 0){break;}}if (isFindEnd){cout << "找到终点了!\n";while (pCurrent){cout << "(" << pCurrent->pos.row << "," << pCurrent->pos.col << ")";pCurrent = pCurrent->pParent;}}else{cout << "没有找到终点!\n";}return 0;
}

终点row,col(7,7):
在这里插入图片描述

终点row,col(6,5)
在这里插入图片描述



http://chatgpt.dhexx.cn/article/SNpIJXns.shtml

相关文章

A星(A*、A Star)路径规划算法详解(附MATLAB代码)

首先看看运行效果&#xff0c;分别有三种模式&#xff0c;代码运行前需要通过鼠标点击设置起点和终点。 第一种模式直接输出最短路径 第二种模式输出最短路径的生成过程 第三种模式输出最短路径的生成过程和详细探索的过程 代码获取 gitee链接&#xff1a;https://gitee.c…

什么是脏读?不可重复读?幻读?如何解决?

什么是脏读&#xff1f;不可重复读&#xff1f;幻读&#xff1f;如何解决&#xff1f; 朋友最近面试美团&#xff0c;被面试官问到数据库的幻读问题&#xff0c;自己正好最近复习到这&#xff0c;做个笔记整理一下数据库的三大特征以及隔离级别。 一.先来回顾一下什么是事务&…

MySQL理论:脏读、不可重复读、幻读

&#x1f3c6;今日学习目标&#xff1a; &#x1f340;MySQL理论&#xff1a;脏读、不可重复读、幻读 ✅创作者&#xff1a;林在闪闪发光 ⏰预计时间&#xff1a;30分钟 &#x1f389;个人主页&#xff1a;林在闪闪发光的个人主页 &#x1f341;林在闪闪发光的个人社区&#xf…

数据库事务隔离级别(脏读、幻读、不可重复读)

一、脏读、幻读和不可重复读 一、脏读、不可重复读、幻读 1、脏读&#xff1a;脏读就是指当一个事务正在访问数据&#xff0c;并且对数据进行了修改&#xff0c;而这种修改还没有提交到数据库中&#xff0c;这时&#xff0c;另外一个事务也访问这个数据&#xff0c;然后使用了…

快速理解脏读,不可重复读,幻读

介绍 要聊事务&#xff0c;不可避免的要提到数据库事务的四大特性&#xff1a;ACID atomicconsistenceisolationdurability 先放一个表格&#xff0c;看看4个隔离级别会出现的各种问题&#xff0c;网上的解释一大堆。看完后还是一脸懵逼&#xff0c;感觉懂了&#xff0c;又好…

MySQL之脏写、脏读、不可重复读、幻读

脏写和脏读都是在多个事务同时修改或读取同一行数据的情况下产生的问题。比如现在有事务1和事务2同时对一行数据进行修改&#xff0c;事务1将值改成1&#xff0c;而事务2将值改成了2&#xff0c;这时的值应该是2&#xff0c;但是就在这时&#xff0c;事务1发生了回滚&#xff0…

数据库必备知识:脏读和幻读的定义及应对策略

随着数据库应用的广泛使用&#xff0c;数据库并发性和一致性的问题成为了引起重视的问题之一。其中&#xff0c;脏读&#xff08;Dirty Read&#xff09;和幻读&#xff08;Phantom Read&#xff09;是常见的并发访问问题&#xff0c;本文将对脏读、幻读进行详细介绍&#xff0…

Seata AT模式怎样防止脏写和脏读

前言 Seata AT 模式是一种非侵入式的分布式事务解决方案&#xff0c;Seata 在内部做了对数据库操作的代理层&#xff0c;我们使用 Seata AT 模式时&#xff0c;实际上用的是 Seata 自带的数据源代理 DataSourceProxy&#xff0c;Seata 在这层代理中加入了很多逻辑&#xff0c;…

mysql 脏数据是什么_什么是脏读?

什么是脏读&#xff1f; 脏读又称无效数据的读出&#xff0c;是指在数据库访问中&#xff0c;事务T1将某一值修改&#xff0c;然后事务T2读取该值&#xff0c;此后T1因为某种原因撤销对该值的修改&#xff0c;这就导致了T2所读取到的数据是无效的&#xff0c;值得注意的是&…

[Database] 脏读、幻读这些都是什么?事务隔离级别又是什么?MySQL数据库的事务隔离级别都有哪些?

文章目录 前言事务隔离级别三种数据不一致问题1. 脏读2. 不可重复读3. 幻读不可重复读 vs 幻读 四种事务隔离级别1. READ UNCOMMITTED2. READ COMMITTED3. REPEATABLE READ4. SERIALIZABLE 不同事务隔离级别会面临的问题不同隔离事务级别的使用率排名 实战查看事务隔离级别更改…

Mysql-详解脏读、不可重复读、幻读

Mysql的事务隔离级别 Mysql有四种事务隔离级别&#xff0c;这四种隔离级别代表当存在多个事务并发冲突时&#xff0c;可能出现的脏读、不可重复读、幻读的问题。 脏读 大家看一下&#xff0c;我们有两个事务&#xff0c;一个是 Transaction A&#xff0c;一个是 Transaction B…

MySQL的事务,脏读,不可重复读,幻读

一、什么是事务 在MySQL中&#xff0c;事务是一种机制、一个操作序列&#xff0c;是访问和更新数据库的程序执行单元。事务中包含一个或多个数据库操作命令&#xff0c;会把所有的命令作为一个整体一起向系统提交或撤销操作请求&#xff0c;即这一组数据库命令要么都执行&#…

脏读、幻读、不可重复读,傻傻分不清楚

脏读 &#xff08;针对未提交数据) 脏读又称无效数据读出&#xff08;读出了脏数据&#xff09;。一个事务读取另外一个事务还没有提交的数据叫脏读。 例如&#xff1a;事务T1修改了某个表中的一行数据&#xff0c;但是还没有提交&#xff0c;这时候事务T2读取了被事务T1修改…

【MySQL理论】脏读、不可重复读、幻读

文章目录 1. 脏读(dirty read)脏读是指事务读取到其他事务未提交的数据 2. 不可重复读(non-repeatable read)不可重复读是指在同一次事务中前后查询不一致的问题 3. 幻读(phantom read)幻读是一次事务中前后数据量发生变化&#xff0c;用户产生不可预料的问题 4. 不可重复读和幻…

脏读、不可重复读、幻读、丢失更新

根儿上来说&#xff0c;为什么需要事务和锁&#xff1f; 如果所有的操作都是依次进行的&#xff0c;或者说mysql的server是单线程的&#xff0c;就不会有并发问题&#xff0c;也就不需要事务和锁了。然而事实上&#xff0c;是多客户端&#xff0c;多线程的&#xff0c;所有必须…

一文搞懂MySQL脏读,幻读和不可重复读

目录 MySQL 中事务的隔离 1.READ UNCOMMITTED2.READ COMMITTED3.REPEATABLE READ4.SERIALIZABLE前置知识 1.事务相关的常用命令2.MySQL 8 之前查询事务的隔离级别3.MySQL 8 之后查询事务的隔离级别4.查看连接的客户端详情5.查询连接客户端的数量6.设置客户端的事务隔离级别7.新…

脏读、幻读和不可重复读

一、引言 脏读、不可重复读和幻读是数据库中由于并发访问导致的数据读取问题。当多个事务同时进行时可以通过修改数据库事务的隔离级别来处理这三个问题。 二、问题解释 1、脏读&#xff08;读取未提交的数据&#xff09; 脏读又称无效数据的读出&#xff0c;是指在数据库访…

一文详解脏读、不可重复读、幻读

MySQL 是支持多事务并发执行的。否则来一个事务处理一个请求&#xff0c;处理一个人请求的时候&#xff0c;其它事务都等着&#xff0c;那估计都没人敢用MySQL作为数据库,因为用户体验太差&#xff0c;估计都要砸键盘了。 既然事务可以并发操作,这里就有一些问题&#xff1a;一…

快速理解脏读、不可重复读和幻读

MySQL的InnoDB引擎是支持事务的&#xff0c;但是并发事务的处理又会带来以下问题&#xff1a; 脏读不可重复读幻读 一、脏读 脏读指事务A读取到了事务B更新了但是未提交的数据&#xff0c;然后事务B由于某种错误发生回滚&#xff0c;那么事务A读取到的就是脏数据。 具体的说…

引用及指针和引用的区别

一.在C语言中&#xff0c;我们要给函数传参&#xff0c;有两种方法。 1.传值 void Swap(int a,int b) {int tmpa;ab;btmp; } int main() {int a10;int b20;Swap(a,b);return 0; }优点&#xff1a;形参不影响实参&#xff08;保护实参&#xff09;&#xff0c;可读性强。 缺点…