zoukankan      html  css  js  c++  java
  • A*寻路算法的实现


    原理:http://www.cppblog.com/christanxw/archive/2006/04/07/5126.html

    算法理论请到原理这个传送门,代码中的注释,已经比较详细,所以我不会讲太多的原理,该文章本身就是以A*的思路,对算法进行一次速度上的优化,用一些更效率的方式来代替算法原理中必要的步骤。

    针对算法原理,做出如下改动:

    抛弃关闭列表,取而代之的是根据地图数据生成一个BYTE类型的二维数组,因为该数组在算法中可能需要修改,所以不能直接使用原始数据。

    注:二维数组动态分配应为:

    BYTE **pMap = new BYTE*[地图高度];

    for(int i = 0; i < 地图高度; i++)

    pMap[i] = new BYTE[地图宽度];

    释放:

    void FreeMap(BYTE **pMap, DWORD dwHeight)
    {
    for(DWORD i = 0; i < dwHeight; i++)
    {
    delete pMap[i];
    pMap[i] = NULL;
    }
    delete [] pMap;
    }

    抛弃曼哈顿算法,使用求绝对距离的方法直接计算F,使用相邻格坐标,其中(x,y)任意相等则为横纵移动判断G

    在遍历开启列表查找最小距离的节点时,加入跳出逻辑。


    修改后的缺点,占内存,10000*10000要算上列表要100+M,所以如果你的游戏有这么大的地图,应该分段查找,分成1000*1000一次比较理想,或者对地图数据进行压缩。


    测试结果:

    [3300] 出口坐标:354, B8   当前坐标:CE, 1FD
    [3300] 最终寻路到:353, CC
    [3300] 耗时:0 毫秒
    [3300] 寻路成功,节点数:1187




    根据图片看到的情况,很显然,这不是最佳路径,要选择最佳路径,我自己能想到的办法就是还是先得到这个路径,然后在这个路径中查找角度改变的地方,满足一个三角之后,或者根据距离分段,根据根据两端的坐标再进行一次A*,这样数次之后可能得到的路径也不错了。

    对于优化路线最简单的办法,请到这个传送门:对A*算法的路径进行优化


    代码如下:

    必须依赖的VC++头文件(vc6.0)有:

    #include <list>

    #include <algorithm>

    #include <stdarg.h>

    #include <math.h>

    大概就是这些,不够再百度下吧


    头文件

    namespace blx
    {
    #ifndef BREAK_GAP
    #define BREAK_GAP 20.0
    #endif
    #ifndef NOTLESS_COUNT
    #define NOTLESS_COUNT 14
    #endif
    	typedef struct _APOINT{
    		int x;
    		int y;
    		double dbGap;
    		_APOINT *parent;
    	}APOINT, *LPAPOINT;
    
    	class CAStar
    	{
    	public:
    		CAStar();
    		CAStar(BYTE **pMap, DWORD dwMapWidth, DWORD dwMapHeight);
    		bool Search(int X, int Y, std::list<POINT> &lResult);//主搜索
    		void SetDestinationPos(int X, int Y)//设置目标坐标
    		{m_dwDestinationX = X; m_dwDestinationY = Y;}
    		void SetMapAttributes(BYTE **pMap, DWORD dwWidth, DWORD dwHeight)//设置地图属性 参数为:指针,宽,高  与第二个构造函数相同
    		{m_pMap = pMap; m_dwMapWidth = dwWidth; m_dwMapHeight = dwHeight;};
    		void printBitmap(BYTE **pMap, int nWidth, int nHeight, std::list<POINT> &lPath, LPCTSTR lpFile);//该接口可将寻路结果保存为bmp图像
    		//参数为 地图指针,宽度,高度,寻路路径,文件
    		//不要使用m_pMap,应该在搜索完毕后新建一个地图,然后再来调用该接口生成图像
    		
    	private:
    		LPAPOINT GenerateSuccessors(std::list<LPAPOINT>::iterator it);//处理每一个节点
    		std::list<LPAPOINT>::iterator GetMingapNode();//获取开启列表中最小距离的节点
    		
    	private:
    		BYTE **m_pMap;
    		DWORD m_dwDestinationX, m_dwDestinationY, m_dwMapWidth, m_dwMapHeight;
    		
    		std::list<LPAPOINT> m_lOpen;
    		std::list<LPAPOINT> m_lSafe;
    	};
    }


    源文件

    namespace blx
    {
    	CAStar::CAStar()
    	{
    		m_pMap = NULL;
    		m_dwDestinationX = 0;
    		m_dwDestinationY = 0;
    		m_dwMapWidth = 0xFFFFFFFF;
    		m_dwMapHeight = 0xFFFFFFFF;
    	}
    
    	CAStar::CAStar(BYTE **pMap, DWORD dwMapWidth, DWORD dwMapHeight)
    	{
    		m_pMap = pMap;
    		m_dwMapWidth = dwMapWidth;
    		m_dwMapHeight = dwMapHeight;
    	}
    
    	std::list<LPAPOINT>::iterator CAStar::GetMingapNode()
    	{
    		//获取开启列表中,距离目的地最近的节点
    		std::list<LPAPOINT>::iterator itResult;
    		double dbMinGap = 100000000.0;
    		int nIntoMaxCount = 0;
    		bool bIntoState = true;
    		for(std::list<LPAPOINT>::iterator it = m_lOpen.begin(); it != m_lOpen.end(); ++it)
    		{
    			if((*it)->dbGap < dbMinGap)
    			{
    				//这种类似冒泡排序的逻辑不用我多说吧
    				dbMinGap = (*it)->dbGap;
    				itResult = it;
    
    				//如果选中一个最小的,把检查循环状态设置为关闭
    				bIntoState = false;
    			}
    			else
    			{
    
    				if(bIntoState)//如果检查循环状态为开启
    					nIntoMaxCount++;//则计数+1
    				else
    				{
    					//如果检查循环状态为关闭
    					nIntoMaxCount = 1;//置计数为1
    					bIntoState = true;//把检查循环状态打开
    				}
    			}
    
    			//当检查循环计数大于 NOTLESS_COUNT 时 跳出
    			if(nIntoMaxCount > NOTLESS_COUNT) // #define NOTLESS_INDEX 14
    			{
    				//除了起始点之外,每次处理之后加入到开启列表的节点不会大于5(它本身已经关闭,它的父节点之前就关闭了)
    				//你可以在纸上画出来,走一步之后,当前格子相邻的8个格子中,有一个是父节点,有两个与他的父节点相邻
    				//我用了14来判断跳出,当然这个14是列表中的节点一直大于或等于已经搜索到的最小节点之后的第14次循环之后
    				//这样就可以把查找开启列表的成本降低到最小,但是路径很可能就不是最好的,因为不知道经过这14次之后,还会不会有更好的节点
    				//要满足这个前提,你要反向的遍历开启列表,本例中的开启列表是向前插入的(push_front),所以我是从链表的正向遍历
    				break;
    			}
    		}
    		
    		return itResult;
    	}
    
    	struct _find_map_note{
    		_find_map_note(int x, int y) : _x(x), _y(y) {}
    		bool operator()(LPAPOINT p){return (p->x == _x && p->y == _y);}
    		int _x, _y;
    	};
    
    	LPAPOINT CAStar::GenerateSuccessors(std::list<LPAPOINT>::iterator it)
    	{
    		int x = (*it)->x, y = (*it)->y;
    		int aX[3] = {x - 1, x, x + 1};
    		int aY[3] = {y - 1, y, y + 1};
    		LPAPOINT pNow = *it;
    		m_lOpen.erase(it);//从开启列表中移除
    		m_pMap[y][x] = 0;//设置为关闭
    		//
    		BYTE bState = 0;
    		LPAPOINT p;
    		std::list<LPAPOINT>::iterator it2;
    		for(int i = 0; i < 3; i++)
    		{
    			for(int j = 0; j < 3; j++)
    			{
    				//交叉遍历
    				if(aX[j] >= m_dwMapWidth || aY[i] >= m_dwMapHeight)
    					continue;
    				
    				bState = m_pMap[aY[i]][aX[j]];
    				
    				if(!bState)
    					continue;//如果这个坐标是障碍或者已经置为关闭,则忽略它
    				else if(bState == 1)
    				{
    					//如果它不在开启列表,将它加入到开启列表中,并设置它的父节点为当前节点
    					p = new APOINT;
    					p->x = aX[j];
    					p->y = aY[i];
    					p->dbGap = _p2g(aX[j], aY[i], m_dwDestinationX, m_dwDestinationY);
    					p->parent = pNow;
    					m_lOpen.push_front(p);//反向加入到容器头部
    					m_lSafe.push_back(p);//加入到公共容器
    					m_pMap[aY[i]][aX[j]] = 2;
    				}
    				else if(bState == 2)
    				{
    					//如果它已经在开启列表中
    					if(x == aX[j] || y == aY[i])//判断它与当前节点的关系是否为横纵移动
    					{
    						//如果是
    						it2 = std::find_if(m_lOpen.begin(), m_lOpen.end(), _find_map_note(aX[j], aY[i]));//从开启列表中把它的指针拿出来
    						//因为开启列表是反向的,所以正向遍历能尽可能的减少开销,如果还要比这个更效率的,可能二叉堆会快一点
    						if((*it2)->parent->x != aX[j] && (*it2)->parent->y != aY[i])//判断它与它之前父节点的关系,是否是横纵移动
    							(*it2)->parent = pNow;//如果不是,则设置它的父节点为当前节点
    					}
    
    					//更形象的说,这是一个很成功的阴谋:
    					//当你与他的关系好到可以让他认贼作父时
    					//你调查一下他和他爹的关系如何
    					//如果他跟他爹的关系不好
    					//那你就对他说:“和你爹断绝关系吧,从此以后你就是我儿子!”
    					//如果他跟他爹的关系跟你一样好,那你就最好打消上面一句话这种不实际的念头。
    
    					//如果你不管你们之间的关系,就想要考虑让他认贼作父,那你就有可能付出了调查他的代价,最后却什么都得不到
    					//最后只能怨天尤人的说:“草,这票白干了!”
    				}
    			}
    		}
    		return NULL;
    	}
    
    	bool CAStar::Search(int X, int Y, std::list<POINT> &lResult)
    	{
    		if(X < 0 || Y < 0
    			|| X > m_dwMapWidth || Y > m_dwMapWidth ||
    			m_dwDestinationX < 0 || m_dwDestinationX < 0 ||
    			m_dwDestinationX > m_dwMapWidth || m_dwDestinationY > m_dwMapHeight)
    		{
    			_outf("坐标或地图参数错误!");
    			return false;
    		}
    
    		LPAPOINT p = new APOINT;
    		p->x = X;
    		p->y = Y;
    		p->parent = NULL;
    		p->dbGap = _p2g(X, Y, m_dwDestinationX, m_dwDestinationY);
    		m_lOpen.push_front(p);//起始节点加入到开启列表
    		m_lSafe.push_back(p);//加入到公共容器,任何新分配的节点,都要加入到这里,便于算法执行完后清理
    
    		std::list<LPAPOINT>::iterator it;
    		DWORD dwTime = clock();
    		while(!m_lOpen.empty())
    		{
    			//这里就是反复遍历开启列表选择距离最小的节点
    			it = GetMingapNode();
    			if((*it)->dbGap < BREAK_GAP)//#define BREAK_GAP 20.0在头文件中,我对寻路的要求不是很高,所以用了与目的地小于20则跳出
    				break;
    			p = *it;
    			GenerateSuccessors(it);
    		}
    
    		if(!m_lOpen.empty())
    		{
    			//如果列表不为空,从最后一个节点开始拷贝路径到返回值中
    			_outf("最终寻路到:%X, %X", p->x, p->y);
    			POINT point;
    			while(p)
    			{
    				point.x = p->x;
    				point.y = p->y;
    				lResult.push_front(point);
    				p = p->parent;
    			}
    		}
    
    		for(it = m_lSafe.begin(); it != m_lSafe.end(); ++it)
    		{
    			//清理内存
    			if(*it != NULL)
    			{
    				delete (*it);
    				*it = NULL;
    			}
    		}
    
    		m_lSafe.clear();//清空容器
    
    		_outf("耗时:%d 毫秒", clock() - dwTime);
    
    		if(m_lOpen.empty())
    		{
    			_outf("寻路失败");
    			return false;
    		}
    
    		m_lOpen.clear();//清空开启列表
    		_outf("寻路成功,节点数:%d", lResult.size());
    		return true;
    	}
    
    	void CAStar::printBitmap(BYTE **pMap, int nWidth, int nHeight, std::list<POINT> &lPath, LPCTSTR lpFile)
    	{
    		int _nWidth = (nWidth + 3) * 4 / 4;
    		HDC hdc = CreateCompatibleDC(NULL);
    		PVOID pBits = NULL;
    		BITMAPINFO bi;
    		bi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    		bi.bmiHeader.biWidth = nWidth;
    		bi.bmiHeader.biHeight = nHeight;
    		bi.bmiHeader.biPlanes = 1;
    		bi.bmiHeader.biBitCount = 24;
    		bi.bmiHeader.biCompression = BI_RGB;
    		bi.bmiHeader.biSizeImage = nWidth * nHeight * 3;
    		HBITMAP hBitmap = CreateDIBSection(NULL, &bi, DIB_RGB_COLORS, &pBits, NULL, NULL);
    		HBITMAP hOld = (HBITMAP)SelectObject(hdc, hBitmap);
    		for(int i = 0; i < nHeight - 1; i++)
    		{
    			for(int j = 0; j < nWidth - 1; j++)
    			{
    				if(pMap[i][j] == 1)
    				{
    					SetPixel(hdc, j, i, RGB(0xFF,0xFF,0xFF));
    				}
    				else
    				{
    					SetPixel(hdc, j, i, RGB(0x80,0x80,0x80));
    				}
    			}
    		}
    
    		for(std::list<POINT>::iterator it = lPath.begin(); it != lPath.end(); ++it)
    			SetPixel(hdc, it->x, it->y, RGB(0xFF,0,0));
    		SaveHBITMAP2File(NULL, lpFile, hBitmap, hdc);
    		SelectObject(hdc, hOld);
    		DeleteObject(hBitmap);
    		DeleteDC(hdc);
    	}
    }
    
    


    必须的函数

    //调试输出
    void _outf(const char *format, ...)
    {
    	va_list al;
    	char buf[BLX_MAXSIZE];
    	va_start(al, format);
    	_vsnprintf(buf, BLX_MAXSIZE, format, al);
    	va_end(al);
    	OutputDebugStringA(buf);
    }
    
    //距离比价函数,计算两个坐标的绝对距离
    double _p2g(int x1, int y1, int x2, int y2)
    {
    	return sqrt(pow(double(abs(x1 - x2)), 2) + pow(double(abs(y1 - y2)), 2));
    }
    
    
    //这个是度娘上抄来的,反正可用,就没管,就是用来保存为bmp用的
    PBITMAPINFO CreateBitmapInfoStruct(HWND hwnd, HBITMAP hBmp)   
    {   
    	BITMAP bmp;    
    	PBITMAPINFO pbmi;    
    	WORD    cClrBits;    
    	if (!GetObject(hBmp, sizeof(BITMAP), (LPSTR)&bmp))    
    		return NULL;  
    	cClrBits = (WORD)(bmp.bmPlanes * bmp.bmBitsPixel);    
    	if (cClrBits == 1)    
    		cClrBits = 1;    
    	else if (cClrBits <= 4)    
    		cClrBits = 4;    
    	else if (cClrBits <= 8)    
    		cClrBits = 8;    
    	else if (cClrBits <= 16)    
    		cClrBits = 16;    
    	else if (cClrBits <= 24)    
    		cClrBits = 24;    
    	else cClrBits = 32;    
    	if (cClrBits != 24)    
    		pbmi = (PBITMAPINFO) LocalAlloc(LPTR,    
    		sizeof(BITMAPINFOHEADER) +    
    		sizeof(RGBQUAD) * (1<< cClrBits));    
    	else    
    		pbmi = (PBITMAPINFO) LocalAlloc(LPTR,    
    		sizeof(BITMAPINFOHEADER));
    	pbmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);    
    	pbmi->bmiHeader.biWidth = bmp.bmWidth;    
    	pbmi->bmiHeader.biHeight = bmp.bmHeight;    
    	pbmi->bmiHeader.biPlanes = bmp.bmPlanes;    
    	pbmi->bmiHeader.biBitCount = bmp.bmBitsPixel;    
    	if (cClrBits < 24)    
    		pbmi->bmiHeader.biClrUsed = (1<<cClrBits);     
    	pbmi->bmiHeader.biCompression = BI_RGB;    
    	pbmi->bmiHeader.biSizeImage = ((pbmi->bmiHeader.biWidth * cClrBits +31) & ~31) /8   
    		* pbmi->bmiHeader.biHeight;    
    	pbmi->bmiHeader.biClrImportant = 0;    
    	return pbmi;    
    } 
    
    //这个是度娘上抄来的,反正可用,就没管,就是用来保存为bmp用的
    BOOL SaveHBITMAP2File(HWND hwnd, LPCTSTR pszFile, HBITMAP hBMP, HDC hDC)   
    {   
    	PBITMAPINFO pbi = CreateBitmapInfoStruct(hwnd, hBMP);   
    	
    	HANDLE hf;                 // file handle    
    	BITMAPFILEHEADER hdr;       // bitmap file-header    
    	PBITMAPINFOHEADER pbih;     // bitmap info-header    
    	LPBYTE lpBits;              // memory pointer    
    	DWORD dwTotal;              // total count of bytes    
    	DWORD cb;                   // incremental count of bytes    
    	BYTE *hp;                   // byte pointer    
    	DWORD dwTmp;    
    	DWORD fileSizeInfo=0;
    	
    	pbih = (PBITMAPINFOHEADER) pbi;    
    	lpBits = (LPBYTE) GlobalAlloc(GMEM_FIXED, pbih->biSizeImage);   
    	
    	if (!lpBits)    
    		return FALSE;
    	
    	if (!GetDIBits(hDC, hBMP, 0, (WORD) pbih->biHeight, lpBits, pbi,    
    		DIB_RGB_COLORS))    
    	{   
    		return FALSE;    
    	}   
    	
    	
    	fileSizeInfo = (DWORD) (sizeof(BITMAPFILEHEADER) +    
    		pbih->biSize + pbih->biClrUsed    
    		* sizeof(RGBQUAD) + pbih->biSizeImage); 
    	if(fileSizeInfo==58)
    		return FALSE;
    	hf = CreateFile(pszFile,    
    		GENERIC_READ | GENERIC_WRITE,    
    		(DWORD) 0,    
    		NULL,    
    		OPEN_ALWAYS,    
    		FILE_ATTRIBUTE_NORMAL,    
    		(HANDLE) NULL);    
    	if (hf == INVALID_HANDLE_VALUE)    
    		return FALSE;    
    	
    	hdr.bfType = 0x4d42; 
    	hdr.bfSize = fileSizeInfo;  
    	
    	hdr.bfReserved1 = 0;    
    	hdr.bfReserved2 = 0;      
    	hdr.bfOffBits = (DWORD) sizeof(BITMAPFILEHEADER) +    
    		pbih->biSize + pbih->biClrUsed    
    		* sizeof (RGBQUAD);      
    	if (!WriteFile(hf, (LPVOID) &hdr, sizeof(BITMAPFILEHEADER),    
    		(LPDWORD) &dwTmp,  NULL))    
    	{   
    		return FALSE;    
    	}   
    	
    	if (!WriteFile(hf, (LPVOID) pbih, sizeof(BITMAPINFOHEADER)    
    		+ pbih->biClrUsed * sizeof (RGBQUAD),    
    		(LPDWORD) &dwTmp, ( NULL)))   
    	{   
    		return FALSE;  
    	}   
    	
    	dwTotal = cb = pbih->biSizeImage;    
    	hp = lpBits;    
    	if (!WriteFile(hf, (LPSTR) hp, (int) cb, (LPDWORD) &dwTmp,NULL))    
    	{   
    		return FALSE;    
    	}   
    	
    	if (!CloseHandle(hf))    
    		return FALSE;     
    	GlobalFree((HGLOBAL)lpBits);   
    	return TRUE;   
    }


  • 相关阅读:
    从Android源码修改cpu信息
    lintcode-->翻转字符串
    lintcode-->哈希函数
    PCP架构设计
    PCP项目立项
    linux下wc功能的简单实现
    goahead3.6.3就基本使用(后台上传信息到html页面),高手请忽略
    四则运算生成器
    快速看完软件工程教材后,我的疑惑
    软件工程学习
  • 原文地址:https://www.cnblogs.com/dyllove98/p/3149573.html
Copyright © 2011-2022 走看看