您的位置:首页 > 编程语言

A* 算法实现及代码下载(多种方式实现,加路径修正)

2013-05-12 19:39 423 查看
A*算法的介绍在下面这篇文章中说的很详细了,就不在这阐述了,这篇文章主要是介绍自己的一些理解,一些重点的解释,和效果展示。同样给出了代码编写的思路。代码会给下载链接。

A*算法可参考文章(介绍的很好):http://www.cppblog.com/christanxw/archive/2006/04/07/5126.html

代码已上传:

1)从txt文本读取地图命令窗口显示,方便算法调试:

http://download.csdn.net/detail/lmnxjf/5362983(免积分)

2) 演示程序下载,即代码编译后的exe文件,直观显示效果,鼠标拖动绘制地图:http://download.csdn.net/detail/lmnxjf/5363017(免积分)

3) 为2)整个工程的代码,vs2010下编写,将A*算法封装为类,同时可以熟悉windows的画笔和画刷的使用:http://download.csdn.net/detail/lmnxjf/5363039(2积分)

以前做的一些画笔画刷程序

1) 俄罗斯方块免积分下载:

http://download.csdn.net/detail/lmnxjf/5332451(免积分)

相应的博文:/article/2474490.html

csdn上传后就不能再对资源进行修改了,所以第三个会要积分下载。

二 A* 算法的实现

A*算法是一种静态路网中求解最短路最有效的方法。公式表示为:f(n)=g(n)+h(n)
n f(n) 是从初始点经由节点n到目标点的估价函数,
n g(n) 是在状态空间中从初始节点到n节点的实际代价,
n h(n) 是从n到目标节点最佳路径的估计代价。
其实A*算法是一种最好优先的算法只不过要加上一些约束条件罢了。由于在一些问题求解时,我们希望能够求解出状态空间搜索最短路径,也就是用最快的方法求解问题,A*就可以很好的完成这个任务。

6. 如果某个相邻的方格已经在 openlist 中,则检查这条路径是否更优,也就是说经由当前方格 ( 我们选中的方格 )到达那个方格是否具有更小的 G 值。如果没有,不做任何操作。相反,如果 G 值更小,则把那个方格的父亲设为当前方格 ( 我们选中的方格 ) ,然后重新计算那个方格的 F 值和G 值。

针对第六步做一些解释(图片借用上面文章的):



当我们计算完其他方块后,以A为中心时计算其邻居的方块时会添加新的方块到openlist中,这是所说的路径的校正就是看A的邻居方块中,有没有通过A路径更短的,如图也就是说B本来是指向A的右边的方块的,而这样回到起点距离是 28(也即左下角的值 G),假如B通过A回到起点的话就是20(10+10 加上B到A的距离),显然通过A的路径更好所以把指针改成指向A的 并更新它的(
FGH值),这就是路径的校正,在复杂地图中会对最终结果有很大的影响,大家可以试试去掉这一步骤会是什么结果,对比会发现这一步是很有必要的。

1.2 判断是否有解

当遍历的时候发现已经找到了终点这个时候就可以结束程序,从终点出发“沿着”它的父指针找到起点,最后把路径画出来即可。

那如何判断无解呢?可以这样来判断,因为每次都是在openlist中取最小的F值那个方块,取出来后放入到closelist中这样以所取的这个元素为中心继续添加元素。而当我们没有找到路径时则openlist中不会有新的数据加入,这个时候openlist中的元素又不断的移动到closelist中,所以当openlist中没有元素时可以说明无解,即没有路径。

1.3 openlist和closelist的建立

首先定义两个结构体,一个是openlist,一个是closelist,openlist前面有介绍用来保存遍历路径的,closelist是用来保存已经遍历完的位置。结构体中应还有这些信息当前保存的节点信息,以及下一个节点的地址信息。

同样建立closelist链表,建立好两个链表和节点后就可以编写代码了。其中重点就是两个链表的管理,在寻找最优路径时两个链表的内容是不断更新的,所以链表的设计要合理,操作要方便。比如在本程序中要找到最小f值的那个方块,我们可以在将方块周围临近的方块加入到openlist中是先对其判断,即先对链表的和第一个元素比对,若小于第一个元素则将其插入到第一个元素前面,若大于则继续和第二个元素比对,直到找到合适的位置。最后得到的openlist链表是从小到大的排序,这样找最小F值方块不需要临时找到最小元素的算法,比较省时。下图演示的是一个值为13的节点要插入到openlist链表中,在找到第一个比自己大的数(示例中为14),则插入到它的前面。(自己画的图不是很形象,但是比较好理解,
见谅)





链表结构体的基本元素, 可以供参考。代码在上面的连接中已经给出。

struct OpenList
{
	Node *opennode;
	OpenList* next;
	OpenList(){next= NULL;};
};
struct CloseList
{
	Node *closenode;
	CloseList* next;
	CloseList(){ next=NULL;};
};
节点结构体代码

struct Node
{
	//char perperty;// 属性, 是墙还是起点或是其他
	int    flag; //标志位 0 为可走, 1 为墙壁  2 在penlist  
					//3 在 closelist中 4 为起点 5 为终点
	unsigned int location_x;
	unsigned int location_y; 
	unsigned int value_h;
	unsigned int value_g;
	unsigned int value_f;
	Node* parent;
	Node();
};




