<编辑中> 一口气看完路径规划 ,耗时85642687分钟制作,全程无尿点。友情提示:备好零食,连接WiFi


avatar
GuoYulong 2024-10-28 647

【本文仅用作笔记和回顾】

一、前言

以无人驾驶车领域为例介绍全局路径规划和局部路径规划:

(一)全局路径规划:全局路径规划算法属于静态规划算法,根据已有的地图信息(SLAM)为基础进行路径规划,寻找一条从起点到目标点的最优路径。通常全局路径规划的实现包括Dijikstra算法,A * 算法,RRT算法等经典算法,也包括蚁群算法、遗传算法等智能算法;

(二)局部路径规划:局部路径规划属于动态规划算法,是无人驾驶汽车根据自身传感器感知周围环境,规划处一条车辆安全行驶所需的路线,常应用于超车,避障等情景。通常局部路径规划的实现包括动态窗口算法(DWA),人工势场算法,贝塞尔曲线算法等,也有学者提出神经网络等智能算法。

原文链接:https://blog.csdn.net/m0_55205668/article/details/123922046

上图来源于:路径规划算法

二、全局路径规划

在全局路径规划算法中,大致可分为三类:基于搜索的算法(Dijkstra算法、A* 算法等)、基于采样的算法(RRTRRT * 等)、智能算法(蚁群、遗传等)。【该分类根据B站IR艾若机器人,下图同】

常用的搜索算法还有D*D*LiteLPA*等,对于 DijkstraA*D*D* LiteLPA* 这五种算法的比较可以参考 https://zhuanlan.zhihu.com/p/124232080

由于规划岗常见的是DijkstraA*D*RRTRRT* ,本篇将着重写这几个算法,可能还会提及蚁群。

【搜索算法 1 】Dijkstra

可以直接看:
https://blog.csdn.net/qq_44431690/article/details/108175827 ,这篇的过程给的非常详细。
https://zhuanlan.zhihu.com/p/346558578 ,这篇的例子给的很清晰,同时给了C#的实现。
https://oi-wiki.org/graph/shortest-path/ ,这篇给出了时间复杂度和正确性证明,同时给出了优先队列实现。
https://www.luogu.com.cn/article/s581e0wm ,洛谷出品,很明显是在算法上进行优化(堆)。

由于数据结构的老师教我它读迪杰斯特拉算法,所以我就保持了这个习惯。Dijkstra可以说是最经典的最短路径算法,利用贪心作为思路,维护两张表:distance(起点到其他所有点的距离信息)、Top_node(最短路径信息)。

其整体逻辑可以理解为进行 n 次循环,每次循环再遍历 n 个点确定一个还未确定最短距离的点中距离源点最近距离的点,然后再用这个点更新其所能到达的点(算法默认当前的点可以到达所有的点,因为没有到达的点之间的距离都已经初始化为正无穷大,所以不会被更新,不影响结果)https://blog.csdn.net/qq_41575507/article/details/114548187

实现伪代码:图源 IR艾若机器人

Dijkstra求最短距离伪代码

https://blog.csdn.net/qq_33375598/article/details/104338104

复制代码
  1. //图G一般设为全局变量;数组d为源点到达各点的最短路径长度,s为起点
  2. Dijkstra(G, d[], s){
  3. 初始化
  4. for (循环n次)
  5. {
  6. u = 使d[u]最小的但还未被访问的顶点的标号;
  7. u已被访问;
  8. for (从u出发能到达的所有顶点v)
  9. {
  10. if(v未被访问 && u为中介点使s到顶点v的最短路径d[v]更优){
  11. 优化d[v];
  12. }
  13. }
  14. }
  15. }

C++ Dijkstra邻接矩阵求最短距离实现

https://blog.csdn.net/qq_33375598/article/details/104338104

复制代码
  1. #include <iostream>
  2. #include <vector>
  3. #include <algorithm>
  4. using namespace std;
  5. const int MAXV = 1000; // 最大顶点数
  6. const int INF = 0x3fffffff; // 定义无穷大
  7. int G[MAXV][MAXV]; // 图的邻接矩阵表示
  8. int d[MAXV]; // 起点到达各点的最短路径
  9. bool vis[MAXV] = { false }; // 标记是否被访问
  10. int n; // 顶点数
  11. void Dijkstra(int s) { // s 是起点
  12. fill(d, d + n, INF); // 将整个数组 d 赋值为 INF
  13. d[s] = 0; // 起点到自身的距离为 0
  14. for (int i = 0; i < n; ++i) { // 循环 n 次
  15. int u = -1, min = INF;
  16. for (int j = 0; j < n; ++j) { // 找到未访问的顶点中 d[] 最小的
  17. if (!vis[j] && d[j] < min) {
  18. u = j;
  19. min = d[j];
  20. }
  21. }
  22. if (u == -1) return; // 找不到小于 INF 的 d[u],说明剩余顶点不可达
  23. vis[u] = true; // 标记 u 已被访问
  24. for (int v = 0; v < n; ++v) { // 更新 d[v]
  25. if (!vis[v] && G[u][v] != INF && d[u] + G[u][v] < d[v]) {
  26. d[v] = d[u] + G[u][v];
  27. }
  28. }
  29. }
  30. }
  31. int main() {
  32. // 顶点数和边数
  33. n = 5; // 节点个数
  34. int edges[7][3] = { // 定义图的边 (起点, 终点, 权重)
  35. {0, 1, 10},
  36. {0, 4, 5},
  37. {1, 2, 1},
  38. {1, 4, 2},
  39. {2, 3, 4},
  40. {3, 0, 7},
  41. {4, 2, 9}
  42. };
  43. // 初始化图的邻接矩阵
  44. for (int i = 0; i < n; ++i)
  45. fill(G[i], G[i] + n, INF);
  46. // 设置边的权重
  47. for (auto& edge : edges) {
  48. int u = edge[0], v = edge[1], w = edge[2];
  49. G[u][v] = w;
  50. }
  51. // 执行 Dijkstra 算法,以 0 为起点
  52. Dijkstra(0);
  53. // 输出从起点到每个顶点的最短距离
  54. cout << "Shortest distances from vertex 0:" << endl;
  55. for (int i = 0; i < n; ++i) {
  56. if (d[i] == INF) cout << "INF ";
  57. else cout << d[i] << " ";
  58. }
  59. cout << endl;
  60. return 0;
  61. }

