Algorithm improvement: graph heuristic search algorithm (A algorithm, A* algorithm)

heuristic search algorithm

Table of contents

concept

Algorithm A

A* algorithm


concept

Heuristically Search, also known as Informed Search, uses the heuristic information of the problem to guide the search, so as to reduce the scope of the search and reduce the complexity of the problem. This search process using heuristic information called heuristic search.

Think about this algorithm from the perspective of data structure and algorithm. Heuristic algorithm is also a kind of greedy algorithm. The selection of heuristic information is the selection of greedy strategy. Conversely, all greedy algorithms can also be regarded as heuristics in the state machine. search algorithm.

The function used to evaluate the importance of nodes is called the evaluation function, and its general form is

 

In the formula: g(x) is the actual cost paid from the initial node to node x; h(x) is the estimated cost of the optimal path from node x to the target node. The heuristic information is mainly reflected in h(x), and its form should be determined according to the characteristics of the problem.

Although the heuristic search is expected to reach the target node quickly, it takes some time to evaluate the nascent node. Therefore, in the heuristic search, the definition of the estimation function is very important. If not properly defined, the above search algorithm may not be able to find the solution to the problem, and even if it finds the solution, it may not be optimal.

Algorithm A

concept

In the state space search, if each step uses the evaluation function f(n)=g(n)+h(n) to sort the nodes in the Open table, it is called the A algorithm. It is a heuristic search algorithm.

Algorithm type:

Global selection: Select the one with the smallest evaluation function value from all the nodes in the Open table to expand.

Local selection: Only select the one with the smallest evaluation function value from the newly generated child nodes for expansion.

Algorithm process

(global selection)

  • ① Put the initial node S0 into the Open table;
  • ②If the Open list is empty, the search fails and exits;
  • ③Take out the first node of the Open table, put it into the Closed table, and record this node as node n;
  • ④ If node n is the target node, the search is successful, a solution is obtained, and exit;
  • ⑤ Expand node n to generate a set of child nodes, and calculate the corresponding evaluation function value for child nodes that are neither in the Open table nor in the Closed table;
  • ⑥ Put the child nodes of node n into the Open table;
  • ⑦Arrange the nodes in the Open table according to the evaluation function value from small to large;
  • ⑧ Go to ②.

(local selection)

  • ① Put the initial node S0 into the Open table;
  • ②If the Open list is empty, the search fails and exits;
  • ③Take out the first node of the Open table, put it into the Closed table, and record this node as node n;
  • ④ If node n is the target node, the search is successful, a solution is obtained, and exit;
  • ⑤ Expand node n to generate a set of child nodes, and calculate the corresponding evaluation function value for child nodes that are neither in the Open table nor in the Closed table;
  • ⑥Arrange the child nodes of node n according to the evaluation function value from small to large, select the first element as a new node n, and transfer the remaining nodes into the open table;
  • ⑦Arrange the nodes in the Open table according to the evaluation function value from small to large;
  • ⑧Go to ④

From the analysis of the above algorithm, it can be found that if the evaluation function is equal to the node depth, it will degenerate into a breadth-first search.

A* algorithm

concept

In the A* algorithm, heuristic information is represented by a special evaluation function f*: f*(x)=g*(x)+h*(x)

In the formula: g*(x) is the cost paid for the best path from the initial node to node x; h*(x) is the cost paid for the best path from x to the target node; f*(x) is The total cost of the best path from the initial node to the goal node via node x.

Based on the above definitions of g*(x) and h*(x), the following restrictions are imposed on g(x) and h(x) in the heuristic search algorithm:

①g(x) is an estimate of g*(x), and g(x)>0;

②h(x) is the lower bound of h*(x), that is, h(x)≤h*(x) for any node x.

The ordered search algorithm that satisfies the above conditions is called the A* algorithm.

For a certain search algorithm, when the optimal path exists, it can be found, then the algorithm is said to be admissible. It can be proved that the A* algorithm is an admissible algorithm. That is to say, for the ordered search algorithm, when the condition of h(x)≤h*(x) is satisfied, as long as the optimal path exists, the path can be found.

The closer the distance estimate is to the actual value, the better the evaluation function achieves

For example, for a geometric road network, the Manhattan distance between two nodes can be taken as the distance estimate, that is, f=g(n) + (abs(dx - nx) + abs(dy - ny)); so that the evaluation function f(n ) In the case of a certain g(n), it will be more or less restricted by the estimated distance h(n), the node is close to the target point, the h value is small, and the f value is relatively small, which can ensure the shortest path search Proceed in the direction of the end point. It is significantly better than the Dijkstra algorithm's undirected search around.


Algorithm process