class CAlgorithms : public CWnd
{
	DECLARE_DYNAMIC(CAlgorithms)
public:
	int startpoint_x;
	int startpoint_y;
	int endpoint_x;
	int endpoint_y;

public:
	CAlgorithms();
	virtual ~CAlgorithms();
	public:
	Node m_node[LENGTH][WIDE];
	void InitNodeMap( char aa[][WIDE], OpenList *open);
	bool FindDestinnation(OpenList* open,CloseList* close, char aa[][WIDE]);
	OpenList* FindMinInOpen(OpenList* open);
	bool Insert2OpenList(OpenList* , int x, int y);
	bool IsInOpenList(OpenList*, int x, int y);
	bool IsInCloseList(OpenList*, int x, int y);
	void IsChangeParent(OpenList*, int x, int y);
	bool IsAviable(OpenList* , int x, int y);
	unsigned int DistanceManhattan(int d_x, int d_y, int x, int y);
	unsigned int	Euclidean_dis(int sx,int sy,int ex,int ey);//欧几里德
	unsigned int Chebyshev_dis(int sx,int sy,int ex,int ey);//切比雪夫
	unsigned int jiaquan_Manhattan(int sx,int sy,int ex,int ey);//加权曼哈顿   //better
private:
	unsigned int steps;
protected:
	DECLARE_MESSAGE_MAP()
};


openlist的部分处理函数,计算H的可以修改为其他方式,下面会介绍。openlist用来寻找最优路径,判断是否有解

//////////////////////////////////////////////////////////////////////////
//  将临近的节点加入 openlist中
//				0      1      2  
//				3      S      4
//				5      6      7
/////////////////////////////////////////////////////////////////////////////
bool CAlgorithms::Insert2OpenList(OpenList* open,int center_x, int center_y)
{
	int i=0;
	for(; i<8 ; i++)
	{
		int new_x=center_x + direction[i][0];
		int new_y=center_y+ direction[i][1];

		if(new_x>=0 && new_y>=0 && new_x<LENGTH &&
			new_y<WIDE &&
			IsAviable(open, new_x, new_y))// 0
		{
			if(	m_node[new_x][new_y].flag==DESTINATION)
			{
				m_node[new_x][new_y].parent = &m_node[center_x][center_y];
				return true;
			}
			m_node[new_x][new_y].flag =INOPEN;
			m_node[new_x][new_y].parent = &m_node[center_x][center_y];
			m_node[new_x][new_y].value_h = 
				DistanceManhattan(endpoint_x, endpoint_y, new_x,new_y);//曼哈顿距离

			if(0==i || 2==i||5==i||7==i)
				m_node[new_x][new_y].value_g = m_node[center_x][center_y].value_g+14;
			else
				m_node[new_x][new_y].value_g = m_node[center_x][center_y].value_g+10;

			m_node[new_x][new_y].value_f = m_node[new_x][new_y].value_g+m_node[new_x][new_y].value_h;

			AddNode2Open(open, &m_node[new_x][new_y]);// 加入到 openlist中
		}
	}
	IsChangeParent(open, center_x,  center_y);
	return false;
}


二 A* 算法效果

首先是命令窗形式显示

*注:s:起点 d:终点 x:墙壁 .:可走道路 @:路径



第二个展示



三 基于mfc的界面改善
在编写调试好程序后,利用mfc来做一个界面以显示更好的效果,本文中采用的是基于文档的mfc程序利用画笔画刷来实现,结合上次的俄罗斯方块更好的理解画笔和画刷。实现的功能是用鼠标即可以绘制地图,而不用在txt文档中输入,同时按下鼠标左键拖动可以实现一个串地图的绘制。需要注意的是起点和终点只能有一个,这个在看个人怎么处理。还有就是当单击鼠标左键时加入此时方块中已经有值了则进行判断是否绘制还是取消绘制。有一个缺点就是当窗口最小化后再弹出窗口时先前画的地图将消失,这是由于mfc中画刷的原因。有兴趣的可以修改一下程序,我现在在做别的了就没解决这个问题。思路很简单,应该开始的时候已经把绘制好的地图保存到starmap.txt中,当最大化时读取txt文件重新绘制窗口就可以了。

本程序有友好的人机界面会显示当前绘制的什么类型的方块。下面是效果展示。(本人无聊自己乱画了一个地图,你也可以绘制个难度级别高的地图来检验A*算法是不是足够强大),同时还提供计算H的多种方法,代码中已经写出,到时候编译的时候可以选择自己的方式计算H(曼哈顿,欧几里德,切比雪夫,加权曼哈顿)
软件也提供下载,如何操作也有说明,这里再说一下,点击右边的颜色按钮可以选择放置的类别同时在“当前”中显示当前选择放置的什么类别的方块。按住左键拖动可以绘制多个方块(仅限墙块),起点和终点只能设置一个,否则不会寻找路径。





地图二





总结

学习这个算法开始的时候会觉得比较难,花了一天的时间看资料理解算法的原理。在知道原理后的第二天开始编写代码,同样也花了一天时间(调试所花时间较多)。最后是程序的显示的优化。总之这些花点时间就可以了。

程序中若有错误,或者做的不够好的请指教。



代码以上传。 若有什么问题或博客意见可直接留言,相互学习共同进步。

结束

作者: 一路向南
2013.5.12
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