目录
0x00:寻路算法分类:
- 深度寻路算法
- 广度寻路算法
- A星寻路算法
0x01:深度寻路算法的局限性
- 只能应用于二维地图,并且只能走直线。
- 深度寻路算法不是为了找最短路径,而是为了到达终点愿意走遍地图上的每一个角落。
0x02 设计实际地图:
如图所示,该实际地图可以用一个相应的二维数组来描述,数组的索引可以分别表示x,y坐标,数组中的元素可以规定0代表空白,即可以走的路,1代表障碍物。
0x03 深度寻路算法思路:
首先,小人站在起点,上下左右四个方向,一个方向只能试探一次。并且我们需要规定一个试探次序,比方说顺时针,先试探上能不能走,如果不能走,就试探右边能不能走,如果还不能走,就试探下面能不能走,如果能走,那么就往下走,然后以此往复。
如果小人走进了死胡同,那么深度寻路算法需要有一个回退的机制(深度寻路算法的核心),即栈。那么栈如何实现回退呢?
小人每走一步,都将走的位置坐标压入栈中,如果小人走入了死胡同,那么就从栈中弹出栈顶元素,然后将小人移动到当前栈顶元素代表的位置。
0x04 设计辅助地图
由以上分析可以知道,仅仅一个由0、1组成的实际地图是远远不够的,我们还需要一个构造一个辅助地图,在地图里存储更多的信息。通俗地说,辅助地图就是一个做了各种标记的地图。辅助地图仍然是用一个二维数组表示,但是辅助地图中的节点是我们构造的结构体,其中存储了该点的值(1、0),该点是否走过,小人在该点应该向那个方向试探等辅助信息。
除了需要构造一个辅助地图用的节点类型外。还需要一个能描述小人位置的数据类型。可以在一个结构体内设置两个变量,一个变量存储x值,一个坐标存储y值。用这个结构体来描述小人的位置。为什么用(x,y)来描述小人的位置呢?因为我们之前的实际地图和辅助地图都是用二维数组表示的。通过x,y值我们可以根据小人的位置对应到辅助地图中的点,然后提取该点的其他信息。
0x05 寻路
小人如何寻路呢?小人寻路的过程本质上就是一个循环。在开始循环前,首先需要准备两个变量,一个变量用来存储小人当前的位置,另一个变量用来存储小人应该试探什么位置。然后,在循环中不断改变这两个变量的值就可以了。如果小人当前的位置 == 终点的位置,那么就说明小人到达了目的地,循环结束。
循环结束的条件只有这么一种吗?假如用户输入的终点就是一个不可能走到的点,那么根据深度寻路算法,小人最终会怎样呢?小人会找遍地图上的每一个角落,最终不断回退,回到起点。
所以为了照顾这种特殊情况,还应该设置小人如果回到起点(栈为空),那么循环结束。两种方式都可以跳出循环,因此我们还需要一个变量来区分这两种跳出循环的不同,即设置一个bool类型的isFind变量。
小人寻路过程 其实 就是 沿着链表找尾节点套路 升级版。辅助地图其实就是一个用数组存储的更复杂的链表(树)(指逻辑结构),只不过位置关系可以通过内存中的物理地址关系描述清楚。因为多了许多岔路,所以需要在原来套路的基础上再添加一个 描述方向的变量,即记录试探点的变量。
0x06 准备栈
栈可以调用系统的栈,也可以自己写一个栈,建议自己写一个,因为系统的栈功能太多了,调用后我们写出来的程序会比较大。一般公司会对应用程序的大小会有一个严格的限制,如果功能都实现了,但是应用程序的大小超过了标准,那么也不行。
0x07命令行版本的动画显示:
首先写一个传入地图首地址和人当前坐标就可以打印地图和人的函数。然后将这个函数放在寻路循环里,人的位置每更新一次就显示一次地图和人。只要在函数中加入清屏函数,每次显示后清一次屏就可以做成动画效果啦!
0x08 图形界面版本:
由于vim暂时无法使用第三方库,所以这个暂时放一放哈,视屏到第二节开头5分钟,等我配置好vim,再拿下它。
补充一点:关于用动态数组实现栈,insert的写法,特别注意capacity+=(capacity>>1)?(capacity>>1):1;
为什么用位运算?
>>1相当于/2 但是位运算的速度要远远快于整数除法。改为除以2也正确。
为什么用三目运算法?
这是为了兼容 capacity ==0 还有!!!特别注意: capacity == 1 的情况,如果只写capacity += capacity/2; 遇到capacity == 1 或者 0 那么capacity的大小是不会发生变化的!程序会异常中断。
为什么用capacity?
这是为了减少开内存的次数。经过科学计算以1.5倍左右的速度扩容,即最大限度的减少开内存的次数,最大限度的提高存储密度,节约内存。
using namespace std;
template <class T>
class MyStack{
T* pBuff;
int len;
int capacity;
public:
MyStack(){
pBuff = NULL;
len = 0;
capacity = 0;
}
//˜MyStack(){if(pBuff) delete[] pBuff;}
bool push(const T& data);
T pop();
T getTop();
void travel();
bool isEmpty();
};
template<class T>
bool MyStack<T>::push(const T& data){
len++;
if(len > capacity){
capacity = capacity + ((capacity/2)?capacity/2:1);
T* pNew = new T[capacity];
if(!pNew){
cout<<"开内存失败"<<endl;
return false;
}
memset(pNew,'\0',sizeof(T)*(capacity));
memcpy(pNew,pBuff,sizeof(T)*(len-1));
pNew[len-1] = data;
delete[] pBuff;
pBuff = pNew;
return true;
}else{
pBuff[len-1] = data;
return true;
}
}
template<class T>
void MyStack<T>::travel(){
cout<<"栈:"<<endl;
for(int i = len-1;i>=0;i--){
cout<<pBuff[i]<<endl;
}
}
template<class T>
T MyStack<T>::pop(){
T temp = pBuff[len-1];
capacity--;
len--;
T* pNew = new T[capacity];
memset(pNew,'\0',sizeof(T)*(capacity));
memcpy(pNew,pBuff,sizeof(T)*(len));
delete[] pBuff;
pBuff = pNew;
return temp;
}
template<class T>
bool MyStack<T>::isEmpty(){
return !len;
}
template<class T>
T MyStack<T>::getTop(){
return pBuff[len-1];
}
#include "00-MyStack.h"
//#include <windows.h>
#include <graphics.h>
//x 横 列数
#define COLS 8
//y 竖 行数
#define ROWS 8
//每个格子大小
#define SPACE 50
//方向枚举
enum direct{p_up,p_right,p_down,p_left};
//辅助地图元素类型
struct pathNode{
int val; //这个点上是神马,是路还是墙
direct dir; //试探方向
bool isFind; //有没有走过 0 false 没走过 1 true 走过
};
//点结构
struct MyPoint{
int row; //y
int col; //x
};
//图片全局变量
IMAGE g_wall, g_road, g_people;
void printMap(int map[ROWS][COLS], MyPoint pos);
void initDraw();
void drawMap(int map[ROWS][COLS], MyPoint pos);
int _tmain(int argc, _TCHAR* argv[])
{
//1 实际地图
int map[ROWS][COLS] = {
{ 1, 1, 1, 1, 1, 1, 1, 1 },
{ 1, 0, 1, 0, 0, 0, 0, 1 },
{ 1, 0, 1, 0, 1, 0, 1, 1 },
{ 1, 0, 1, 0, 1, 0, 1, 1 },
{ 1, 0, 1, 0, 1, 0, 0, 1 },
{ 1, 0, 1, 0, 1, 0, 1, 1 },
{ 1, 0, 0, 0, 1, 0, 0, 1 },
{ 1, 1, 1, 1, 1, 1, 1, 1 }
};
//2 辅助地图
pathNode pathMap[ROWS][COLS] = { 0 };
for (int i = 0; i < ROWS; i++){
for (int j = 0; j < COLS; j++){
pathMap[i][j].val = map[i][j];
}
}
//4 起点 终点
MyPoint beginPos;
MyPoint endPos;
printf("请输入起点(x,y)\n");
scanf("%d,%d", &(beginPos.col), &(beginPos.row) );
printf("请输入终点(x,y)\n");
scanf("%d,%d", &(endPos.col), &(endPos.row));
//5 准备栈
MyStack<MyPoint> stack;
//6 标记起点走过
pathMap[beginPos.row][beginPos.col].isFind = true;
//7 起点入栈
stack.push(beginPos);
//8 寻路
//8.1 准备变量
//当前角色位置
MyPoint currentPos = beginPos;
//角色试探点
MyPoint searchPos;
bool isFindEnd = false;
initDraw();//准备图形界面
while (1){
searchPos = currentPos;
//暂时让试探点为当前点,然后稍微调整一下就好了。
switch (pathMap[currentPos.row][currentPos.col].dir)
{
case p_up:
//8.2 确定试探点
searchPos.row--;
//8.4 改变当前点试探方向
pathMap[currentPos.row][currentPos.col].dir = p_right;
//注意不论试探点能不能走,都要改变当前点的试探方向,因为如果不能走,自然要改变试探方向,如果能走,并不代表着这条路能到终点,万一是死胡同呢?那么就可能会回退到该点,如果方向不变,还是一头往死胡同里撞。
//8.3 试探试探点能不能走
if (pathMap[searchPos.row][searchPos.col].isFind != true &&//没有走过
pathMap[searchPos.row][searchPos.col].val != 1)//不是障碍
{//能走
//8.5 走
currentPos = searchPos;
//8.6 标记当前点已经走过
pathMap[currentPos.row][currentPos.col].isFind = true;
//8.7 当前点入栈
stack.push(currentPos);
}
break;
case p_right:
//8.2 确定试探点
searchPos.col++;
//8.4 改变当前点试探方向
pathMap[currentPos.row][currentPos.col].dir = p_down;
//8.3 试探试探点能不能走
if (pathMap[searchPos.row][searchPos.col].isFind != true &&//没有走过
pathMap[searchPos.row][searchPos.col].val != 1)//不是障碍
{//能走
//8.5 走
currentPos = searchPos;
//8.6 标记当前点已经走过
pathMap[currentPos.row][currentPos.col].isFind = true;
//8.7 当前点入栈
stack.push(currentPos);
}
break;
case p_down:
//8.2 确定试探点
searchPos.row++;
//8.4 改变当前点试探方向
pathMap[currentPos.row][currentPos.col].dir = p_left;
//8.3 试探试探点能不能走
if (pathMap[searchPos.row][searchPos.col].isFind != true &&//没有走过
pathMap[searchPos.row][searchPos.col].val != 1)//不是障碍
{//能走
//8.5 走
currentPos = searchPos;
//8.6 标记当前点已经走过
pathMap[currentPos.row][currentPos.col].isFind = true;
//8.7 当前点入栈
stack.push(currentPos);
}
break;
case p_left:
//8.2 确定试探点
searchPos.col--;
//8.3 试探试探点能不能走
if (pathMap[searchPos.row][searchPos.col].isFind != true &&//没有走过
pathMap[searchPos.row][searchPos.col].val != 1)//不是障碍
{//能走
//8.5 走
currentPos = searchPos;
//8.6 标记当前点已经走过
pathMap[currentPos.row][currentPos.col].isFind = true;
//8.7 当前点入栈
stack.push(currentPos);
}
else{//不能走
//8.8 回退
//8.8.1 退栈
stack.pop();
//8.8.2 当前点变为栈顶元素
currentPos = stack.getTop();
}
break;
}
printMap(map, currentPos);
drawMap(map, currentPos);
//找到终点 循环结束
if (currentPos.row == endPos.row &&
currentPos.col == endPos.col){
isFindEnd = true;
break;
}
//栈为空:回到起点 循环结束
if (stack.isEmpty())
break;
}
//9 打印整个路径
if (isFindEnd){
printf("path:");
while (!stack.isEmpty()){
//9.1 打印栈顶元素
printf("(%d,%d)\n", stack.getTop().row, stack.getTop().col);
//9.2 退栈
stack.pop();
}
}
LOGFONT font = { 0 };
font.lfHeight = 25;
font.lfWeight = 10;
settextstyle(&font);
outtextxy((COLS*SPACE - 110) / 2, (ROWS*SPACE - 25) / 2, L"恭喜你通关!");
while (1);
return 0;
}
void printMap(int map[ROWS][COLS], MyPoint pos){
system("cls");//清屏
for (int i = 0; i < ROWS; i++){
for (int j = 0; j < COLS; j++){
if (i == pos.row && j == pos.col){//人
printf("人");
}
else if (map[i][j] == 1){//障碍
printf("墙");
}
else{//路
printf(" ");
}
}
printf("\n");
}
Sleep(500);
}
void initDraw(){
initgraph(COLS*SPACE, ROWS*SPACE, SHOWCONSOLE);
loadimage(&g_wall, L"wall.jpg", SPACE, SPACE, true);
loadimage(&g_road, L"road.jpg", SPACE, SPACE, true);
loadimage(&g_people, L"people.jpg", SPACE, SPACE, true);
}
void drawMap(int map[ROWS][COLS], MyPoint pos/*人的当前位置*/){
cleardevice();//清屏
for (int i = 0; i < ROWS; i++){
for (int j = 0; j < COLS; j++){
if (i == pos.row && j == pos.col){//人
putimage(j*SPACE, i*SPACE, &g_people);
}
else if (map[i][j] == 1){//障碍
putimage(j*SPACE, i*SPACE, &g_wall);
}
else{//路
putimage(j*SPACE, i*SPACE, &g_road);
}
}
}
}