[学习笔记]A星寻路算法实例

A星寻路算法的实现,单击左键编辑障碍物,单击右键清除障碍物,双击右键开始自动寻路。

AStar.h:

#pragma once
#include <windows.h>
#include <vector>
#define F_H_WHITE  0x0004 | 0x0002 | 0x0001 | 0x0008
#define B_H_WHITE  0x0010|0x0020|0x0040|0x0080
#define F_H_YELLOW 0x0002|0x0004|0x0008
#define F_H_GREEN  0x0002|0x0008
#define F_RED      0x0004       
using std::vector;
class CAStar
{
public:
	CAStar();
	~CAStar();
public:
	//表的节点结构体
	typedef struct _NODE
	{
		COORD stcPos;		//当前点的坐标
		COORD stcPrtPos;	//上一个点的坐标
		int F;				//移动损耗
		int G;				//估算距离
		int H;				//G+H
	}NODE,*PNOED;
public:
	//寻路函数,获取最短路径
	bool GetPath(_Out_ vector<COORD>& vecPath);
	//设置控制台信息
	HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
	void SetConsoleInfo();
	//描点画图函数
	void WriteChar(int x, int y, wchar_t* pszChar, WORD wArr);
	//画地图
	void DrawMap();
	//编辑地图
	void EditMap();
private:
	COORD m_Begin;			//起始点
	COORD m_End;			//终止点
	int m_Map[40][40];		//地图
	vector<NODE> m_vecOpen;	//Open表
	vector<NODE> m_vecClose;//Close表
};


AStar.cpp:

#include "stdafx.h"
#include "AStar.h"