C++ Dijkstra邻接表求最短距离实现

https://blog.csdn.net/qq_33375598/article/details/104338104

复制代码
  1. #include <iostream>
  2. #include <vector>
  3. #include <algorithm>
  4. using namespace std;
  5. struct Node {
  6. int v; // 边的目标顶点
  7. int dis; // 边的权重
  8. };
  9. const int MAXV = 1000; // 最大顶点数
  10. const int INF = 0x3fffffff; // 无穷大表示不可达
  11. vector<Node> Adj[MAXV]; // 邻接表表示的图
  12. int d[MAXV]; // 起点到各个顶点的最短路径
  13. bool vis[MAXV] = { false }; // 标记是否访问过的顶点
  14. int n; // 顶点数
  15. void Dijkstra(int s) { // s 是起点
  16. fill(d, d + n, INF); // 初始化所有最短路径为不可达
  17. d[s] = 0; // 起点到自身的距离为 0
  18. for (int i = 0; i < n; ++i) { // 循环 n 次
  19. int u = -1, min = INF;
  20. for (int j = 0; j < n; ++j) { // 找到未访问的顶点中 d[] 最小的
  21. if (!vis[j] && d[j] < min) {
  22. u = j;
  23. min = d[j];
  24. }
  25. }
  26. if (u == -1) return; // 找不到小于 INF 的 d[u],说明剩余顶点不可达
  27. vis[u] = true; // 标记 u 已访问
  28. for (int j = 0; j < Adj[u].size(); ++j) { // 更新 d[v]
  29. int v = Adj[u][j].v;
  30. int weight = Adj[u][j].dis;
  31. if (!vis[v] && d[u] + weight < d[v]) {
  32. d[v] = d[u] + weight;
  33. }
  34. }
  35. }
  36. }
  37. int main() {
  38. // 顶点数和边数
  39. n = 5; // 顶点数
  40. int edges[7][3] = { // 图的边定义 (起点, 终点, 权重)
  41. {0, 1, 10},
  42. {0, 4, 5},
  43. {1, 2, 1},
  44. {1, 4, 2},
  45. {2, 3, 4},
  46. {3, 0, 7},
  47. {4, 2, 9}
  48. };
  49. // 初始化邻接表
  50. for (auto& edge : edges) {
  51. int u = edge[0], v = edge[1], w = edge[2];
  52. Adj[u].push_back({ v, w });
  53. }
  54. // 执行 Dijkstra 算法,以 0 为起点
  55. Dijkstra(0);
  56. // 输出从起点到每个顶点的最短路径
  57. cout << "Shortest distances from vertex 0:" << endl;
  58. for (int i = 0; i < n; ++i) {
  59. if (d[i] == INF) cout << "INF ";
  60. else cout << d[i] << " ";
  61. }
  62. cout << endl;
  63. return 0;
  64. }

Dijkstra求最短路径伪代码

https://blog.csdn.net/qq_33375598/article/details/104338104

复制代码
  1. //图G一般设为全局变量;数组d为源点到达各点的最短路径长度,s为起点
  2. Dijkstra(G, d[], s){
  3. 初始化
  4. for (循环n次)
  5. {
  6. u = 使d[u]最小的但还未被访问的顶点的标号;
  7. u已被访问;
  8. for (从u出发能到达的所有顶点v)
  9. {
  10. if(v未被访问 && u为中介点使s到顶点v的最短路径d[v]更优){
  11. 优化d[v];
  12. v的前驱为u;
  13. }
  14. }
  15. }
  16. }

C++ Dijkstra邻接矩阵求最短路径实现

https://blog.csdn.net/qq_33375598/article/details/104338104

