A*算法学习笔记

这篇具有很好参考价值的文章主要介绍了A*算法学习笔记。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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;
};
  • 算法步骤:

A*算法学习笔记
图1 算法流程步骤

2 实验结果

(1)测试用例1:起点固定为左下角,终点固定为右上角。

A*算法学习笔记
图2 测试用例1整体运行结果截图

原始Map(*为可访问的节点,$为障碍节点),图大小为25 x 25(由MAP_SIZE参数进行控制),其中有30%的节点随机作为障碍节点(由OBSTACLE_RATIO参数进行控制):

A*算法学习笔记
图2 测试用例1中随机生成的25*25个节点大小的初始网格

搜索到的路径为: (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(*为可访问的节点,$为障碍节点,#为结果路径节点):

A*算法学习笔记
图3 测试用例1中红色线为A*算法搜索到的路径

(2)测试用例2: 原始图(*为可访问的节点,$为障碍节点),图大小为25 x 25,其中有30%的节点随机作为障碍节点:

A*算法学习笔记
图4 测试用例2中随机生成的25*25个节点大小的初始网格

 结果路径:(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(*为可访问的节点,$为障碍节点,#为结果路径节点):

A*算法学习笔记
图5 测试用例2中红色线表示A*算法搜索到的路径

 (3)测试用例3:测试路径不存在的情况,从左下角起点无法到达右上角终点,则会输出“Path does not exists”。

A*算法学习笔记
图6 测试用例3中路径不存在的情况

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。

A*算法学习笔记
图7 vscode中配置clang-format

 然后通过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文件设置相关参数文章来源地址https://www.toymoban.com/news/detail-510686.html

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
)

到了这里,关于A*算法学习笔记的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包