news 2026/5/23 13:12:10

C++实现D星 Lite算法

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
C++实现D星 Lite算法

D* Lite算法核心概念

D* Lite是一种增量式的路径规划算法,适用于动态环境,能够高效地重新规划路径,而无需每次都从头开始计算。下表汇总了其实现中的关键数据结构与核心函数:

组件类型名称说明
关键数据结构优先队列 (U)存储待处理的节点,按键值(key)排序。键值通常包含[k1, k2],其中k1 = min(g(s), rhs(s)) + h(s_start, s) + kmk2 = min(g(s), rhs(s))
g值记录节点s到目标点的历史最短代价估计。
rhs值基于邻居节点g值计算的最小代价,满足rhs(s) = min_{s'∈Succ(s)} (c(s, s') + g(s'))。若g(s) == rhs(s),则称节点s一致的。
核心函数CalculateKey(s)计算节点s的键值,用于确定在优先队列中的优先级。
Initialize()初始化算法,设置grhs的初始值(通常为无穷大),并将目标节点的rhs设为0后加入队列。
UpdateVertex(u)当节点urhs值或其邻居信息发生变化时,更新其rhs值及其在队列中的状态。
ComputeShortestPath()核心计算过程,持续扩展节点直到起始节点达到一致且队列顶部的键值不小于起始节点的键值。

在D* Lite中,前驱(Pred)和后继(Succ)指的是图的邻接关系。Succ(u)是所有从u出发有边直接到达的节点(即u的后继),而Pred(u)是所有有边直接指向u的节点(即u的前驱)。由于算法是从目标点向起始点反向搜索的,所以在代码中我们通常操作的是节点的前驱集合Pred

算法实现步骤与C++代码框架

以下是一个简化的D* Lite算法C++实现框架,基于网格地图环境。实际的实现会更复杂,但这能帮你理清主线逻辑。

#include<vector>#include<queue>#include<unordered_map>#include<limits>// 定义节点类型,例如用网格坐标表示structNode{intx,y;// 重载运算符以便用于比较或作为哈希键值booloperator==(constNode&other)const{returnx==other.x&&y==other.y;}};// 为Node特化std::hashnamespacestd{template<>structhash<Node>{size_toperator()(constNode&n)const{returnhash<int>()(n.x)^(hash<int>()(n.y)<<1);}};}classDStarLite{private:std::unordered_map<Node,double>g_;// g值表std::unordered_map<Node,double>rhs_;// rhs值表// 优先队列:元素为 (键值k1, 键值k2, 节点)usingQueueElement=std::tuple<double,double,Node>;std::priority_queue<QueueElement,std::vector<QueueElement>,std::greater<QueueElement>>U_;doublekm_;// 用于键值计算的偏移量Node s_start_,s_goal_,s_last_;// ... 其他成员变量,如地图信息 ...// 计算启发式函数h(s, s')doubleheuristic(constNode&a,constNode&b){// 例如,使用曼哈顿距离或欧几里得距离returnstd::abs(a.x-b.x)+std::abs(a.y-b.y);}// 计算键值std::pair<double,double>CalculateKey(constNode&s){doubleg_val=g_.count(s)?g_[s]:std::numeric_limits<double>::infinity();doublerhs_val=rhs_.count(s)?rhs_[s]:std::numeric_limits<double>::infinity();doublek1=std::min(g_val,rhs_val)+heuristic(s_start_,s)+km_;doublek2=std::min(g_val,rhs_val);return{k1,k2};}// 初始化算法voidInitialize(){U_=std::priority_queue<QueueElement,std::vector<QueueElement>,std::greater<QueueElement>>();km_=0.0;// 初始化所有节点的g和rhs为无穷大g_.clear();rhs_.clear();// 设置目标节点的rhs为0rhs_[s_goal_]=0.0;U_.push({CalculateKey(s_goal_),s_goal_});}// 更新顶点uvoidUpdateVertex(constNode&u){if(u==s_goal_){// 目标节点特殊处理,通常其rhs保持为0return;}// 获取u的所有前驱节点 Pred(u) (在反向搜索中,这些是原图中u的后继)std::vector<Node>predecessors=GetPredecessors(u);// 需要你根据图的结构实现此函数// 计算新的rhs值:取所有 (c(u, s') + g(s')) 的最小值,其中s'属于Pred(u)doublemin_rhs=std::numeric_limits<double>::infinity();for(constNode&pred:predecessors){doublecost=GetCost(u,pred);// 获取边(u, pred)的成本,需要实现。注意方向:在反向搜索中,我们关心从u到pred的成本(原图中是pred到u的成本)。doubleg_pred=g_.count(pred)?g_[pred]:std::numeric_limits<double>::infinity();if(cost+g_pred<min_rhs){min_rhs=cost+g_pred;}}rhs_[u]=min_rhs;// 如果u在队列中,先移除// (在实际实现中,可能需要一个标记或更复杂的队列管理来高效判断和移除)// 这里简化为先尝试移除(如果存在),然后再判断是否需要插入// 更高效的实现可能需要维护一个单独的队列元素存在性标记// 如果g(u) != rhs(u),则将u以其CalculateKey插入或更新到队列U中doubleg_u=g_.count(u)?g_[u]:std::numeric_limits<double>::infinity();if(g_u!=rhs_[u]){// 这里需要实现U_.update或先删除再插入的逻辑。简单起见,可以先删除再插入,但效率较低。// 假设我们的队列不支持直接update,我们这里先简单推入新键值,在ComputeShortestPath中处理重复节点。autokey=CalculateKey(u);U_.push({key.first,key.second,u});}else{// 如果一致,且u在队列中,则应移除。这里简化处理,依赖ComputeShortestPath中处理无效节点。}}// 计算最短路径voidComputeShortestPath(){while(!U_.empty()){auto[k1_old,k2_old,u]=U_.top();U_.pop();auto[k1_new,k2_new]=CalculateKey(u);// 如果旧的键值小于新的键值,说明节点需要重新以新键值加入队列if(k1_old<k1_new||(k1_old==k1_new&&k2_old<k2_new)){U_.push({k1_new,k2_new,u});}// 如果节点u是过一致的 (g(u) > rhs(u))elseif((g_.count(u)?g_[u]:INFINITY)>(rhs_.count(u)?rhs_[u]:INFINITY)){g_[u]=rhs_[u];// 使节点一致// 更新u的所有前驱节点(在反向搜索中,这些是原图中u的后继)std::vector<Node>predecessors=GetPredecessors(u);for(constNode&pred:predecessors){UpdateVertex(pred);}}// 如果节点u是欠一致的 (g(u) < rhs(u))else{g_[u]=std::numeric_limits<double>::infinity();// 将g(u)设为无穷大,使其变为过一致或未定义// 需要更新u本身及其所有前驱节点UpdateVertex(u);// 更新自身std::vector<Node>predecessors=GetPredecessors(u);for(constNode&pred:predecessors){UpdateVertex(pred);}}// 循环终止条件:起始节点一致且队列顶部的键值不小于起始节点的键值doubleg_start=g_.count(s_start_)?g_[s_start_]:std::numeric_limits<double>::infinity();doublerhs_start=rhs_.count(s_start_)?rhs_[s_start_]:std::numeric_limits<double>::infinity();if(g_start==rhs_start&&(U_.empty()||CalculateKey(U_.top().s)>=CalculateKey(s_start_))){break;}}}public:// 主循环voidMain(){s_last_=s_start_;Initialize();ComputeShortestPath();while(s_start_!=s_goal_){// 如果g(s_start)是无穷大,说明无路径if(g_.count(s_start_)==0||g_[s_start_]==std::numeric_limits<double>::infinity()){// 处理无路径情况break;}// 选择下一个移动点:argmin_{s' in Succ(s_start)} (c(s_start, s') + g(s'))std::vector<Node>successors=GetSuccessors(s_start_);// 获取s_start在原图中的后继节点doublemin_cost=std::numeric_limits<double>::infinity();Node next_node=s_start_;for(constNode&succ:successors){doublecost=GetCost(s_start_,succ);// 获取边(s_start, succ)的成本doubleg_succ=g_.count(succ)?g_[succ]:std::numeric_limits<double>::infinity();if(cost+g_succ<min_cost){min_cost=cost+g_succ;next_node=succ;}}s_start_=next_node;// 移动到下一个节点// 移动后,扫描地图变化(在实际应用中,这里需要你根据传感器信息更新边成本)// 例如:如果检测到边(u, v)的成本发生变化// km_ = km_ + heuristic(s_last_, s_start_);// s_last_ = s_start_;// for each changed edge (u, v):// Update the edge cost c(u, v)// UpdateVertex(u)// ComputeShortestPath(); // 重新规划}}// 需要你实现的辅助函数:std::vector<Node>GetPredecessors(constNode&u);// 在反向搜索中,获取节点u的前驱(即原图结构中的后继)std::vector<Node>GetSuccessors(constNode&u);// 获取节点u在原图结构中的后继doubleGetCost(constNode&from,constNode&to);// 获取两个相邻节点之间的移动成本};

实现注意事项与常见问题

  1. 图的表示与邻居获取:上述代码中的GetPredecessorsGetSuccessorsGetCost函数需要你根据具体的图结构(如网格地图)来实现。在网格中,一个节点的邻居通常是其上下左右(或包括对角线)的相邻格子。
  2. 优先队列的高效管理:标准库的priority_queue不支持直接更新元素的值。一个常见的优化是使用"惰性删除"策略:当从队列顶部弹出节点时,检查其键值是否最新(即其grhs值自该元素入队后是否未改变),如果已过时则直接忽略。你也可以考虑使用支持 decrease-key 操作的堆结构。
  3. 处理动态变化:当检测到边成本变化时(例如,在Main函数的注释部分),需要更新受影响节点的rhs值并调用UpdateVertex。关键在于只更新成本实际发生变化的边所关联的节点。在机器人路径规划中,通常机器人只感知局部环境的变化。
  4. 避免循环:不正确的UpdateVertex逻辑或键值计算可能导致算法在两个节点间无限循环。确保在节点变为一致(g(u) == rhs(u))时将其从队列中移除(或在键值计算中体现其一致性),并正确更新受影响的邻居。
  5. 启发式函数的选择:启发式函数h(s_start, s)应满足可容性(admissible,即不高估真实成本)以保证最优性。在网格中,曼哈顿距离或欧几里得距离是常见选择。

参考代码 D*lite算法的C++实现www.3dddown.com/csa/60495.html

实现 D* Lite 算法确实有一定挑战性,建议从简单的静态环境开始,逐步增加动态障碍物功能,并善加调试。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/23 7:26:25

5、运动心理学与人格评估深度解析

运动心理学与人格评估深度解析 运动心理学核心议题探讨 运动心理学作为一门融合体育与心理学的学科,在发展过程中面临着诸多关键问题的思考。 首先,体育教育(运动机能学)和心理学在运动心理学的发展历程中扮演了重要角色。在未来,体育教育应着重于将心理学原理融入实际…

作者头像 李华
网站建设 2026/5/21 9:12:43

【2025版】最新在IDEA中接入DeepSeek,从零基础到工程师必备收藏指南

IDEA接入DeepSeek教程网络安全学习资源&#xff0c;小白程序员必备收藏指南 本文详细介绍了在IDEA开发环境中接入DeepSeek AI模型的步骤&#xff0c;包括安装Continue插件、配置API密钥及使用方法。同时分享了全套网络安全学习资源&#xff0c;涵盖成长路线图、视频教程、SRC文…

作者头像 李华
网站建设 2026/5/19 4:26:12

如何成为一名黑客?小白必学的12个基本步骤

如何成为一名黑客&#xff1f;小白必学的12个基本步骤 如何成为一名黑客&#xff1f;小白必学的12个基本步骤 黑客攻防是一个极具魅力的技术领域&#xff0c;但成为一名黑客毫无疑问也并不容易。你必须拥有对新技术的好奇心和积极的学习态度&#xff0c;具备很深的计算机系统…

作者头像 李华
网站建设 2026/5/22 18:11:51

防火墙配置【最详细的实验演示】

防火墙配置【最详细的实验演示】 原创 是傲 [东南网络安全](javascript:void(0)&#x1f609; 2024年06月01日 15:29 北京 声明&#xff1a;无恶意引导&#xff01; 目录 拓扑及拓扑分析 进入防火墙前准备工作 1.配置其他区域ip 2.如何进入防火墙 防火墙的安全域 1.防…

作者头像 李华
网站建设 2026/5/23 12:56:49

每天一个大模型知识点系列--大模型如何训练,分为哪些阶段

大模型的训练数据来自大规模的语料库&#xff0c;数据中包含海量的信息&#xff0c;那如何训练出一个可以进行交互的大语言模型呢&#xff1f;大语言模型的训练往往包含三个阶段&#xff0c;预训练阶段&#xff0c;监督微调阶段&#xff0c;和强化学习阶段。第一阶段是pretrain…

作者头像 李华
网站建设 2026/5/16 10:50:23

vue基于Spring Boot的校园餐厅菜品自选系统_96f895uz

目录具体实现截图项目介绍论文大纲核心代码部分展示项目运行指导结论源码获取详细视频演示 &#xff1a;文章底部获取博主联系方式&#xff01;同行可合作具体实现截图 本系统&#xff08;程序源码数据库调试部署讲解&#xff09;同时还支持java、ThinkPHP、Node.js、Spring B…

作者头像 李华