复制代码
  1. #include <iostream>
  2. #include <vector>
  3. #include <algorithm>
  4. using namespace std;
  5. const int MAXV = 1000;
  6. const int INF = 0x3ffffff;
  7. int G[MAXV][MAXV]; // 图的邻接矩阵
  8. int pre[MAXV]; // 前驱节点数组
  9. int d[MAXV]; // 最短路径数组
  10. bool vis[MAXV] = { false }; // 标记是否被访问
  11. int n; // 顶点数
  12. void Dijkstra(int s) { // 求出最短路径上每个结点的前驱
  13. fill(d, d + n, INF); // 初始化最短路径数组
  14. d[s] = 0; // 起点到自身的距离为 0
  15. for (int i = 0; i < n; ++i) pre[i] = i; // 初始化前驱数组
  16. for (int i = 0; i < n; ++i) {
  17. int u = -1, min = INF;
  18. for (int j = 0; j < n; ++j) {
  19. if (!vis[j] && d[j] < min) {
  20. u = j;
  21. min = d[j];
  22. }
  23. }
  24. if (u == -1) return; // 找不到小于 INF 的 d[u],说明剩余顶点不可达
  25. vis[u] = true; // 标记 u 已被访问
  26. for (int v = 0; v < n; ++v) { // 更新 d[v] 和 pre[v]
  27. if (!vis[v] && G[u][v] != INF && d[u] + G[u][v] < d[v]) {
  28. d[v] = d[u] + G[u][v];
  29. pre[v] = u;
  30. }
  31. }
  32. }
  33. }
  34. void DFS(int s, int v) { // 递归打印从起点 s 到当前顶点 v 的路径
  35. if (v == s) {
  36. cout << s << " ";
  37. return;
  38. }
  39. DFS(s, pre[v]);
  40. cout << v << " ";
  41. }
  42. int main() {
  43. // 定义图的边数和顶点数
  44. n = 5; // 顶点数
  45. int edges[7][3] = { // 图的边定义 (起点, 终点, 权重)
  46. {0, 1, 10},
  47. {0, 4, 5},
  48. {1, 2, 1},
  49. {1, 4, 2},
  50. {2, 3, 4},
  51. {3, 0, 7},
  52. {4, 2, 9}
  53. };
  54. // 初始化图的邻接矩阵
  55. for (int i = 0; i < n; ++i)
  56. fill(G[i], G[i] + n, INF);
  57. for (auto& edge : edges) {
  58. int u = edge[0], v = edge[1], w = edge[2];
  59. G[u][v] = w;
  60. }
  61. // 以 0 为起点执行 Dijkstra 算法
  62. Dijkstra(0);
  63. // 输出从起点到每个顶点的最短路径
  64. cout << "Shortest distances from vertex 0:" << endl;
  65. for (int i = 0; i < n; ++i) {
  66. if (d[i] == INF) cout << "INF ";
  67. else cout << d[i] << " ";
  68. }
  69. cout << endl;
  70. // 输出从起点到每个顶点的路径
  71. cout << "Paths from vertex 0:" << endl;
  72. for (int i = 0; i < n; ++i) {
  73. cout << "Path to " << i << ": ";
  74. if (d[i] == INF) cout << "No path" << endl;
  75. else {
  76. DFS(0, i); // 打印从 0 到 i 的路径
  77. cout << endl;
  78. }
  79. }
  80. return 0;
  81. }

C++ Dijkstra邻接表求最短路径实现

https://blog.csdn.net/qq_33375598/article/details/104338104

复制代码
  1. #include <iostream>
  2. #include <vector>
  3. #include <algorithm>
  4. using namespace std;
  5. struct Node {
  6. int v; // 目标顶点
  7. int dis; // 边的权重
  8. };
  9. const int MAXV = 1000;
  10. const int INF = 0x3ffffff;
  11. vector<Node> Adj[MAXV]; // 邻接表表示的图
  12. int pre[MAXV]; // 前驱节点数组
  13. int d[MAXV]; // 最短路径数组
  14. bool vis[MAXV] = { false }; // 标记是否被访问
  15. int n; // 顶点数
  16. void Dijkstra(int s) { // 求出最短路径上每个结点的前驱
  17. fill(d, d + n, INF); // 初始化最短路径数组
  18. d[s] = 0; // 起点到自身的距离为 0
  19. for (int i = 0; i < n; ++i) pre[i] = i; // 初始化前驱数组
  20. for (int i = 0; i < n; ++i) {
  21. int u = -1, min = INF;
  22. for (int j = 0; j < n; ++j) { // 找到未访问的顶点中 d[] 最小的
  23. if (!vis[j] && d[j] < min) {
  24. u = j;
  25. min = d[j];
  26. }
  27. }
  28. if (u == -1) return; // 找不到小于 INF 的 d[u],说明剩余顶点不可达
  29. vis[u] = true; // 标记 u 已被访问
  30. for (int j = 0; j < Adj[u].size(); ++j) { // 更新 d[v] 和 pre[v]
  31. int v = Adj[u][j].v;
  32. int weight = Adj[u][j].dis;
  33. if (!vis[v] && d[u] + weight < d[v]) {
  34. d[v] = d[u] + weight;
  35. pre[v] = u;
  36. }
  37. }
  38. }
  39. }
  40. void DFS(int s, int v) { // 递归打印从起点 s 到当前顶点 v 的路径
  41. if (v == s) {
  42. cout << s << " ";
  43. return;
  44. }
  45. DFS(s, pre[v]);
  46. cout << v << " ";
  47. }
  48. int main() {
  49. // 顶点数和边数
  50. n = 5; // 顶点数
  51. int edges[7][3] = { // 定义图的边 (起点, 终点, 权重)
  52. {0, 1, 10},
  53. {0, 4, 5},
  54. {1, 2, 1},
  55. {1, 4, 2},
  56. {2, 3, 4},
  57. {3, 0, 7},
  58. {4, 2, 9}
  59. };
  60. // 初始化邻接表
  61. for (auto& edge : edges) {
  62. int u = edge[0], v = edge[1], w = edge[2];
  63. Adj[u].push_back({ v, w });
  64. }
  65. // 执行 Dijkstra 算法,以 0 为起点
  66. Dijkstra(0);
  67. // 输出从起点到每个顶点的最短路径
  68. cout << "Shortest distances from vertex 0:" << endl;
  69. for (int i = 0; i < n; ++i) {
  70. if (d[i] == INF) cout << "INF ";
  71. else cout << d[i] << " ";
  72. }
  73. cout << endl;
  74. // 输出从起点到每个顶点的路径
  75. cout << "Paths from vertex 0:" << endl;
  76. for (int i = 0; i < n; ++i) {
  77. cout << "Path to " << i << ": ";
  78. if (d[i] == INF) cout << "No path" << endl;
  79. else {
  80. DFS(0, i); // 打印从 0 到 i 的路径
  81. cout << endl;
  82. }
  83. }
  84. return 0;
  85. }

