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

A*启发搜索+二进制压缩存储; 代码效率不错,对二进制的应用理解越来越深刻了;

2010-05-17 19:44 190 查看

电子老鼠闯迷宫

时限:1000ms 内存限制:10000K  总时限:3000ms

描述:

有一只电子老鼠被困在如下图所示的迷宫中。这是一个12*12单元的正方形迷宫,黑色部分表示建筑物,白色部分是路。电子老鼠可以在路上向上、下、左、右行走,每一步走一个格子。现给定一个起点S和一个终点T,求出电子老鼠最少要几步从起点走到终点。

输入:

本题包含一个测例。在测例的第一行有四个由空格分隔的整数,分别表示起点的坐标S(x.y)和终点的坐标T(x,y)。从第二行开始的12行中,每行有12个字符,描述迷宫的情况,其中'X'表示建筑物,'.'表示路.

输出:

输出一个整数,即电子老鼠走出迷宫至少需要的步数。

输入样例:

2 9 11 8
XXXXXXXXXXXX
X......X.XXX
X.X.XX.....X
X.X.XX.XXX.X
X.X.....X..X
X.XXXXXXXXXX
X...X.X....X
X.XXX...XXXX
X.....X....X
XXX.XXXX.X.X
XXXXXXX..XXX
XXXXXXXXXXXX

输出样例:

28

提示:

来源:

 

 

 

#include <iostream>
#include <queue>
using namespace std;

#define FLAG1 0x02
#define CHECK 0x00000001
#define CHECK1 0x01

typedef unsigned int Byte;

Byte flag[5]={0};
int s,e;
int turn[4]={1,-1,12,-12};

struct Node
{
int pos;
unsigned char vi_can;
int g,h;
}matrix[144];

struct compare
{
bool operator() (Node *p,Node *q)
{
return (p->g+p->h)>(q->g+q->h);
}
};

priority_queue<Node*,vector<Node *>,compare> minHeap;

int getHash(int pos)
{
return CHECK&(flag[pos/32]>>(31-pos%32));
}

int getVisited(int pos)
{
return CHECK1&(matrix[pos].vi_can);
}

int getCan(int pos)
{
return FLAG1&&matrix[pos].vi_can;
}

void setHash(int pos)
{
flag[pos/32]|=CHECK<<(31-pos%32);
}

void setVisited(int pos)
{
matrix[pos].vi_can|=CHECK1;
}

int getH(int pos)
{
return abs(pos/12-e/12)+abs(pos%12-e%12);
}

bool checkBound(int now,int pos)
{
if(pos>=0&&pos<=143)
{
int rn=now/12;
int rp=pos/12;
if(abs(pos-now)==1&&rn!=rp)
{
return false;
}
return true;
}
else
{
return false;
}
}

int A_STAR()
{
int distH=getH(s);
matrix[s].h=distH;
setVisited(s);
minHeap.push(&matrix[s]);
Node *p;
int state;

while(!minHeap.empty())
{
p=minHeap.top();
minHeap.pop();
state=p->pos;
setHash(state);

if(state==e)
{
return p->g;
}

for(int i=0;i<4;++i)
{
int toPos=state+turn[i];
if(checkBound(state,toPos)==true&&getCan(toPos)==0)
{
if(getVisited(toPos)==0)
{
matrix[toPos].g=p->g+1;
matrix[toPos].h=getH(toPos);
setVisited(toPos);
minHeap.push(&matrix[toPos]);
}
else
{
if(getHash(toPos)==0)
{
if(matrix[toPos].g>p->g+1)
{
matrix[toPos].g=p->g+1;
}
}
}
}
}
}
return 0;
}

int main()
{
int sx,sy,ex,ey;
cin>>sx>>sy>>ex>>ey;
s=(sx-1)*12+(sy-1);
e=(ex-1)*12+(ey-1);
char in;
for(int i=0;i<144;++i)
{
cin>>in;
matrix[i].vi_can=0;
switch(in)
{
case 'X':
{
matrix[i].vi_can|=FLAG1;
}break;
case '.':
{

}break;
default:
{
return -1;
};break;
}
matrix[i].pos=i;
matrix[i].g=0;
matrix[i].h=0;
}
cout<<A_STAR()<<endl;
return 0;
}


 

代码注释在提交OJ时候删了.....   

 

我讲一下核心算法:

 

A*:

1.封闭列表 , 由一个hash数组标记每个结点是否封闭, 这里采用了二进制压缩, 用1bit标记一个位置,矩阵共12*12个位置,所以需要144bits, 一个unsigned int 32bits, 所以用了5个unsigned int.   封闭列表的作用是判断结点封闭还是开放.

 

2.开放列表,  在最小堆中的结点都是开放, pop()过的结点都被封闭了.   所以,每次pop()一个开放结点后,sethash进行封闭,然后拓展周边未封闭的结点, 核心算法就是跑这样一个流程.    但是, 还需要判断的一点就是结点是否被访问过(访问过或者开放,其hash都可能为0,所以需需要unsigned char标记是否访问), hash只能判断是否封闭, 不封闭的结点可能是不在开放列表中的,可能是在开放列表中的.  对于在开放列表中的结点只需要更新数据,而不在开放列表中的结点需要入堆.

为了区分结点是否在开放列表中,在每个结点内部设置了一个unsigned char变量, 用其中的最0位标记是否开放.  另外,同时也用第1位标记该格子是否为障碍物,所以这个unsinged char vi_can的使用也压缩了存储量, 聚集了2个重要信息.

 

3.迷宫的存储方式, 为了方便取4个方向周边格子的下标,用一维数组存储整个矩阵,取相邻上下格子pos只需要+-12, 取左右相邻格子只需要+-1,免去了二维数组存储时row,col都要变的尴尬.   

 

另一个重要方面, 即开放列表存储状态结点的方式, 是直接存储了数组指针, 每次pop()得到一个结点的指针, 根据结构体内pos获知它所在位置, 根据pos取pos+1,pos-1,pos+12,pos-12 这些周边状态结点, 进行更新操作.. 由于pos已经,直接去迷宫数组里取array[pos+1]就可以得到右边的结点,根据结点内的unsigned char的0位判断是否已访问,如果没访问,则更新数据入堆,否则判断是否封闭,不封闭则说明其在开放列表中,只需要更新数据即可.   另外,可以根据unsigned char的第1位判断是否该格为障碍物, 利用pos这个值与矩阵大小的关系,用checkBound()函数判断该格子是否在迷宫范围内.

 

以上就是写这个算法时的所有考虑,感觉比较亮点的是迷宫从二维存储为一维, 利用pos变量做了大量的精彩事情, 另外,利用二进制进行数据的压缩存储的技巧得到了充分的印证,效果显著.

 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  存储 matrix struct 算法