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变量做了大量的精彩事情, 另外,利用二进制进行数据的压缩存储的技巧得到了充分的印证,效果显著.
相关文章推荐
- 第八周(2) 项目2.1 - 对称矩阵压缩存储的实现与应用
- 第九周项目2-对称矩阵压缩存储的实现与应用(2)
- 第8周项目3- 对称矩阵压缩存储的实现与应用(3)
- 八(1)3 矩阵压缩存储的实现与应用2
- 第9周项目2对称矩阵压缩存储的实现与应用1
- 第九周项目2 - 对称矩阵压缩存储的实现与应用(1)
- 一个小菜鸟对数据在内存中的二进制存储的理解
- 第九周项目2-对称矩阵压缩存储的实现与应用(2)
- 第8周项目4- 对称矩阵压缩存储的实现与应用
- 第九周项目二--对称矩阵压缩存储的实现与应用(2)
- 第九周项目2——对称矩阵压缩存储的实现与应用(2)
- 第九周 项目二(2) 对称矩阵压缩存储的实现与应用
- 第八周 项目三 — 对称矩阵压缩存储的实现与应用(2)
- 第九周项目2-对称矩阵压缩存储的实现和应用(1)
- 第九周项目2-对称矩阵压缩存储的实现与应用(1)
- 第九周项目2- 对称矩阵压缩存储的实现与应用(2)
- 八周 1 3 对矩阵的压缩存储的实现与应用
- android应用开发存储方式之SQLite(android自带的数据库)的一些个人理解
- 第9周SHH数据结构-【项目2-对称矩阵压缩存储的实现与应用】
- 第9周项目2-(2)对称矩阵压缩存储的实现与应用