Dijkstra 优先队列 优化

https://zhuanlan.zhihu.com/p/34624812

复制代码
  1. #include <iostream>
  2. #include <vector>
  3. #include <queue>
  4. #include <cstring>
  5. using namespace std;
  6. struct Edge {
  7. int to; // 目标顶点
  8. int cost; // 边的权重
  9. };
  10. const int MAXV = 1000; // 最大顶点数
  11. const int INF = 0x3ffffff; // 无穷大表示不可达
  12. vector<Edge> G[MAXV]; // 邻接表表示的图
  13. int d[MAXV]; // 最短路径数组
  14. int n; // 顶点数
  15. void Dijkstra(int s) { // 求出从起点 s 到其他顶点的最短路径
  16. fill(d, d + n, INF); // 初始化距离
  17. d[s] = 0; // 起点到自身的距离为 0
  18. priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> que; // 小顶堆
  19. que.push({ 0, s }); // 将起点推入队列
  20. while (!que.empty()) {
  21. auto p = que.top(); // 获取队头元素
  22. que.pop();
  23. int v = p.second; // 当前顶点编号
  24. if (d[v] < p.first) continue; // 跳过已处理的顶点
  25. for (const auto& e : G[v]) { // 遍历邻接边
  26. if (d[e.to] > d[v] + e.cost) { // 松弛操作
  27. d[e.to] = d[v] + e.cost; // 更新距离
  28. que.push({ d[e.to], e.to }); // 将更新后的顶点推入队列
  29. }
  30. }
  31. }
  32. }
  33. int main() {
  34. // 定义图的边和顶点数
  35. n = 5; // 顶点数
  36. int edges[7][3] = { // 定义图的边 (起点, 终点, 权重)
  37. {0, 1, 10},
  38. {0, 4, 5},
  39. {1, 2, 1},
  40. {1, 4, 2},
  41. {2, 3, 4},
  42. {3, 0, 7},
  43. {4, 2, 9}
  44. };
  45. // 初始化邻接表
  46. for (auto& edge : edges) {
  47. int u = edge[0], v = edge[1], w = edge[2];
  48. G[u].push_back({ v, w });
  49. }
  50. // 执行 Dijkstra 算法,以 0 为起点
  51. Dijkstra(0);
  52. // 输出从起点到每个顶点的最短路径
  53. cout << "Shortest distances from vertex 0:" << endl;
  54. for (int i = 0; i < n; ++i) {
  55. if (d[i] == INF) cout << "INF ";
  56. else cout << d[i] << " ";
  57. }
  58. cout << endl;
  59. return 0;
  60. }

【搜索算法 2】 A *

首先,https://paul.pub/a-star-algorithm/ 这篇文章写得很好,我基本都是抄这篇的;
其次,https://tashaxing.blog.csdn.net/article/details/47152137 这篇是我一开始学A* 的时候看的,也算是入门文章,当时在vs下编写运行的;后来在学 A* 的改进的时候对其进行了修改;
最后,https://github.com/lh9171338/Astar 这篇是我到现在一直在用的,因为我懒得再写一份,它用优先队列进行存储,算是对最基础的A* 进行了改进
很多地方都在说A* 引入启发式函数巴拉巴拉什么的,说白了,就是给搜索路线上的每个位置都评价打分一下,然后排个序选个最优点去走,整体理解起来我个人认为要比Dijkstra要简单的多的。

A * 算法实现伪代码

图源艾若机器人

复制代码
  1. 初始化open_setclose_set
  2. * 将起点加入open_set中,并设置优先级为0(优先级最高);
  3. * 如果open_set不为空,则从open_set中选取优先级最高的节点n
  4. * 如果节点n为终点,则:
  5. * 从终点开始逐步追踪parent节点,一直达到起点;
  6. * 返回找到的结果路径,算法结束;
  7. * 如果节点n不是终点,则:
  8. * 将节点nopen_set中删除,并加入close_set中;
  9. * 遍历节点n所有的邻近节点:
  10. * 如果邻近节点mclose_set中,则:
  11. * 跳过,选取下一个邻近节点
  12. * 如果邻近节点m也不在open_set中,则:
  13. * 设置节点mparent为节点n
  14. * 计算节点m的优先级
  15. * 将节点m加入open_set

启发函数:

f(n) = g(n) + h(n)

g(n)表示单次移动耗费,h(n)表示到目标点的预计耗费,f(n)则为综合代价。

g(n)很好理解,就是我从这一格走到下一格需要耗费的代价,在栅格地图中当前位置存在8个邻居节点,移动也只会移动到8个邻居节点上,所以此时的代价就只有两种情况 1 或者 sqrt(2) ,也就对应着直走或者斜走。

h(n)在我看来更好理解,就是节点到目标点耗费的代价,代价的方式有很多种,使用规则如下:

  • 如果图形中只允许朝上下左右四个方向移动,则可以使用曼哈顿距离(Manhattan distance)。
  • 如果图形中允许朝八个方向移动,则可以使用对角距离。
  • 如果图形中允许朝任何方向移动,则可以使用欧几里得距离(Euclidean distance)。
①曼哈顿距离


计算曼哈顿距离的函数如下,这里的D是指两个相邻节点之间的移动代价,通常是一个固定的常数。

复制代码
  1. function heuristic(node) =
  2. dx = abs(node.x - goal.x)
  3. dy = abs(node.y - goal.y)
  4. return D * (dx + dy)
②对角距离


计算对角距离的函数如下,这里的D2指的是两个斜着相邻节点之间的移动代价。如果所有节点都正方形,则其值就是sqrt(2) * D

