广度寻路的简单实现

辗辗转转,终究觉得要做个笔记,广度寻路与深度寻路不同,一个找最短,另一个找到就行,但广度思路与深度无太多差异,只是从寻找1个方向变成4个方向,而a*是从4变成8。

那么寻路要哪些元素呢? 

1.地图

2.地址

3.方向

首先我们先定义地图,用数组定义。

#define MAP_ROW 6
#define MAP_COL 8



int arr[MAP_ROW][MAP_COL] = {
		{ 0, 0, 0, 0, 0, 0, 0, 0 },
		{ 0, 0, 0, 0, 0, 0, 0, 0 },
		{ 0, 0, 0, 1, 0, 0, 0, 0 },
		{ 0, 0, 0, 0, 0, 0, 0, 0 },
		{ 0, 0, 0, 0, 0, 0, 0, 0 },
		{ 0, 0, 0, 0, 0, 0, 0, 0 }
	};

很简单的二维数组,0代表可通行,1代表障碍。(在推箱子中,会有更多的数值代表不同的意义)

接下来就是定义一个结构体,去存取位置信息:

struct MyPos
{
	int row, col;
};

这里我们用行列存取,不用x,y来存取,因为x代表列数,一不小心代码就写错了,行列分别从0,0开始,这是为了符合数组的arr[0][0];

方向我们采用枚举:

enum PathDir{p_up,p_down,p_left,p_right};

 这样我们的数据结构就出来了:

struct PathDate
{
	bool isFind;
	int val;
	PathDir dir;
};

val保存当前这个点是障碍还是路;

dir保存方向;

isFind保存这个点是否已经被找过(防止交叉查找)。

你可能有疑问?地址呢?怎么不保存地址呢?其实地址是可以保存的,但同学们仔细想想就能发现,我们点的信息有了,如何定位这个点了?我们靠地址呀,我们是通过行列去找这个点,再找这个点的相关信息的;所以把地址存入点的信息中,不是多此一举吗?

上面只是结构体,我们要的是一个地图的数据,所以还得做一个结构数组:

int arr[MAP_ROW][MAP_COL] = {
		{ 0, 0, 0, 0, 0, 0, 0, 0 },
		{ 0, 0, 0, 0, 0, 0, 0, 0 },
		{ 0, 0, 0, 1, 0, 0, 0, 0 },
		{ 0, 0, 0, 0, 0, 0, 0, 0 },
		{ 0, 0, 0, 0, 0, 0, 0, 0 },
		{ 0, 0, 0, 0, 0, 0, 0, 0 }
	};

	PathDate pathArr[MAP_ROW][MAP_COL];//这相当于地址
	for (int i = 0; i < MAP_ROW; ++i)
	{
		for (int j = 0; j < MAP_COL; ++j)
		{
			pathArr[i][j].dir = p_up;//默认先找上方向
			pathArr[i][j].isFind = false;
			pathArr[i][j].val = arr[i][j];
		}
	}
	//准备了一个辅助数组,并把资源数组的数据复制到辅助数组

这不难吧,很简单的操作,如果你觉得这难的话,我只能说编程太伤元气了,不适合你,

 好了,我们地图的数据结构出来了,但是这只是数据,我们寻路还没开始呀,我们需要去处理这些数据,给它一个行为逻辑,它才能运行。这里我们采用树去做这个事情。

struct MyPathNode//树形结构中的节点类型
{
	MyPos pos;
	MyPathNode *pParentNode;//指父节点
	vector<MyPathNode *> pChildNode;//动态指针数组,找所有的子节点
};

 上面我们定义了一个树结点的普通形式,细心的同学会发现我们存入了地址,存地址干什么?稍后会有解答。

相信看这篇文章的同学应该熟悉链表,也应该熟悉vector,所以上图后两个参数的意义我就不解释了,不太懂得同学可以看成两个点之间的路径,一个是通往 逻辑上是自己上级的路径,另外是不知道多少条(n<=4)通行下级的路径(一个点会有多条路径通向其它点,不给这些路径安排逻辑上的先后(根据起点到终点的路径来定),是不符合常理的,算法也会复杂很多)。

接下来就是重头戏:定义起点、终点,标记起点为已查找。

        MyPos beginPos = { 1, 1 };
	MyPos endPos = { 4, 6 };
	pathArr[beginPos.row][beginPos.col].isFind = true;//起点标记为已查找

把起点的数据信息存入结点。

//定义一个根节点,用来保存寻到的路径点,起点坐标为根节点数据域数据
	MyPathNode *pRoot = NULL;
	pRoot = new MyPathNode;
	pRoot->pos = beginPos;
	pRoot->pParentNode = NULL;

这里我们就可以解释上面为什么要存入地址了,因为地址才是我们区别一个点的根本。

广度寻路 寻根究底只是将起点到终点的路径打通(当然也打通了其它路径),我们做的不过是让起点指向其子节点,子节点再指向子节点的子节点,直到有一个子节点为终点,便结束我们的寻路。就是去寻找pRoot的pChildNode的pChildNode的pChildNode...........直到有一个为终点,我们便将这条路径显式打印出来,其数据也可单独做一个存储(本例未单独存储)。

广度寻路 理论上可表现为这样的形式:1->4  ,  4->16, 16->64;显然我们要写这样一个函数来符合所有点:

所以我们应该写:一个点的四次方向寻找函数。

当1个点数据被输入时,按理论我们应该得到4个点的数据,这4个点又要被输入函数得到16个点的数据,故我们应该保存每次函数运行后的点数据,所以我把数据分成两部分:待搜索和搜索(当然你可以按照你个人喜好分成其它的  如:函数前,函数后),定义了两个vector去保存这两部分数据:

    //定义一个待搜索的指针数组,把根节点压入进这个搜索序列
	vector<MyPathNode *>  nodeList;
	nodeList.push_back(pRoot);

	//定义一个临时的搜索指针数组
	vector<MyPathNode *>  tempNodeList;
	MyPos tempPos;//临时坐标,用来记录待搜索节点的坐标,已至于不用去修改待搜索节点坐标