CAStar::CAStar()
{
	m_Begin.X = 10;
	m_Begin.Y = 10;
	m_End.X = 30;
	m_End.Y = 30;
}
CAStar::~CAStar()
{
}
//************************************************************
// 函数名称:	GetPath
// 函数说明:	寻路函数,获取最短路径
// 作	者:	Dylan
// 时	间:	2015/08/28
// 参	数:	_Out_ vector<COORD> & vecPath
// 返 回	值:	bool
//************************************************************
bool CAStar::GetPath(_Out_ vector<COORD>& vecPath)
{
	//0.起点就是终点,直接返回
	if (m_Begin.X == m_End.X&&m_Begin.Y == m_End.Y)
	{
		return true;
	}
	//1.将起始点加入到Open表中
	NODE FirstNode = { 0 };
	FirstNode.stcPos = m_Begin;
	FirstNode.G = 0;
	FirstNode.H =
		abs(FirstNode.stcPos.X - m_End.X) +
		abs(FirstNode.stcPos.Y - m_End.Y);
	FirstNode.F = FirstNode.G + FirstNode.H;
	m_vecOpen.push_back(FirstNode);		//Open表中有了第一个点
	while (true)
	{
		//2.开始循环,从Open表中,选取F值最小的点
		int nMin = m_vecOpen[0].F;
		int nPos = 0;
		int nSize = m_vecOpen.size();
		for (int i = 0; i < nSize; i++)
		{
			if (nMin>m_vecOpen[i].F)
			{
				nMin = m_vecOpen[i].F;
				nPos = i;
			}
		}
		//3.从此点派生出周围的四个点,并为其赋值
		NODE stcTemp[4] = { 0 };
		//上
		stcTemp[0].stcPos.X = m_vecOpen[nPos].stcPos.X;
		stcTemp[0].stcPos.Y = m_vecOpen[nPos].stcPos.Y - 1;
		//下
		stcTemp[1].stcPos.X = m_vecOpen[nPos].stcPos.X;
		stcTemp[1].stcPos.Y = m_vecOpen[nPos].stcPos.Y + 1;
		//左
		stcTemp[2].stcPos.X = m_vecOpen[nPos].stcPos.X - 1;
		stcTemp[2].stcPos.Y = m_vecOpen[nPos].stcPos.Y ;
		//右
		stcTemp[3].stcPos.X = m_vecOpen[nPos].stcPos.X + 1;
		stcTemp[3].stcPos.Y = m_vecOpen[nPos].stcPos.Y;
		for (int i = 0; i < 4;i++)
		{
			stcTemp[i].stcPrtPos = m_vecOpen[nPos].stcPos;
			stcTemp[i].G = m_vecOpen[nPos].G + 1;
			stcTemp[i].H =
				abs(stcTemp[i].stcPos.X - m_End.X) +
				abs(stcTemp[i].stcPos.Y - m_End.Y);
			stcTemp[i].F = stcTemp[i].G + stcTemp[i].H;
		}
		//3.1将此最小点,将其加入到Close表中,从Open表中删除
		m_vecClose.push_back(m_vecOpen[nPos]);
		m_vecOpen.erase(m_vecOpen.begin() + nPos);		
		//4.检测一下这四个点是否可用
		for (int i = 0; i < 4; i++)
		{
			int nX = stcTemp[i].stcPos.X;
			int nY = stcTemp[i].stcPos.Y;
			//4.0找到了终点			
			if (nX == m_End.X&&nY == m_End.Y)
			{
				//4.0.1已经找到终点,从Close表中进行回溯,得到最短的路径,退出
				//回溯
				//-------------------------------------------------
				vecPath.push_back(stcTemp[i].stcPos);
				NODE PathNode = stcTemp[i];				
				for (int i = m_vecClose.size()-1; i >= 0; i--)
				{
					if (PathNode.stcPrtPos.X == m_vecClose[i].stcPos.X&&
						PathNode.stcPrtPos.Y == m_vecClose[i].stcPos.Y
						)
					{
						vecPath.push_back(m_vecClose[i].stcPos);
						PathNode = m_vecClose[i];
					}
				}
				//-------------------------------------------------
				m_vecOpen.clear();
				m_vecClose.clear();
				return true;
			}
			//4.1是否越界
			if (nX<0||nY<0||nX>39||nX>39)
			{
				continue;
			}
			//4.2是否在障碍物上
			if (m_Map[nY][nX]!=0)
			{
				continue;
			}
			//4.3是否在Open表中
			int j = 0;
			for (; j < m_vecOpen.size();j++)
			{
				if (m_vecOpen[j].stcPos.X == nX&&m_vecOpen[j].stcPos.Y == nY)
				{
					break;
				}
			}
			if (j != m_vecOpen.size())
			{
				continue;
			}
			//4.4是否在Close表中
			for (j = 0; j < m_vecClose.size();j++)
			{
				if (m_vecClose[j].stcPos.X == nX&&m_vecClose[j].stcPos.Y == nY)
				{
					break;
				}
			}
			if (j != m_vecClose.size())
			{
				continue;
			}
		    //4.5将可用的点加入到Open表中
			m_vecOpen.push_back(stcTemp[i]);
		}	
		//5.Open表中没有点了,说明没有找到路径,退出函数
		if (m_vecOpen.size()==0)
		{
			m_vecClose.clear();
			return false;
		}
	}
}
//************************************************************
// 函数名称:	SetConsoleInfo
// 函数说明:	设置控制台信息
// 作	者:	Dylan
// 时	间:	2015/08/28
// 返 回	值:	void
//************************************************************
void CAStar::SetConsoleInfo()
{
	//设置控制台标题
	SetConsoleTitle(L"A星寻路算法");
	//设置控制台缓冲区大小
	COORD BufferSize = { 99, 42 };
	SetConsoleScreenBufferSize(hStdOut, BufferSize);
	//设置控制台窗口大小
	SMALL_RECT strctWindow = { 0, 0, BufferSize.X - 1, BufferSize.Y - 1 };
	SetConsoleWindowInfo(hStdOut, true, &strctWindow);
	//设置光标位置
	COORD pos = { 0, 40 };
	SetConsoleCursorPosition(hStdOut, pos);
}
//************************************************************
// 函数名称:	WriteChar
// 函数说明:	描点画图函数
// 作	者:	Dylan
// 时	间:	2015/08/28
// 参	数:	int x
// 参	数:	int y
// 参	数:	wchar_t * pszChar
// 参	数:	WORD wArr
// 返 回	值:	void
//************************************************************
void CAStar::WriteChar(int x, int y, wchar_t* pszChar, WORD wArr)
{
	CONSOLE_CURSOR_INFO cci;
	cci.dwSize = 1;
	cci.bVisible = FALSE;//光标是否可见
	SetConsoleCursorInfo(hStdOut, &cci);//将光标属性应用到控制台
	COORD loc = { x * 2, y };
	DWORD dwRes1, dwRes2;
	WORD wClr[10];
	wmemset((wchar_t*)wClr, wArr, 10);
	WriteConsoleOutputAttribute(hStdOut, wClr, 2, loc, &dwRes1);//打印字符串颜色
	WriteConsoleOutputCharacter(hStdOut, pszChar, 1, loc, &dwRes2); //打印字符串到指定位置
}
//************************************************************
// 函数名称:	DrawMap
// 函数说明:	画地图
// 作	者:	Dylan
// 时	间:	2015/08/28
// 返 回	值:	void
//************************************************************
void CAStar::DrawMap()
{
	SetConsoleInfo();
	WriteChar(m_Begin.X, m_Begin.Y, L"☆", F_H_YELLOW);
	WriteChar(m_End.X, m_End.Y, L"★", F_H_YELLOW);
	for (int i = 0; i < 40; i++)
	{
		for (int j = 0; j < 40; j++)
		{
			m_Map[i][j] = 0;
			if (i == 0 || j == 0 || i == 39 || j == 39)
			{
				m_Map[i][j] = 1;
			}
		}
	}
	for (int i = 0; i < 40; i++)
	{
		for (int j = 0; j < 40; j++)
		{
			if (m_Map[j][i] == 1)
			{
				WriteChar(i, j, L"▇", B_H_WHITE | F_RED);
			}
		}
	}
}
//************************************************************
// 函数名称:	EditMap
// 函数说明:	编辑地图
// 作	者:	Dylan
// 时	间:	2015/08/28
// 返 回	值:	void
//************************************************************
void CAStar::EditMap()
{
	//画张空地图
	DrawMap();
	//获取标地图准输入输出句柄 
	HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
	HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE);
	CONSOLE_SCREEN_BUFFER_INFO bInfo;
	INPUT_RECORD mouseRec;
	DWORD res;
	COORD crPos = { 0, 0 }, crHome = { 82, 2 };
	SetConsoleMode(hIn, ENABLE_MOUSE_INPUT);//设置鼠标可用,这样cls以后鼠标就不会不好使
	while (true)
	{
		ReadConsoleInput(hIn, &mouseRec, 1, &res);
		if (mouseRec.EventType == MOUSE_EVENT)
		{
			if (mouseRec.Event.MouseEvent.dwButtonState == RIGHTMOST_BUTTON_PRESSED)
			{
				if (mouseRec.Event.MouseEvent.dwEventFlags == DOUBLE_CLICK)
				{
					//双击右键退出循环,画路径
					break;					
				}
			}
			//设置坐标显示信息
			crPos = mouseRec.Event.MouseEvent.dwMousePosition;
			GetConsoleScreenBufferInfo(hOut, &bInfo);
			SetConsoleTextAttribute(hOut, F_H_WHITE);
			SetConsoleCursorPosition(hOut, crHome);
			printf("坐标X: %2lu Y: %2lu", crPos.X, crPos.Y);
			SetConsoleCursorPosition(hOut, bInfo.dwCursorPosition);
			//鼠标点击事件
			switch (mouseRec.Event.MouseEvent.dwButtonState)
			{
				//单击左键
			case FROM_LEFT_1ST_BUTTON_PRESSED:
				if (crPos.X > 1 && crPos.X<77 && crPos.Y>0 && crPos.Y < 39)
				{
					crPos.X = crPos.X / 2 * 2;
					if (!((crPos.X / 2 == m_Begin.X&&crPos.Y == m_Begin.Y) ||
						(crPos.X / 2 == m_End.X&&crPos.Y == m_End.Y)))
					{
						WriteChar(crPos.X / 2, crPos.Y, L"▇", B_H_WHITE | F_RED);
						m_Map[crPos.Y][crPos.X / 2] = 1;
					}
				}
				break;
				//单击右键
			case RIGHTMOST_BUTTON_PRESSED:
				if (crPos.X > 1 && crPos.X<77 && crPos.Y>0 && crPos.Y < 39)
				{
					crPos.X = crPos.X / 2 * 2;
					if (!((crPos.X / 2 == m_Begin.X&&crPos.Y == m_Begin.Y) ||
						(crPos.X / 2 == m_End.X&&crPos.Y == m_End.Y)))
					{
						WriteChar(crPos.X / 2, crPos.Y, L"  ",NULL);
						m_Map[crPos.Y][crPos.X / 2] = 0;
					}
				}
				break;
			}
		}
	}
}