复制代码
  1. function heuristic(node) =
  2. dx = abs(node.x - goal.x)
  3. dy = abs(node.y - goal.y)
  4. return D * (dx + dy) + (D2 - 2 * D) * min(dx, dy)
③欧几里得距离

如果图形中允许朝任意方向移动,则可以使用欧几里得距离。
欧几里得距离是指两个节点之间的直线距离,因此其计算方法也是我们比较熟悉的,其函数表示如下:

复制代码
  1. function heuristic(node) =
  2. dx = abs(node.x - goal.x)
  3. dy = abs(node.y - goal.y)
  4. return D * sqrt(dx * dx + dy * dy)

C++实现

这里贴的代码是本人优化后的,初始的代码是参考 https://tashaxing.blog.csdn.net/article/details/47152137 这篇文章的
Astar.cpp

复制代码
  1. #include <iostream>
  2. #include "AStar.h"
  3. namespace pathplanning {
  4. void Astar::InitAstar(vector<vector<int>>& _Map) {
  5. Map = _Map;
  6. Map_rows = _Map.size();
  7. Map_cols = _Map[0].size();
  8. }
  9. int Astar::CalcG(Point* _curpoint, Point* _point) {
  10. int extraG = (abs(_point->x - _curpoint->x) + abs(_point->y - _curpoint->y)) == 1 ? 10 : 14;
  11. int parentG = _point->parent == nullptr ? 0 : _point->parent->G;
  12. return parentG + extraG;
  13. }
  14. int Astar::CalcH(Point* _targetpoint, Point* _endpoint) {
  15. int dist = (_endpoint->x - _targetpoint->x) * (_endpoint->x - _targetpoint->x) + (_endpoint->y - _targetpoint->y) * (_endpoint->y - _targetpoint->y);
  16. return round(10 * sqrt(dist));
  17. }
  18. int Astar::CalcF(Point* _point) {
  19. return _point->G + _point->H;
  20. }
  21. void Astar::PathPlanning(Point _startPoint, Point _targetPoint, vector<pair<int, int>>& path) {
  22. startPoint = _startPoint;
  23. endPoint = _targetPoint;
  24. Point* TailPoint = FindPath(isIgnoreCorner);
  25. GetPath(TailPoint, path);
  26. }
  27. Point* Astar::isInCloseList(const vector<Point*>& _closelist, const Point* point) const
  28. {
  29. for (auto p : _closelist)
  30. if (p->x == point->x && p->y == point->y)
  31. return p;
  32. return nullptr;
  33. }
  34. Point* Astar::isInOpenList(const priority_queue<pair<int, Point*>, vector<pair<int, Point*>>, cmp>& _openlist, const Point* point) const
  35. {
  36. priority_queue<pair<int, Point*>, vector<pair<int, Point*>>, cmp> temp = _openlist;
  37. while (!temp.empty()) {
  38. Point* p = temp.top().second;
  39. temp.pop();
  40. if (p->x == point->x && p->y == point->y) {
  41. return p;
  42. }
  43. }
  44. return nullptr;
  45. }
  46. bool Astar::isCanreach(const Point* _point, const Point* _targetPoint) const
  47. {
  48. if (_targetPoint->x<0 || _targetPoint->x>Map.size() - 1
  49. || _targetPoint->y<0 || _targetPoint->y>Map[0].size() - 1
  50. || Map[_targetPoint->x][_targetPoint->y] == 1
  51. || _targetPoint->x == _point->x && _targetPoint->y == _point->y
  52. || isInCloseList(CloseList, _targetPoint))
  53. return false;
  54. else
  55. {
  56. if (abs(_point->x - _targetPoint->x) + abs(_point->y - _targetPoint->y) == 1) //非斜角可以
  57. return true;
  58. else
  59. {
  60. if (Map[_point->x][_targetPoint->y] == 0 && Map[_targetPoint->x][_point->y] == 0)
  61. return true;
  62. else
  63. return isIgnoreCorner;
  64. }
  65. }
  66. }
  67. vector<Point*> Astar::getSurroundPoints(const Point* _point) const {
  68. vector<Point*>SurroundPoints;
  69. #if Neibor5 == 1
  70. /* 5邻域*/
  71. int dx = endPoint.x - _point->x;
  72. int dy = endPoint.y - _point->y;
  73. if (dx >= 0 && dy >= 0) {
  74. addSurroundPoint(SurroundPoints, _point->x - 1, _point->y - 1, _point);
  75. addSurroundPoint(SurroundPoints, _point->x - 1, _point->y , _point);
  76. addSurroundPoint(SurroundPoints, _point->x - 1, _point->y + 1, _point);
  77. addSurroundPoint(SurroundPoints, _point->x , _point->y + 1, _point);
  78. addSurroundPoint(SurroundPoints, _point->x + 1, _point->y + 1, _point);
  79. }
  80. else if (dx >= 0 && dy <= 0) {
  81. addSurroundPoint(SurroundPoints, _point->x - 1, _point->y + 1, _point);
  82. addSurroundPoint(SurroundPoints, _point->x , _point->y + 1, _point);
  83. addSurroundPoint(SurroundPoints, _point->x + 1, _point->y - 1, _point);
  84. addSurroundPoint(SurroundPoints, _point->x + 1, _point->y , _point);
  85. addSurroundPoint(SurroundPoints, _point->x + 1, _point->y + 1, _point);
  86. }
  87. else if (dx <= 0 && dy >= 0) {
  88. addSurroundPoint(SurroundPoints, _point->x - 1, _point->y - 1, _point);
  89. addSurroundPoint(SurroundPoints, _point->x - 1, _point->y , _point);
  90. addSurroundPoint(SurroundPoints, _point->x - 1, _point->y + 1, _point);
  91. addSurroundPoint(SurroundPoints, _point->x , _point->y - 1, _point);
  92. addSurroundPoint(SurroundPoints, _point->x + 1, _point->y - 1, _point);
  93. }
  94. else if (dx <= 0 && dy <= 0) {
  95. addSurroundPoint(SurroundPoints, _point->x - 1, _point->y - 1, _point);
  96. addSurroundPoint(SurroundPoints, _point->x , _point->y - 1, _point);
  97. addSurroundPoint(SurroundPoints, _point->x + 1, _point->y - 1, _point);
  98. addSurroundPoint(SurroundPoints, _point->x + 1, _point->y , _point);
  99. addSurroundPoint(SurroundPoints, _point->x + 1, _point->y + 1, _point);
  100. }
  101. #else
  102. for (int x = _point->x - 1; x <= _point->x + 1; x++)
  103. for (int y = _point->y - 1; y <= _point->y + 1; y++)
  104. if (isCanreach(_point, new Point(x, y)))
  105. SurroundPoints.push_back(new Point(x, y));
  106. #endif
  107. return SurroundPoints;
  108. }
  109. // 辅助函数,用于添加可达的邻域点
  110. void Astar::addSurroundPoint(vector<Point*>& SurroundPoints, int x, int y, const Point* _point) const {
  111. if (isCanreach(_point, new Point(x, y))) {
  112. SurroundPoints.push_back(new Point(x, y));
  113. }
  114. }
  115. Point* Astar::FindPath(bool isIgnoreCorner) {
  116. // Add startPoint to OpenList
  117. Point* startPointNode = new Point(startPoint.x, startPoint.y);
  118. OpenList.push(pair<int, Point*>(startPointNode->F, startPointNode));
  119. int index = point2index(*startPointNode);
  120. OpenDict[index] = startPointNode;
  121. while (!OpenList.empty()) {
  122. // Find the node with least F value
  123. Point* CurPoint = OpenList.top().second;
  124. OpenList.pop();
  125. int index = point2index(*CurPoint);
  126. Point* CurNode = OpenDict[index];
  127. OpenDict.erase(index);
  128. // Determine whether arrive the target point
  129. if (CurPoint->x == endPoint.x && CurPoint->y == endPoint.y)
  130. {
  131. return CurNode; // Find a valid path
  132. }
  133. CloseList.push_back(CurPoint);
  134. // Find the accessible node around the Current node
  135. auto SurroundPoints = getSurroundPoints(CurPoint);
  136. for (auto& targetPoint : SurroundPoints) {
  137. if (!OpenDict.count(point2index(*targetPoint))) {
  138. //if (!isInOpenList(OpenList, targetPoint)) {
  139. targetPoint->parent = CurPoint;
  140. targetPoint->G = CalcG(CurPoint, targetPoint);
  141. targetPoint->H = CalcH(targetPoint, &endPoint);
  142. targetPoint->F = CalcF(targetPoint);
  143. OpenList.push(pair<int, Point*>(targetPoint->F, targetPoint));
  144. int index = point2index(*targetPoint);
  145. OpenDict[index] = targetPoint;
  146. }
  147. else {
  148. int TempG = CalcG(CurPoint, targetPoint);
  149. if (TempG < targetPoint->G) {
  150. targetPoint->parent = CurPoint;
  151. targetPoint->G = TempG;
  152. targetPoint->F = CalcF(targetPoint);
  153. }
  154. }
  155. /*
  156. Point* resPoint = isInOpenList(OpenList, &endPoint);
  157. if (resPoint)
  158. return resPoint; //返回列表里的节点指针,不要用原来传入的endpoint指针,因为发生了深拷贝
  159. */
  160. }
  161. }
  162. return nullptr;
  163. }
  164. void Astar::GetPath(Point* TailNode, vector<pair<int, int>>& path) {
  165. PathList.clear();
  166. path.clear();
  167. // Save path to PathList
  168. Point* CurNode = TailNode;
  169. while (CurNode != nullptr)
  170. {
  171. PathList.push_back(CurNode);
  172. CurNode = CurNode->parent;
  173. }
  174. // Save path to vector<Point>
  175. int length = PathList.size();
  176. for (int i = 0; i < length; i++)
  177. {
  178. path.push_back(pair<int, int>(PathList.back()->x, PathList.back()->y));
  179. PathList.pop_back();
  180. }
  181. // Release memory
  182. while (OpenList.size()) {
  183. OpenList.pop();
  184. }
  185. CloseList.clear();
  186. }
  187. }