The simple map is shown in the figure, where the green square is the starting point (indicated by A), the middle blue is the obstacle, and the red square (indicated by B) is the destination. In order to represent the map with a two-dimensional array, We divide the map into small squares.

There are many applications of two-dimensional arrays in games. For example, the basic principle of Snake and Tetris is to move cubes. The map of large games is to spread various "landforms" on such small cubes.


1. Start from the starting point A, store it as a pending square into an "open list", and the open list is a list of squares waiting to be checked.

2. Find reachable squares around the starting point A, put them into the "open list", and set their "parent square" to A.

3. Delete the starting point A from the "open list", and add the starting point A to the "closed list", and all the cells in the "closed list" do not need to be checked again

The square with a light green stroke in the figure indicates that it has been added to the "open list" and is waiting for inspection. The starting point A of the light blue stroke indicates that it has been placed in the "close list", and it does not need to be checked again.

Find the most reliable block from the "Open List", what is the most reliable? They are calculated by the formula F=G+H.

We assume that the cost of moving a grid horizontally is 10. For the convenience of calculation, the cost of moving a grid in an oblique direction is 14. In order to show how to calculate FGH more intuitively, the upper left corner of the figure in the figure represents F, the lower left corner represents G, and the right The lower corner means H. See if it is the same as what you think?

Select the square C with the lowest F value from the "open list" (the square to the right of the green starting square A), and process it as follows:

4. Remove it from "Open List" and put it in "Close List".

5. Check all its adjacent and reachable squares (obstacles and "closed list" squares are not considered). If these squares are not already in the "open list", add them to the "open list", Calculate the G, H and F values ​​of these squares, and set their "parent square" to C.

6. If an adjacent square D is already in the "open list", check if the G value will be lower if a new path (that is, the path through C) is used to reach it, if the new G value is more low, then change its "parent square" to the currently selected square C, and then recalculate its F value and G value (the H value does not need to be recalculated, because for each square, the H value is unchanged ). If the new G value is relatively high, it means that it is not a wise choice to go through C and then reach D, because it needs a longer road, and we do nothing at this time.

As shown in the figure, we selected C because its F value is the smallest, we delete it from the "open list" and add it to the "close list". is the starting block, which has already been added to the "closed list", and is not considered. So there are only 4 candidate blocks around it. Let's take a look at the grid below C, its current G is 14, if To reach it via C, G will be 10 + 10, which is greater than 14, so we do nothing.

Then we continue to find the smallest F value from the "open list", but we find that the above and below C are both 54, what should we do at this time? At this time, we can choose any one, for example, we choose C below the square D.

The right side of D and the upper right are all walls, so I don’t consider it, but why is the lower right corner not added to the "open list"? Because if the block below C is not allowed to go, if you want to reach the block in the lower right corner of C Need to walk from the "corner of the square", set whether to allow this way in the program. (The example in the picture does not allow this way)

In this way, we find the one with the smallest F value from the "open list", remove it from the "open list", and add it to the "close list". Then continue to find the reachable squares around it, and so on.. .

So when will it stop? ——When we find that the target end block appears in the "Start List", it means that the path has been found.

how to get back the path

As shown in the figure above, except for the starting block, every block that was or is still in the "open list" has a "parent block", through which the "parent block" can be indexed to the original "starting block", This is the path.

Code


#include <vector>
#include <list>
#include <iostream>
#include <math>
 
const int kCost1=10; //直移一格消耗
const int kCost2=14; //斜移一格消耗
 
struct Point
{
	int x,y; //点坐标,这里为了方便按照C++的数组来计算,x代表横排,y代表竖列
	int F,G,H; //F=G+H
	Point *parent; //parent的坐标,这里没有用指针,从而简化代码
	Point(int _x,int _y):x(_x),y(_y),F(0),G(0),H(0),parent(NULL)  //变量初始化
	{
	}
};
 
 
class Astar
{
public:
	void InitAstar(std::vector<std::vector<int>> &_maze);
	std::list<Point *> GetPath(Point &startPoint,Point &endPoint,bool isIgnoreCorner);
 
private:
	Point *findPath(Point &startPoint,Point &endPoint,bool isIgnoreCorner);
	std::vector<Point *> getSurroundPoints(const Point *point,bool isIgnoreCorner) const;
	bool isCanreach(const Point *point,const Point *target,bool isIgnoreCorner) const; //判断某点是否可以用于下一步判断
	Point *isInList(const std::list<Point *> &list,const Point *point) const; //判断开启/关闭列表中是否包含某点
	Point *getLeastFpoint(); //从开启列表中返回F值最小的节点
	//计算FGH值
	int calcG(Point *temp_start,Point *point);
	int calcH(Point *point,Point *end);
	int calcF(Point *point);
private:
	std::vector<std::vector<int>> maze;
	std::list<Point *> openList;  //开启列表
	std::list<Point *> closeList; //关闭列表
};


 
void Astar::InitAstar(std::vector<std::vector<int>> &_maze)
{
	maze=_maze;
}
 
