搜索过程
广度优先搜索(BFS)算法与Dijsktra算法的结合,可以得出最短的路径。
将地图信息通过划分为方形或者其他多边形格子的方法进行表示,便于利用二维数组存放地图信息,每个格子有可行和不可行两种状态;每个方格用节点的形式表示(节点可以放置在形状内的任何位置 - 在中心或沿边缘),以便于用于任何形状的格子中的情况。
如上图所示,绿色格子为起点,红色格子为终点,蓝色格子为障碍物
评价函数
移动代价
横向/纵向移动代价设为 10
对角线移动代价设为14
主要计算三个值F、G及H
F = G + H
F:从初始块经由n个块后到目标块的最小代价估计
G:表示从起点s到点n的实际成本
G(s) = G(s的父节点) + 10或14(横向移动或者纵向移动)
H:表示从节点n到目标T的估算成本(试探法)
利用曼哈顿方法计算: 计算当前方格n通过横向或者纵向移动到T的节点数 * 10
d = |x1 - x2| + |y1 - y2| * 10
欧式距离计算: 计算n与T的欧式距离
步骤(参考)
设置一个 open list(待检查列表) 及 close list(无需检查列表)
-
从起点开始,将其存入open list中,open list中的方格可能是路径会通过的,也可能是路径不会通过的方格。
-
寻找起点周围所有可以到达的节点(相邻节点),忽略障碍方格;并将这些相邻节点存入open list中并且这些节点的父节点均为起点。
-
从open list中将起点取出,存入 close list中,如下图所示:![[Pasted image 20230426120659.png]]图中绿色方块为起点,其边框为蓝色,表示该方块已存入close list中,并且其周围的8个方块均在open list中,且指针指向中间的绿色方块,表示父节点为绿色方块
-
继续遍历open list中的节点,并重复下述过程。
- 计算每个节点的F值,选择F值的节点n作为当前处理的节点
- 对于当前节点n,与其相邻的所有节点(b,c,d等)如下操作:
- 如果节点b是不可到达(unreachable)或者在close list中,则不进行任务操作
- 如果节点c不在open list中,则将其加入open list中,并将当前节点n设为c 的父代,计算节点b的F、G、H值
- 若节点d在open list中,则检查节点n到节点d的路径是否更优(若该路径的g值更小,则说明其更优);若该路径更优,则将节点d的父节点(假设为e)设置为当前节点,并重新计算节点e的F、G值
- 将节点n存入 close list中
-
程序运行结束条件(二者满足其一)
- 终点在close list中
- 无法找到重点,且open list为空
-
得到最优路径:从终点开始,每个节点沿着父节点移动,直达到达起点
流程图
代码
A-star.h
#pragma once
#include<vector>
#include<list>using namespace std;const int kCost1 = 10; // 横向、纵向移动代价
const int kCost2 = 14; // 对角线移动代价struct Point
{int x, y; // 点坐标,x为横,y为纵坐标int F, G, H; // F = G + HPoint* parent; // 父代坐标Point(int _x, int _y) : x(_x), y(_y), F(0), G(0), H(0), parent(nullptr) {} // 初始
};class Astar
{
public:void InitAstar(vector<vector<int>>& _maze);list<Point*> GetPath(Point &starPoint, Point &endPoint, bool isIgnoreCorner);
private:Point* findPath(Point &startPoint, Point &endPoint, bool isIgnoreCorner);vector<Point*> getSurrentPoints(const Point* point, bool isIgnoreCorner) const;bool isCanreach(const Point *point, const Point *target, bool isIgnoreCorner) const; // 判断某点是否可以用于下一步判断Point *isInList(const list<Point *> &list, const Point *point) const; // 判断open_list/close_list中是否包含某点Point* getLeastFpoint(); // 从open_list中返回F值最小的节点// 计算 F G Hint calG(Point *temp_start, Point *point);int calH(Point *point, Point *end);int calF(Point *point);vector<vector<int>> maze; // 存放地图的二维数组list<Point*> openList; // open_list 列表list<Point*> closeList; // close_list 列表};
A-star.cpp
#include "A-star.h"void Astar::InitAstar(vector<vector<int>>& _maze)
{maze = _maze;
}list<Point*> Astar::GetPath(Point& starPoint, Point& endPoint, bool isIgnoreCorner)
{Point* res = findPath(starPoint, endPoint, isIgnoreCorner);list<Point*> path;// 返回路径while (res){path.push_back(res);res = res->parent;}// 情况open list和close listopenList.clear();closeList.clear();return path;
}Point* Astar::findPath(Point& startPoint, Point& endPoint, bool isIgnoreCorner)
{openList.push_back(new Point(startPoint.x, startPoint.y)); // open_list中存入七点,并开辟一个新的下一个节点while (!openList.empty()){auto curPoint = getLeastFpoint(); // 找到最小F值的节点openList.remove(curPoint); // 从open list中将当前节点移出closeList.push_back(curPoint); // 将当前节点存入close list中// 1.找到当前节点相邻的可以通过的格子auto surroundPoints = getSurrentPoints(curPoint, isIgnoreCorner);for (auto& target : surroundPoints){// 2. 对某一个格子进行判断, 若其不在open list中,则加入到open list中, 并设其为父节点 计算其 F G H值if (!isInList(openList, target)){target->parent = curPoint;target->G = calG(curPoint, target);target->H = calH(target, &endPoint);target->F = calF(target);openList.push_back(target);}// 3. 若其在open list中,则计算其G值并 与当前节点进行比较,若其G值大,则不进行任何操作, 否则设置它的父节点为当前节点,并更新G F else{int tempG = calG(curPoint, target);if (tempG < target->G){target->parent = curPoint;target->G = tempG;target->F = calF(target);}}Point* resPoint = isInList(openList, &endPoint);if (resPoint)return resPoint;}}return nullptr;
}vector<Point*> Astar::getSurrentPoints(const Point* point, bool isIgnoreCorner) const
{vector<Point*> surroundPoints;for (int x = point->x - 1; x <= point->x + 1; ++x)for (int y = point->y - 1; y <= point->y; ++y)if (isCanreach(point, new Point(x, y), isIgnoreCorner))surroundPoints.push_back(new Point(x, y));return surroundPoints;
}bool Astar::isCanreach(const Point* point, const Point* target, bool isIgnoreCorner) const
{/*if (target->x < 0 || target->x > maze.size() - 1 || target->y < 0 || target->y > maze[0].size() - 1 || maze[target->x][target->y] == 1 || target->x == point->x && target->y == point->y || isInList(closeList, target))return false;*/if (target->x<0 || target->x>maze.size() - 1|| target->y<0 || target->y>maze[0].size() - 1|| maze[target->x][target->y] == 1|| target->x == point->x && target->y == point->y|| isInList(closeList, target)) //如果点与当前节点重合、超出地图、是障碍物、或者在关闭列表中,返回falsereturn false;else{只有对角线才能进行搜索//if (abs(point->x - target->x) + abs(point->y - target->y) == 1)// return true;//else//{// // 对角线需要判断是否会遇到障碍// if (maze[point->x][target->y] == 0 && maze[target->x][point->y] == 0)// return true;// else// return isIgnoreCorner;//}return true;}
}// 判断某个节点是否在列表中
Point* Astar::isInList(const list<Point*>& list, const Point* point) const
{for (auto p : list)if (p->x == point->x && p->y == point->y)return p;return nullptr;
}Point* Astar::getLeastFpoint()
{if (!openList.empty()){auto resPoint = openList.front();for (auto& point : openList){if (point->F < resPoint->F){resPoint = point;}}return resPoint;}return nullptr;
}int Astar::calG(Point* temp_start, Point* point)
{int extraG = (abs(point->x - temp_start->x) + abs(point->y - temp_start->y)) == 1 ? kCost1 : kCost2;int parentG = point->parent == nullptr ? 0 : point->parent->G; // 如果为初始点, 则其父节点为空return parentG + extraG;
}int Astar::calH(Point* point, Point* end)
{// 根据曼哈顿算法进行计算return (abs(point->x - end->x) + abs(point->y - end->y)) * kCost1;//return sqrt((double)(end->x - point->x) * (double)(end->x - point->x) + (double)(end->y - point->y) * (double)(end->y - point->y)) * kCost1;
}int Astar::calF(Point* point)
{return point->G + point->H;
}
main.cpp
#include<iostream>
#include "A-star.h"
#include "A-star2.h"bool InPath(const int &row, const int &col, const list<Point *> &path)
{for (const auto& p : path){if (row == p->x || col == p->y){return true;}}return false;
}int main()
{// 初始化地图,用二维矩阵代表地图,1表示障碍物,0表示可通vector<std::vector<int>> map = { {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},{1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1},{1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1},{1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1},{1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1},{1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1},{1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1},{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1} };Astar astar;astar.InitAstar(map);// 设置起点和目标点Point start(1, 1);Point end(6, 10);// 寻找路径list<Point*> path = astar.GetPath(start, end, false);// 打印路径for (auto& p : path){cout << "(" << p->x << ", " << p->y << ")";}cout << endl;for (int row = 0; row < map.size(); ++row) {for (int col = 0; col < map[0].size(); ++col) {if (InPath(row, col, path)) {if (map[row][col] != 0) {cout << "e ";}else {cout << "* ";}}else {cout << map[row][col] << " ";}}cout << endl;}}
参考链接:
https://www.gamedev.net/reference/articles/article2003.asp
https://blog.csdn.net/A_L_A_N/article/details/81392212
https://zhuanlan.zhihu.com/p/590786438