Astar.h

复制代码
  1. //
  2. // Created by GuoYulong on 24-8-23.
  3. //
  4. #ifndef ASTAR_H
  5. #define ASTAR_H
  6. #define Neibor5 0
  7. #include <vector>
  8. #include <queue>
  9. #include <unordered_map>
  10. using namespace std;
  11. constexpr auto isIgnoreCorner = true;;
  12. namespace pathplanning {
  13. struct Point {
  14. int x, y;
  15. int F, G, H;
  16. Point* parent;
  17. Point(int _x, int _y) :x(_x), y(_y), F(0), G(0), H(0), parent(nullptr) {
  18. }
  19. };
  20. struct cmp
  21. {
  22. bool operator() (pair<int, Point*> a, pair<int, Point*> b) // Comparison function for priority queue
  23. {
  24. return a.first > b.first; // min heap
  25. }
  26. };
  27. class Astar {
  28. public:
  29. Astar() :startPoint(0, 0), endPoint(0, 0), Map(), Map_cols(0), Map_rows(0), OpenList(), CloseList(), PathList() {
  30. }
  31. void InitAstar(vector<vector<int>>& _Map);
  32. void PathPlanning(Point _startPoint, Point _targetPoint, vector<pair<int, int>>& path);
  33. inline int point2index(Point point) {
  34. return point.y * Map_cols + point.x;
  35. }
  36. inline Point index2point(int index) {
  37. return Point(int(index / Map_cols), index % Map_cols);
  38. }
  39. private:
  40. Point* FindPath(bool isIgnoreCorner);
  41. void GetPath(Point* TailNode, vector<pair<int, int>>& path);
  42. bool isCanreach(const Point* _point, const Point* _targetPoint) const;
  43. void addSurroundPoint(vector<Point*>& SurroundPoints, int x, int y, const Point* _point) const;
  44. vector<Point*> getSurroundPoints(const Point* _point) const;
  45. Point* isInCloseList(const vector<Point*>& _closelist, const Point* point) const;
  46. Point* isInOpenList(const priority_queue<pair<int, Point*>, vector<pair<int, Point*>>, cmp>& _openlist, const Point* point) const;
  47. int CalcG(Point* _startpoint, Point* _point);
  48. int CalcH(Point* _point, Point* _targetpoint);
  49. int CalcF(Point* _point);
  50. private:
  51. Point startPoint, endPoint;
  52. vector<vector<int>> Map;
  53. int Map_cols, Map_rows;
  54. priority_queue<pair<int, Point*>, vector<pair<int, Point*>>, cmp> OpenList;
  55. unordered_map<int, Point*> OpenDict;
  56. vector<Point*> CloseList;
  57. vector<Point*> PathList;
  58. };
  59. }
  60. #endif