int Astar::calcG(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==NULL?0:point->parent->G; //如果是初始节点,则其父节点是空
	return parentG+extraG;
}
 
int Astar::calcH(Point *point,Point *end)
{
	//用简单的欧几里得距离计算H,这个H的计算是关键,还有很多算法,没深入研究^_^
	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::calcF(Point *point)
{
	return point->G+point->H;
}
 
Point *Astar::getLeastFpoint()
{
	if(!openList.empty())
	{
		auto resPoint=openList.front();
		for(auto &point:openList)
			if(point->F<resPoint->F)
				resPoint=point;
		return resPoint;
	}
	return NULL;
}
 
Point *Astar::findPath(Point &startPoint,Point &endPoint,bool isIgnoreCorner)
{
	openList.push_back(new Point(startPoint.x,startPoint.y)); //置入起点,拷贝开辟一个节点,内外隔离
	while(!openList.empty())
	{
		auto curPoint=getLeastFpoint(); //找到F值最小的点
		openList.remove(curPoint); //从开启列表中删除
		closeList.push_back(curPoint); //放到关闭列表
		//1,找到当前周围八个格中可以通过的格子
		auto surroundPoints=getSurroundPoints(curPoint,isIgnoreCorner);
		for(auto &target:surroundPoints)
		{
			//2,对某一个格子,如果它不在开启列表中,加入到开启列表,设置当前格为其父节点,计算F G H
			if(!isInList(openList,target))
			{
				target->parent=curPoint;
 
				target->G=calcG(curPoint,target);
				target->H=calcH(target,&endPoint);
				target->F=calcF(target);
 
				openList.push_back(target);
			}
			//3,对某一个格子,它在开启列表中,计算G值, 如果比原来的大, 就什么都不做, 否则设置它的父节点为当前点,并更新G和F
			else
			{
				int tempG=calcG(curPoint,target);
				if(tempG<target->G)
				{
					target->parent=curPoint;
 
					target->G=tempG;
					target->F=calcF(target);
				}
			}
			Point *resPoint=isInList(openList,&endPoint);
			if(resPoint)
				return resPoint; //返回列表里的节点指针,不要用原来传入的endpoint指针,因为发生了深拷贝
		}
	}
 
	return NULL;
}
 
std::list<Point *> Astar::GetPath(Point &startPoint,Point &endPoint,bool isIgnoreCorner)
{
	Point *result=findPath(startPoint,endPoint,isIgnoreCorner);
	std::list<Point *> path;
	//返回路径,如果没找到路径,返回空链表
	while(result)
	{
		path.push_front(result);
		result=result->parent;
	}
 
    // 清空临时开闭列表,防止重复执行GetPath导致结果异常
    openList.clear();
	closeList.clear();
 
	return path;
}
 
Point *Astar::isInList(const std::list<Point *> &list,const Point *point) const
{
	//判断某个节点是否在列表中,这里不能比较指针,因为每次加入列表是新开辟的节点,只能比较坐标
	for(auto p:list)
		if(p->x==point->x&&p->y==point->y)
			return p;
	return NULL;
}
 
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)) //如果点与当前节点重合、超出地图、是障碍物、或者在关闭列表中,返回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;
		}
	}
}
 
std::vector<Point *> Astar::getSurroundPoints(const Point *point,bool isIgnoreCorner) const
{
	std::vector<Point *> surroundPoints;
 
	for(int x=point->x-1;x<=point->x+1;x++)
		for(int y=point->y-1;y<=point->y+1;y++)
			if(isCanreach(point,new Point(x,y),isIgnoreCorner))
				surroundPoints.push_back(new Point(x,y));
	
	return surroundPoints;
}

using namespace std;
 
int main()
{
	//初始化地图,用二维矩阵代表地图,1表示障碍物,0表示可通
	vector<vector<int>> maze={
		{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(maze);
 
	//设置起始和结束点
	Point start(1,1);
	Point end(6,10);
	//A*算法找寻路径
	list<Point *> path=astar.GetPath(start,end,false);
	//打印
	for(auto &p:path)
		cout<<'('<<p->x<<','<<p->y<<')'<<endl;
 
	system("pause");
	return 0;
}

Guess you like

Origin blog.csdn.net/qq_32378713/article/details/128048823