对于这种循环直到符合某一条件的,最简单的代码就是:while(true){};

while (true)
	{
		bool isFind = false;//定义一个标记,是否找到终点,false为否
		for (int i = 0; i < (int)nodeList.size(); ++i)//循环查找待搜索数组里面的节点
		{
			for (int j = 0; j < 4; ++j)//判断4个方向是否可通行,且没有被访问
			{
				tempPos = nodeList[i]->pos;//得到待搜索的起始坐标
				switch (j)
				{
				case p_up:
					tempPos.row--;
					break;
				case p_down:
					tempPos.row++;
					break;
				case p_left:
					tempPos.col--;
					break;
				case p_right:
					tempPos.col++;
					break;
				}

我们先做一个开关,找到终点时好退出,同学们阅读完上面代码后应该知道我们已经得到了4个点的坐标,但是我们寻路算法是有条件判断的呀,不是什么点我们都要的呀!符合条件的我们才压入 待搜索vector中。

这样的话我们做一个bool函数来判断:

bool isCheckPoint(MyPos const& point, PathDate pArr[][MAP_COL])
{
	bool isCheck = false;//每一个点都认可是不可通行且访问的
	
	//如果在地图范围内
	if (point.col >= 0 && point.col < MAP_COL && point.row >= 0 && point.row < MAP_ROW)
	{
		//如果这个点没有被访问且可以通行
		if (pArr[point.row][point.col].isFind == false
			&& pArr[point.row][point.col].val == 0)
			isCheck = true;
	}

	return isCheck;
}

其实你不写成bool函数也行,直接用也是ok的。但写成bool函数 ,整个函数体会简洁一些,阅读代码时方便,维护也好一点

(就是函数体会更健硕)。

如果判断通过,我们就应该构造数据关系了,

if (isCheckPoint(tempPos, pathArr))//条件满足的情况需要压入节点到容器
				{
					MyPathNode * pNode = new MyPathNode;
					pNode->pos = tempPos;
					pNode->pParentNode = nodeList[i];//构造父关系
					nodeList[i]->pChildNode.push_back(pNode);//父节点构造子关系
 					tempNodeList.push_back(pNode);//把子节点压入临时动态指针数组
                                        pathArr[tempPos.row][tempPos.col].isFind = true;
		

这里值得一提的是为什么把pNode压入tempNodeList,是把tempNodeList做一个中转站来看。

当执行这句代码:    

nodeList[i]->pChildNode.push_back(pNode);//父节点构造子关系

我们应注意一点。第一次 i=0,nodeList[i]为nodeList[0]存放的是pRoot的地址,故进行父子关系构造时,改变的是pRoot的数值域。

构造完父子关系,我们接下来就简单多了,数据处理了,点也压入了,只差结束条件了,

	if (pNode->pos.row == endPos.row && pNode->pos.col == endPos.col)
					{
						isFind = true;//找到终点
						MyPathNode *tempNode = pNode;
						break;
					}
				}
			}

			if (isFind)//找到终点结束查找
				break;
		}
		if (isFind)//找到终点结束死循环
			break;

		if (tempNodeList.size() == 0)//证明没有可通行的路径,结束死循环
			break;

		nodeList = tempNodeList;//把待搜索数据赋值nodeList
		tempNodeList.clear();
	}

我们这里写伪代码来理一下逻辑:

while (true)
	{
		bool isFind = false;//定义一个标记,是否找到终点,false为否
		for (int i = 0; i < (int)nodeList.size(); ++i)//循环查找待搜索数组里面的节点
		{
			for (int j = 0; j < 4; ++j)//判断4个方向是否可通行,且没有被访问
			{
				//压入数据,找终点
			}
		if (isFind)//找到终点结束查找,提前跳出for循环。
		  break;
		}  

		if (isFind)//找到终点结束查找,跳出while循环。
	          break;
		 if (tempNodeList.size() == 0)//证明没有可通行的路径,结束死循环
			break;
		nodeList = tempNodeList;//把待搜索数据赋值nodeList,此操作会丢失根节点或其它数据吗?
		tempNodeList.clear();
	}

好了,到这里我们的广度寻路就算完成了,

呃呃呃,我们的路径你是找到了,但是你没有显示出来呀,你一个寻路算法,不显示路径,我们看数据呀,我怎么知道你代码是否正确呢?

对哟,那好的,我们还有显示的代码没完成,那就加上吧,

怎么显示路径呢:从根结点一直找到终点再打印出来吗?好像不好找,因为子节点那么多,数目又不确定,遍历起来很麻烦,所以我们反过来一想,我们从终点一直找父节点到起点不就行了,父节点可只有一个!但是麻烦来了,我们运行完while循环后是拿不到end的结点呀,怎么办了:


    MyPathNode *endNode=NULL;//定义在while循环外。

    if (pNode->pos.row == endPos.row && pNode->pos.col == endPos.col)
			{
				isFind = true;//找到终点
				endNode = pNode;
				break;
			}
while (endNode)
	{
		printf("row = %d\t  col = %d\n", endNode->pos.row, endNode->pos.col);
		endNode = endNode->pParentNode;
	}

在外面定义一个结点接受终点就行,至此我们寻路就写完了,如有错误,欢迎指正。

源码链接:https://pan.baidu.com/s/1fWsYumq-uRyhHZEDWwBUNQ

猜你喜欢

转载自blog.csdn.net/yuyuchiyue/article/details/81128419