A * 的优化

①程序上优化复杂度

A * 算法的核心计算在于:
1.从open和close表中进行查找
2.从open表中搜索最小栅格
3.从邻居中查找最小栅格

考虑从①②两点进行优化,传统的 A * 使用 vector<Point * > 或者 list<Point* >(Point为自己定义的点的结构体,包括坐标,FGH,父节点)

考虑到查找的复杂度较高,所以思考采用hash表进行查找,其查找的复杂度为O(1),所以我们采用unordered_mapopenlistcloselist 进行备份,在查找时直接调用查找函数find或者count,由于unordered_map存在键值对应关系,可以设计为点的坐标的序号为key,即 (point.x-1)*map.col+point.y ,这样能确保唯一性;

考虑到排序的复杂度较高,快排也有O(nlog)的复杂度,所以使用priority_queue结构来存储openlist,即先前给出的代码中的 priority_queue<pair<int, Point*>, vector<pair<int, Point*>>, cmp> OpenList

注意:事实上,在我的代码中并没有对closelist进行哈希表备份,主要是懒得改了,如果读者有需要的话可以在Astar.h中进行修改,这样就不需要函数InCloseList了。

②优化启发函数 F(N) = G(N)+W(N)*H(N)

参考文章: A* 算法及优化思路

在应用中就是动态加权,在H(N)较大时W(N)也应较大,当H(N)较小时W(N)也应较小,但这样修改的话,搜索出的路径并不是最优路径,但时间可以大大缩短;W(N)可以根据H(N)自行设定。

③优化邻域

参考:依据象限搜索及混合预计耗费的A*改进算法,包含8邻域及24邻域的改进
根据目标点与当前点的位置关系将邻域缩小一角,由于我的代码是基于8邻域的没测试24邻域,在8邻域下修改为5邻域时间缩短1/3左右(在测试的时候我将地图扩大到1000点左右为了效果更加明显)

④使用双向A*算法

并不是一个比较稳定的做法,后续如果使用到后再做补充
参考:双向A*搜索 | 双向启发式搜索(有几个关键问题做出了解答)

⑤其他优化方法

参考文章:【路径规划】A*算法方法改进思路简析

【搜索算法 3】 D *

D*Dynamic A* 的简写,其算法和A* 类似,不同的是,其代价的计算在算法运行过程中可能会发生变化。

D * 包含了下面三种增量搜索算法:
1.原始的D* 由Anthony Stentz发表。
2.Focussed D* 由Anthony Stentz发表,是一个增量启发式搜索算法,结合了A* 和原始D* 的思想。
3.D*Lite是由Sven Koenig和Maxim Likhachev基于LPA* 构建的算法。

所有三种搜索算法都解决了相同的基于假设的路径规划问题,包括使用自由空间假设进行规划。在这些环境中,机器人必须导航到未知地形中的给定目标坐标。它假设地形的未知部分(例如:它不包含障碍物),并在这些假设下找到从当前坐标到目标坐标的最短路径。

然后机器人沿着路径行进。当它观察到新的地图信息(例如以前未知的障碍物)时,它会将信息添加到其地图中,并在必要时将新的最短路径从其当前坐标重新添加到给定的目标坐标。它会重复该过程,直到达到目标坐标或确定无法达到目标坐标。在穿越未知地形时,可能经常发现新的障碍,因此重新计划需要很快。增量(启发式)搜索算法通过使用先前问题的经验来加速搜索当前问题,从而加速搜索类似搜索问题的序列。假设目标坐标没有改变,则所有三种搜索算法都比重复的A* 搜索更有效。

D* 及其变体已广泛用于移动机器人和自动车辆导航。当前系统通常基于D*Lite而不是原始D*Focussed D*

D * 算法实现伪代码

下述内容(伪代码部分),摘自https://blog.csdn.net/rezrezre/article/details/131008284 推荐去看,写的很不错