A_Star.cpp:

// A_Star.cpp : 定义控制台应用程序的入口点。
// A星算法

#include "stdafx.h"
#include "AStar.h"

int _tmain(int argc, _TCHAR* argv[])
{
	vector<COORD> Path;
	CAStar obj;
	obj.EditMap();
	obj.GetPath(Path);
	//画路线
	for (int i = Path.size() - 2;i>0;i--)
	{
		obj.WriteChar(Path[i].X, Path[i].Y, L"☉", F_H_GREEN);
	}
	system("pause");
	return 0;
}

演示效果:


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
以下是一个 A* 算法的 C 语言实现示例: ```c #include <stdio.h> #include <stdlib.h> #include <stdbool.h> #define ROW 10 #define COL 10 typedef struct { int x; int y; } Point; // 地图,0 表示可以通过,1 表示障碍物 int map[ROW][COL] = { {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 1, 0, 1, 0, 0}, {0, 0, 0, 0, 0, 1, 0, 1, 0, 0}, {0, 0, 0, 0, 0, 1, 0, 1, 0, 0}, {0, 0, 0, 0, 0, 1, 0, 1, 0, 0}, {0, 0, 0, 0, 0, 1, 0, 1, 0, 0}, {0, 0, 0, 0, 0, 1, 0, 1, 0, 0}, {0, 0, 0, 0, 0, 1, 0, 1, 0, 0}, {0, 0, 0, 0, 0, 1, 0, 1, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0} }; // 起点和终点 Point start = {0, 0}; Point end = {9, 9}; // 计算两点间的曼哈顿距离 int manhattan_distance(Point a, Point b) { return abs(a.x - b.x) + abs(a.y - b.y); } // 检查点是否在地图内且可以通过 bool is_valid_point(Point p) { if (p.x < 0 || p.x >= ROW || p.y < 0 || p.y >= COL) return false; return map[p.x][p.y] == 0; } // 判断点是否在列表中 bool is_point_in_list(Point p, Point list[], int len) { for (int i = 0; i < len; i++) { if (list[i].x == p.x && list[i].y == p.y) return true; } return false; } // A* 算法 bool a_star() { // 开启列表和关闭列表 Point open_list[ROW * COL]; int open_list_len = 0; Point close_list[ROW * COL]; int close_list_len = 0; // 当前节点和下一个节点 Point current = start; Point next; // 将起点加入开启列表 open_list[open_list_len++] = current; while (open_list_len > 0) { // 从开启列表中找到曼哈顿距离最小的节点 int min_index = 0; for (int i = 1; i < open_list_len; i++) { if (manhattan_distance(open_list[i], end) < manhattan_distance(open_list[min_index], end)) min_index = i; } current = open_list[min_index]; // 如果当前节点是终点,则找到路径 if (current.x == end.x && current.y == end.y) return true; // 将当前节点从开启列表中移除,并加入关闭列表 for (int i = min_index; i < open_list_len - 1; i++) open_list[i] = open_list[i + 1]; open_list_len--; close_list[close_list_len++] = current; // 扩展当前节点的邻居节点 for (int i = -1; i <= 1; i++) { for (int j = -1; j <= 1; j++) { next.x = current.x + i; next.y = current.y + j; // 忽略自身和对角线方向的邻居节点 if ((i == 0 && j == 0) || (i != 0 && j != 0)) continue; // 检查邻居节点是否在地图内且可以通过 if (!is_valid_point(next)) continue; // 检查邻居节点是否在关闭列表中 if (is_point_in_list(next, close_list, close_list_len)) continue; // 计算邻居节点的 f 值 next.f = current.g + 1 + manhattan_distance(next, end); // 检查邻居节点是否已经在开启列表中 int index = -1; for (int k = 0; k < open_list_len; k++) { if (open_list[k].x == next.x && open_list[k].y == next.y) { index = k; break; } } // 如果邻居节点不在开启列表中,则将其加入开启列表 if (index == -1) { next.g = current.g + 1; open_list[open_list_len++] = next; } // 否则,如果邻居节点的 f 值小于开启列表中对应节点的 f 值,则更新开启列表中对应节点的 f 值为邻居节点的 f 值,并更新其父节点为当前节点 else if (next.f < open_list[index].f) { open_list[index].f = next.f; open_list[index].g = current.g + 1; } } } } // 没有找到路径 return false; } int main() { if (a_star()) { printf("找到路径!\n"); } else { printf("没有找到路径!\n"); } return 0; } ``` 在本例中,我们使用一个二维数组来表示地图,0 表示可以通过,1 表示障碍物。起点和终点分别用坐标表示。在 `a_star()` 函数中,我们使用开启列表和关闭列表来实现 A* 算法。开启列表中存储的是待扩展的节点,关闭列表中存储的是已经扩展过的节点。每次从开启列表中找到曼哈顿距离最小的节点进行扩展,直到找到终点或者开启列表为空。在扩展节点时,我们先检查其邻居节点是否在地图内且可以通过,然后判断其是否在关闭列表中,最后计算其 f 值(g 值加上曼哈顿距离),并加入开启列表或者更新开启列表中已有节点的 f 值。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值