搜索过程
广度优先搜索(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 + H
Point* 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 H
int 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 list
openList.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)) //如果点与当前节点重合、超出地图、是障碍物、或者在关闭列表中,返回false
return 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