1 算法思路
1、Dijkstra算法与A*算法
(1)Dijkstra算法(贪心策略 + 优先队列):
-
集合S:已确定的顶点集合,初始只含源点s。
-
集合T:尚未确定的顶点集合。
-
算法反复从集合T中选择当前到源点s最近的顶点u,将u加入集合S,然后对所有从u发出的边进行松弛操作。
-
算法步骤:维护一个优先队列,将集合T中的顶点到源点s的距离,作为这些点的优先级,距离越低,优先级越高。那么只要从优先队列中取出队首元素,即可获得当前离源点s最近的顶点。
附注:松弛操作:若d(u) + w(u, v) < d(v),就更新d(v) = d(u) + w(u, v),其中w(u, v)表示边的权重,d(u)表示从顶点u到达源点s的最短距离(目前已知)
(2)A*算法(启发式搜索):
-
F = G + H,G表示从起点到某中间节点的移动代价,启发函数H(Heuristic)表示从某中间节点到终点的估算移动代价,当H始终为0,算法就退化为Dijkstra算法。
-
曼哈顿距离
-
欧式距离
-
-
数据结构设计:
节点数据类型:
class Node
{
public:
Node(int x, int y, bool c, Node* fa = nullptr) : _x(x), _y(y), _father(fa), _is_closed(c){};
Node() = default;
~Node() = default;
int get_f() { return _f; }
void set_f(int f) { _f = f; }
int get_g() { return _g; }
void set_g(int g) { _g = g; }
int get_h() { return _h; }
void set_h(int h) { _h = h; }
int get_x() { return _x; }
int get_y() { return _y; }
Node* get_father() { return _father; }
void set_father(Node* father) { _father = father; }
int get_state() { return _state; }
void set_state(int state) { _state = state; }
void set_is_closed(bool flag) { _is_closed = flag; }
bool get_is_closed() { return _is_closed; }
void set_is_in_openlist(bool flag) { _is_in_openlist = flag; }
bool get_is_in_openlist() { return _is_in_openlist; }
private:
int _x; ///< Node x coordinate
int _y; ///< Node y coordinate
Node* _father; ///< Previous node of the node
int _g; ///< Cost g
int _h; ///< Cost h
int _f; ///< Cost f
int _state; ///< Node state(OBSTACLE / ACCESS / RESULT_NODE)
bool _is_closed; ///< Whether the node is closed
bool _is_in_openlist; ///< Whether the node is in the openlist
};
网格数据类型:
class GridMap
{
public:
GridMap() = default;
~GridMap() = default;
void constructMap(); ///< Construct map and node information
void printMap(); < Print map information
std::vector<std::vector<Node>> get_map() { return _map; }
private:
std::vector<std::vector<Node>> _map;
};
-
算法步骤:
2 实验结果
(1)测试用例1:起点固定为左下角,终点固定为右上角。
原始Map(*为可访问的节点,$为障碍节点),图大小为25 x 25(由MAP_SIZE参数进行控制),其中有30%的节点随机作为障碍节点(由OBSTACLE_RATIO参数进行控制):
搜索到的路径为: (24, 0) (24, 1) (23, 2) (24, 3) (23, 4) (24, 5) (24, 6) (24, 7) (24, 8) (23, 9) (22, 10) (21, 11) (20, 12) (19, 13) (18, 14) (17, 14) (16, 15) (15, 16) (14, 16) (13, 17) (12, 18) (11, 19) (10, 20) (9, 21) (8, 21) (7, 22) (6, 22) (5, 22) (4, 22) (3, 23) (2, 23) (1, 24) (0, 24) 结果Map(*为可访问的节点,$为障碍节点,#为结果路径节点):
(2)测试用例2: 原始图(*为可访问的节点,$为障碍节点),图大小为25 x 25,其中有30%的节点随机作为障碍节点:
结果路径:(24, 0) (24, 1) (24, 2) (24, 3) (24, 4) (23, 5) (23, 6) (24, 7) (23, 8) (24, 9) (24, 10) (23, 11) (22, 12) (21, 13) (20, 14) (21, 15) (22, 16) (21, 17) (20, 18) (19, 19) (18, 20) (17, 21) (16, 22) (15, 22) (14, 22) (13, 22) (12, 22) (11, 23) (10, 23) (9, 23) (8, 23) (7, 23) (6, 23) (5, 23) (4, 23) (3, 23) (2, 23) (1, 24) (0, 24) 结果Map(*为可访问的节点,$为障碍节点,#为结果路径节点):
(3)测试用例3:测试路径不存在的情况,从左下角起点无法到达右上角终点,则会输出“Path does not exists”。
3 代码示例
(1)include/project/AStar.hpp:A*算法步骤的主要接口和参数定义,其中节点类型有三种:ACCESS表示可通行节点,用"* "表示;OBSTACLE表示障碍节点,用"$ "表示;RESULT_NODE表示搜索到的结果节点,用"# "表示。节点间上下左右通行的代价(DIRECT_COST)初始化为10,斜向通行的代价(OBLIQUE_COST)初始化为15;网格大小(MAP_SIZE)初始化为25;随机障碍节点占所有节点的比例(OBSTACLE_RATIO)初始化为0.3。
/**
* @file AStar.hpp
* @author Qinyi Deng (remoa@qq.com)
* @brief
* @version 0.1
* @date 2023-02-09
*
* @copyright Copyright (c) 2023 SCUT
*
*/
#pragma once
#include <queue>
#include <vector>
const int OBSTACLE = 1; ///< Used in _state attribute in node
const int ACCESS = 2; ///< Used in _state attribute in node
const int RESULT_NODE = 3; ///< Used in _state attribute in node
const int INITIALIZE_COST = 0x7fffffff; ///< Initialize cost
const int MAP_SIZE = 25; ///< 25 * 25 's map
const int DIRECT_COST = 10; ///< The path to walk up, down, left and right, costs 10
const int OBLIQUE_COST = 15; ///< The path of walking obliquely, costs 15
const double OBSTACLE_RATIO = 0.3; < Scale of obstacle nodes in the figure
namespace assignment2 {
/**
* @brief Node class information
*/
class Node
{
public:
Node(int x, int y, Node* fa = nullptr) : _x(x), _y(y), _father(fa){};
Node() = default;
~Node() = default;
int get_f() { return _f; }
void set_f(int f) { _f = f; }
int get_g() { return _g; }
void set_g(int g) { _g = g; }
int get_h() { return _h; }
void set_h(int h) { _h = h; }
int get_x() { return _x; }
int get_y() { return _y; }
Node* get_father() { return _father; }
void set_father(Node* father) { _father = father; }
int get_state() { return _state; }
void set_state(int state) { _state = state; }
void set_is_closed(bool flag) { _is_closed = flag; }
bool get_is_closed() { return _is_closed; }
void set_is_in_openlist(bool flag) { _is_in_openlist = flag; }
bool get_is_in_openlist() { return _is_in_openlist; }
bool operator<(const Node& a) const { return this->_f < a._f; }
private:
int _x; ///< Node x coordinate
int _y; ///< Node y coordinate
Node* _father; ///< Previous node of the node
int _g; ///< Cost g
int _h; ///< Cost h
int _f; ///< Cost f
int _state; ///< Node state(OBSTACLE / ACCESS / RESULT_NODE)
bool _is_closed; ///< Whether the node is closed
bool _is_in_openlist; ///< Whether the node is in the openlist
};
///
/**
* @brief GridMap class information
*/
class GridMap
{
public:
GridMap() = default;
~GridMap() = default;
std::vector<std::vector<Node>> _map;
void constructMap(); ///< Construct map and node information
void printMap(); < Print map information
};
class AStar
{
public:
AStar() = default;
~AStar() = default;
std::priority_queue<Node*> _openlist;
bool aStar(GridMap* grid_map); < A star main algorithm
bool checkBound(int x, int y); < Check out of bounds
bool addOpenlist(GridMap* grid_map, Node* node); < Add Nodes to openlist
bool checkInOpenList(GridMap* grid_map, Node* node, int x_offset, int y_offset,
int cost); < Judge whether nodes in openlist and update cost function
void printPath(GridMap* grid_map); < Print path node information
};
}; // namespace assignment2
(2)src/AStar.cpp:A*算法的主要算法步骤实现。
/**
* @file AStar.cpp
* @author Qinyi Deng (remoa@qq.com)
* @brief
* @version 0.1
* @date 2023-02-09
*
* @copyright Copyright (c) 2023 SCUT
*
*/
#include <cstdlib>
#include <ctime>
#include <iostream>
#include <random>
#include "project/AStar.hpp"
using namespace assignment2;
/**
* @brief construct map and node information
*/
void GridMap::constructMap()
{
std::vector<Node> tmp_list(MAP_SIZE);
_map.resize(MAP_SIZE, tmp_list);
std::default_random_engine e;
std::uniform_int_distribution<int> u(0, MAP_SIZE);
e.seed(time(0));
for (int i = 0; i < MAP_SIZE; i++) {
for (int j = 0; j < MAP_SIZE; j++) {
_map[i][j] = Node(i, j);
if (u(e) % MAP_SIZE < (int) (MAP_SIZE * OBSTACLE_RATIO)) {
_map[i][j].set_state(OBSTACLE);
} else {
_map[i][j].set_state(ACCESS);
}
_map[i][j].set_h((i + MAP_SIZE - 1 - j) * DIRECT_COST);
_map[i][j].set_g(INITIALIZE_COST);
_map[i][j].set_f(INITIALIZE_COST);
_map[i][j].set_is_in_openlist(false);
_map[i][j].set_is_closed(false);
}
}
_map[MAP_SIZE - 1][0].set_state(ACCESS);
_map[MAP_SIZE - 1][0].set_g(0);
_map[MAP_SIZE - 1][0].set_f(_map[MAP_SIZE - 1][0].get_h());
_map[0][MAP_SIZE - 1].set_state(ACCESS);
}
/**
* @brief print map information
*/
void GridMap::printMap()
{
for (int i = 0; i < MAP_SIZE; i++) {
for (int j = 0; j < MAP_SIZE; j++) {
if (_map[i][j].get_state() == ACCESS) {
std::cout << "* ";
} else if (_map[i][j].get_state() == OBSTACLE) {
std::cout << "$ ";
} else if (_map[i][j].get_state() == RESULT_NODE) {
std::cout << "# ";
}
}
std::cout << std::endl;
}
}
/**
* @brief A star algorithm
* @return true find path
* @return false path does not exist.
*/
bool AStar::aStar(GridMap* grid_map)
{
Node* start = &(grid_map->_map[MAP_SIZE - 1][0]);
_openlist.push(start);
while (!_openlist.empty()) {
Node* head = _openlist.top();
_openlist.pop();
if (addOpenlist(grid_map, head)) {
return true;
}
head->set_is_closed(true);
head->set_is_in_openlist(false);
}
return false;
}
/**
* @brief Check out of boundary
* @param x x coordinate of node
* @param y y coordinate of node
* @return true Within the boundary
* @return false Outside the boundary
*/
bool AStar::checkBound(int x, int y)
{
if (x >= 0 && x < MAP_SIZE && y >= 0 && y < MAP_SIZE) {
return true;
}
return false;
}
/**
* @brief judge whether nodes in openlist and update cost function
* @param grid_map grid_map
* @param node input node
* @param x_offset x_offset
* @param y_offset y_offset
* @param cost cost(DIRECT_COST / OBLIQUE_COST)
* @return true Find path
* @return false We haven't found the path yet
*/
bool AStar::checkInOpenList(GridMap* grid_map, Node* node, int x_offset, int y_offset, int cost)
{
if (checkBound(node->get_x() + x_offset, node->get_y() + y_offset)) {
Node* node_ptr = &(grid_map->_map[node->get_x() + x_offset][node->get_y() + y_offset]);
if (node_ptr->get_state() != OBSTACLE) {
if ((node_ptr->get_is_in_openlist() && node->get_g() + cost < node_ptr->get_g())
|| (!node_ptr->get_is_in_openlist() && !node_ptr->get_is_closed())) {
node_ptr->set_g(node->get_g() + cost);
node_ptr->set_f(node_ptr->get_h() + node_ptr->get_g());
node_ptr->set_father(node);
if (!node_ptr->get_is_in_openlist()) {
node_ptr->set_is_in_openlist(true);
_openlist.push(node_ptr);
if (node_ptr->get_x() == 0 && node_ptr->get_y() == MAP_SIZE - 1) {
return true;
}
}
}
}
}
return false;
}
/**
* @brief Add Nodes to openlist
* @param grid_map grid_map
* @param node node information
* @return true find path
* @return false We haven't found the path yet
*/
bool AStar::addOpenlist(GridMap* grid_map, Node* node)
{
bool flag1 = false;
bool flag2 = false;
for (int i = -1; i <= 1; i++) {
for (int j = -1; j <= 1; j++) {
if (i == 0 && j == 0) {
continue;
}
if (abs(i) + abs(j) == 1) {
flag1 = checkInOpenList(grid_map, node, i, j, DIRECT_COST);
} else {
flag2 = checkInOpenList(grid_map, node, i, j, OBLIQUE_COST);
}
if (flag1 || flag2) {
return true;
}
}
}
return false;
}
/**
* @brief print path node information
* @param grid_map grid_map
*/
void AStar::printPath(GridMap* grid_map)
{
Node* end_ptr = &(grid_map->_map[0][MAP_SIZE - 1]);
std::vector<Node*> path_list;
while (end_ptr != nullptr) {
end_ptr->set_state(RESULT_NODE);
path_list.push_back(end_ptr);
end_ptr = end_ptr->get_father();
}
std::cout << "The result path is as follows:" << std::endl;
for (int i = path_list.size() - 1; i >= 0; i--) {
std::cout << "(" << path_list[i]->get_x() << ", " << path_list[i]->get_y() << ")"
<< " ";
}
std::cout << std::endl;
}
(3)test/src/AStar_test.cpp:测试用例生成和打印运行结果。
/**
* @file myMap_test.cpp
* @author Qinyi Deng (remoa@qq.com)
* @brief
* @version 0.1
* @date 2023-02-09
*
* @copyright Copyright (c) 2023 SCUT
*
*/
#include <gtest/gtest.h>
#include "project/AStar.hpp"
using namespace assignment2;
TEST(TmpAddTest, CheckValues)
{
// ASSERT_EQ(tmp::add(1, 2), 3);
EXPECT_TRUE(true);
}
int main(int argc, char** argv)
{
AStar* astar = new AStar();
GridMap* map = new GridMap();
map->constructMap();
map->printMap();
if (astar->aStar(map)) {
astar->printPath(map);
std::cout << "The map and its result path are as follows:" << std::endl;
map->printMap();
} else {
std::cout << "Path does not exist!\n";
}
return 0;
// ::testing::InitGoogleTest(&argc, argv);
// return RUN_ALL_TESTS();
}
(4).clang-format的配置文件,对代码进行格式化。首先在Settings.json中将Clang_format_style配置修改为file。
然后通过ctrl+shift+p在VSCode中修改配置文件settings.json,加入以下代码:
"clang-format.assumeFilename": "./.clang-format",
"[cpp]": {
"editor.defaultFormatter": "xaver.clang-format"
},
最后添加.clang-format文件:
---
Language: Cpp
BasedOnStyle: Google
ColumnLimit: 100
AlignConsecutiveAssignments: false
AlignConsecutiveDeclarations: false
AllowAllParametersOfDeclarationOnNextLine: false
AllowShortFunctionsOnASingleLine: InlineOnly
AllowShortIfStatementsOnASingleLine: false
AllowShortLoopsOnASingleLine: false
BinPackArguments: true
BinPackParameters: true
BraceWrapping:
AfterClass: true
AfterControlStatement: false
AfterEnum: true
AfterFunction: true
AfterNamespace: false
AfterObjCDeclaration: false
AfterStruct: true
AfterUnion: true
AfterExternBlock: false
BeforeCatch: false
BeforeElse: false
IndentBraces: false
SplitEmptyFunction: true
SplitEmptyRecord: true
SplitEmptyNamespace: true
BreakBeforeBinaryOperators: All
BreakBeforeBraces: Custom
DerivePointerAlignment: false
SpaceAfterCStyleCast: true
Standard: c++20
(5).clang-tidy的配置文件:对代码进行静态分析,检查违反代码规范的代码模式。首先,在vscode下载好插件后,select a kit clang gcc中选择clang。然后通过ctrl+shift+p在VSCode中修改配置文件settings.json,加入以下代码:
"clang-tidy.buildPath": "build/compile_commands.json"
最后构造.clang-tidy文件进行配置:
---
Checks: '*,-fuchsia-*,-google-*,-zircon-*,-abseil-*,-modernize-use-trailing-return-type,-llvm-*,-llvmlibc-*'
CheckOptions: [{ key: misc-non-private-member-variables-in-classes, value: IgnoreClassesWithAllMemberVariablesBeingPublic }]
WarningsAsErrors: '*'
HeaderFilterRegex: ''
FormatStyle: none
(6)Doxygen插件配置:下载好插件后,通过ctrl+shift+p在VSCode中修改配置文件settings.json,加入以下代码:
"doxdocgen.generic.authorEmail": "remoa@qq.com",
"doxdocgen.generic.authorName": "Qinyi Deng",
"doxdocgen.file.copyrightTag": [
"@copyright Copyright (c) {year} SCUT"
],
"doxdocgen.file.fileOrder": [
"file",
"author",
"brief",
"version",
"date",
"empty",
"copyright",
"empty",
"custom",
],
"doxdocgen.generic.order": [
"brief",
"tparam",
"param",
"return"
],
"doxdocgen.generic.paramTemplate": "@param{indent:0} {param}{indent:4} param doc",
"doxdocgen.generic.returnTemplate": "@return {type} ",
"doxdocgen.generic.includeTypeAtReturn": true,
然后在文件开头/函数前输入/**,然后回车,会输出对应的注释段。
(7)cmake/SourcesAndHeaders.cmake文件设置相关参数
set(sources
src/AStar.cpp
)
set(exe_sources
src/AStar.cpp
${sources}
)
set(headers
include/project/AStar.hpp
)
set(test_sources
src/AStar_test.cpp
)