复制代码
  1. function Dstar(map, start, end):
  2. # map:m*n的地图,记录了障碍物。map[i][j]是一个state类对象
  3. # start:起点,state类对象
  4. # end: 终点,state类对象
  5. open_list = [ ] # 新建一个open list用于引导搜索
  6. insert_to_openlist(end0 open_list) # 将终点放入open_list中
  7. # 第一次搜索,基于离线先验地图找到从起点到终点的最短路径,注意从终点往起点找
  8. loop until start.t == close”):
  9. process_state() # D*算法核心函数之一
  10. end loop
  11. # 让机器人一步一步沿着路径走,有可能在走的过程中会发现新障碍物
  12. temp_p = start
  13. while (p != end) do
  14. if ( unknown obstacle found) then # 发现新障碍物, 执行重规划
  15. for new_obstacle in new_obstacles: # 对每个新障碍物调用modify_cost函数
  16. modify_cost( new_obstacle ) #(D*算法核心函数之一)
  17. end for
  18. do
  19. k_min = process_state()
  20. while not ( k_min >= temp_p.h or open_list.isempty() )
  21. continue
  22. end if
  23. temp_p = temp_p.parent
  24. end while

上述伪代码中核心函数为2个:modify_cost 和 process_state
下面是modify_cost的伪代码:

复制代码
  1. function modify_cost( new_obstacle ):
  2. set_cost(any point to new_obstacle ) = 10000000000 # 让“从任何点到障碍点的代价”和“从障碍点到任何点的代价” 均设置为无穷大,程序中使用超级大的数代替(考虑8邻接)
  3. if new_obstacle.t == "close" then
  4. insert(new_obstacle, new_obstacle.h ) # 放到open表中,insert也是d*算法中的重要函数之一
  5. end if

下面是 Process_state函数的伪代码:

复制代码
  1. function process_state( ):
  2. x = get_min_k_state(oepn_list) # 拿出openlist中获取k值最小那个state,这点目的跟A*是一样的,都是利用k值引导搜索顺序,但注意这个k值相当于A*算法中的f值(f=g+h, g为实际代价函数值,h为估计代价启发函数值),而且在D*中,不使用h这个启发函数值, 仅使用实际代价值引导搜索,所以其实硬要说,D*更像dijkstra,都是使用实际代价引导搜索而不用启发函数缩减搜索范围,D*这点对于后面发现新障碍物进行重新规划来说是必要的。
  3. if x == Null then return -1
  4. k_old = get_min_k(oepn_list) # 找到openlist中最小的k值,其实就是上面那个x的k值
  5. open_list.delete(x) # 将x从open list中移除, 放入close表
  6. x.t = "close" # 相当于放入close表,只不过这里不显式地维护一个close表
  7. # 以下为核心代码:
  8. # 第一组判断
  9. if k_old < x.h then # 满足这个条件说明x的h值被修改过,认为x处于raise状态
  10. for each_neighbor Y of X: #考虑8邻接neighbor
  11. if y.h<k_old and x.h> y.h + cost(y,x) then
  12. x.parent = y
  13. x.h = y.h + cost(x,y)
  14. end if
  15. end for
  16. end if
  17. # 第二组判断
  18. if k_old == x.h then
  19. for each_neighbor Y of X: #考虑8邻接neighbor
  20. if y.t== "new" or
  21. (y.parent == x and y.h !=x.h + cost(x,y) ) or
  22. (y.parent != x and y.h >x.h + cost(x,y)) then
  23. y.parent = x
  24. insert(y, x.h + cost(x,y))
  25. end if
  26. end for
  27. else: # 不满足k_old == x.h 那就是k_old < x.h
  28. for each_neighbor Y of X: #考虑8邻接neighbor
  29. if y.t == "new" or
  30. (y.parent == x and y.h !=x.h + cost(x,y) ) then
  31. y.parent = x
  32. insert(y, x.h + cost(x,y))
  33. else:
  34. if (y.parent != x and y.h >x.h + cost(x,y)) then
  35. x.k = x.h # 注意这行!没有这行会出现特定情况死循环。在查阅大量资料后,在wikipedia的d*算法页面中找到解决办法就是这行代码。网上大部分资料,包括d*原始论文里都是没这句的,不知道为啥
  36. insert(x, x.h)
  37. else:
  38. if (y.parent!=x and x.h>y.h+cost(y,x) and y.t= "close" and y.h>k_old then
  39. insert(y,y.h)
  40. end if
  41. end if
  42. end if
  43. end for
  44. end if
  45. return get_min_k(oepn_list)

C++ 实现

< 待补充 >
由于D* 强调的是在障碍地图改变后防止重新全局规划而对A*的调整,“障碍物改变”这一情况在仿真中不太好搞,所以先不做这部分的处理。

【搜索算法 4】 LPA *

< 待补充 >

【采样算法 1】 RRT 与 RRT*

< 待补充 >
相对于上述提到的所有算法,RRT更好理解,整体步骤就是【 采样->找最近点连线->判断是否通过障碍物->取点 】循环进行这个步骤,直到达到目标点范围内,然后连接最短的路径。可以根据原理就可以看出,RRT并不能得出最优路径,只是能找到一条路径,但是谁让它快啊,很多时候最优并不是必须,快速反应才比较好。
不过RRT也有局限,在狭窄道路生成的较慢,毕竟路线中只要有障碍物就不采用这个点的策略确实会导致这个问题。

【采样算法 2】 PRM

< 待补充 >

局部路径规划

< 待补充 >

人工势场法

< 待补充 >

DWA

< 待补充 >

TEB

< 待补充 >

相关阅读

注意!!!

站点域名更新!!!部分文章图片等由于域名问题无法显示!!!

通知!!!

站点域名更新!!!部分文章图片等由于域名问题无法显示!!!