题目连接:http://poj.org/problem?id=3083
题目大意:这是一道有基于图搜索的题目,主要考察两个方面,
一个是DFS搜索图中左优先、右优先的相关路径。
另一个考察点是BFS用于搜索图中最短的路径。
解题报告:
大致的算法步骤如下:
使用DFS思想来解决题目中的左优先、右优先步数计算
使用BFS思想来解决题目中的最短路径步数的计算。
根据本题的所要实现的是在图中的某一点,向四个方向有计划的进行迈进,
所以在表示图的数据结构上,采用矩阵方法所对应的二维数组来表示在时间空间效率上是较优的。
1.InitGraph()
对于图对应的字符串的读取处理的方法大致分为两种,
一个是开辟一个最大限度的40 * 40的二维字符串数组,
在嵌套分别对应行和列的两个循环后,
将图对应的迷宫字符矩阵读入到数组中。
然后在一个个的读取判断并且记录哪一个是S、E等等。
另一个方法就是只申请一个40的一维字符串数组,
外面一个针对于迷宫字符串h的大循环,
每次读取一行。
然后针对改行进行一个针对于w的小循环,
然后一一记录S,E所在的坐标。
1,如果读入的是E,记录E的坐标Ex Ey,将map[Ex][Ey]置为2
2.如果读入的是S,记录S的坐标Sx Sy,并且将map[Sx][Sy]的值置为-1
3.如果读入的是'#',将对应其坐标的map二维数组值置为 -1
4.如果读入的是'.',将对应其坐标的map二维数组值置为0
对于当前坐标(x,y)在相应的h*w的矩阵中,x对应的是h,而 y对应的是w。
这个是根据个人喜好而定的,不是绝对的。
1 void InitGraph() 2 { 3 int i,j; 4 5 6 7 for(i = 0; i < h; i++) 8 { 9 scanf("%s", line); 10 getchar(); 11 12 for(j = 0 ; j < w; j++) 13 { 14 15 16 if(line[j]=='#') 17 { 18 map[i][j] = -1; 19 } 20 21 else if(line[j]=='S') 22 { 23 Sx = i; 24 Sy = j; 25 map[i][j] = -1; 26 } 27 else if(line[j]=='E') 28 { 29 Ex = i; 30 Ey = j; 31 map[i][j] = 2; 32 } 33 34 else if(line[j]=='.') 35 { 36 map[i][j] = 0; 37 } 38 } 39 40 } 41 42 if(Sx == 0)//on the 0 direction should be 2 43 { 44 d = 2; 45 46 47 } 48 49 else if(Sx == h -1) //on the 2 direction should be 0 50 { 51 d = 0; 52 53 } 54 55 else if(Sy == 0) // on the 1 , direction should be 3 56 { 57 d = 3; 58 59 60 61 } 62 63 else if(Sy == w-1)//on the 3 , direction should be 1 64 { 65 d = 1; 66 67 } 68 69 70 71 return; 72 }
因为根据题目的要求,Output的前两个步骤分别是针对左转优先和右转优先的,
所以,在S的起始位置就应该确定好初始的方向。
根据S的所在位置设定它的前后左右的方向。
如下图所示:
根据题意可以知道,S的位置共分为一下几种情况:
a.
S在长方形的最下边上:
此时设定方向为0
b.
S在长方形的最右边上:
此时设定方向为1
c.
S在长方形的最左边上:
此时设定方向为3
d.
S在长方形的最上边上:
此时设定方向为2
所设定的方向也就是根据当前的左(右)转优先所要前进的方向。
2.DFS()
在这里需要说明一下,其实针对本题的DFS算法的实现大致有一下几种:
a.根据左优先还是右优先不同的方式分别针对两段实现代码。
b.使用相应的标识符来标识当前是左优先还是右优先,然后在实现方法中在仔细进行判断。
c.根据左优先还是右优先的实现方式来确定两个direction的不同设定,
根据上述(a b c d四种情形的方向设定)
的规律可以总结出,如果设当前方向为D并且是左转有限的话,
那么它的旋转方向一定是这样的:
先左转方向为1
如果不可以前进,那么接下来选取的方向为0,
如果依旧不能行进,接下来的方向选取的是3;
所以如果是左转优先的话,
假设当前结点是0
对四个方向的访问顺序是:
首先左转90度 此时方向为1(如果行不通)->再右转90度 此时方向为0(如果行不通)
->再右转90度 此时方向为3 (如果行不通)->再右转90度 此时方向为2(这个时候刚刚好就是原路返回了)
同样的情况如果是右转优先的话,
假设当前结点是0
对四个方向的访问顺序是:
首先右转90度,然后依次左转90度,如果不满足在左转90度,
如果还是不满足继续左转90度,如果仍旧不满足在此左转90度,
同样的在最后这次左转90度的时候,结点是按照原路返回的。
同样根据方向事先是定义好的,如上图所示:
上(0)下(2)左(1)右(1)
对于当前的点的方向为D的话,
左转为(D+1)%4
右转为(D+3)%4
所以左转优先对应(D+i)%4的话呢,
i的值分别是 1 3 3 3
右优先的话呢,对应的是同样的公式:
(D+i)%4
i的值分别对应的是 3 1 1 1
所以可以定义一个数组turn[2][2]={{1,3},{3,1},{3,1},{3,1}}
所以如果让一个结点满足左转优先的条件的话,
可以设置一个for循环配合一个,
设定数组的原因其实就是LZ一厢情愿的想要把左右优先对应的两种方法合并为一组代码实现。
1 declare global variable 2 turn[2][2] ={{1,3},{3,1},{3,1},{3,1}} 3 4 if left set turn = 0 5 if right set turn =1 6 7 for(i = 0 ; i < 4; i++) 8 { 9 D = (D+turn[i][turn])%4 10 }
方向选取的问题解决了之后呢,还有一点需要注意一下,
那就是坐标前后移动的问题,
设定当先坐标为(x,y)
如果想向方向X(X取值为 0,1,2,3 中其中一个)移动的话,
则需要对x,y 坐标进行相加减才可以得出。
所以在每次知道要行进的方向之后,只要让当前坐标对应的加上对应该点坐标的值
就可以实现向该方向行进一个单位了。
所以可以实现设定好数组direction [4][2]={{-1.,0},{0,-1},{1,0},{0,1}}
其中4 分别对应的是四个行进方向(0,1,2,3)
2对应的是x,y坐标的加减1或是0对应的值的变化
例如当前所标的方向为D,坐标为(x,y)我想要向D方向前进一个单位的话,
可以使用以下代码实现:
nextX = x+direction[D][0];
nextY = y +direction[D][1];
而当前的方向是根据外层的一个 4次的循环来实现的,也就是上面的代码,
需要根据DFS方法传进的参数来决定是左转优先(DFS(0))
还是右转优先(DFS(1))来判断当前所选取的方向。
如是下来就可以实现DFS这一方法了,具体代码如下:
1 int DFS(int direction) 2 { 3 4 5 int s=2; 6 //this is used to count the steps 7 8 int tmpx,tmpy; 9 10 int i; 11 int tmp; 12 13 tmp =d; //d is already set in the InitGraph method
15 17 switch(d) 18 { 19 case 0: 20 { 21 x = Sx-1; 22 y = Sy; 23 break; 24 } 25 case 1: 26 { 27 x = Sx; 28 y = Sy-1; 29 break; 30 } 31 case 2: 32 { 33 x = Sx+1; 34 y = Sy; 35 break; 36 } 37 case 3: 38 { 39 x = Sx; 40 y = Sy+1; 41 break; 42 } 43 } 44 45 //上述的switch case 是根据具体的方向向前迈进一步进入到迷宫中去,
//这样可以免去在'S'点出就四周搜索的情况,故记录步数的变量已经置为2 46 while(map[x][y]==0) 47 { 48 49 for(i = 0; i < 4; i++) 50 { 51 tmp = (tmp+turn[i][direction])%4; 52 53 tmpx = x+dir[tmp][0]; //试探性的向该方向进行迈进,先设为tmpx/tmpy 如果tmpx tmpy
//满足迈进条件的话,在将其赋值为x,y
54 tmpy = y+dir[tmp][1]; 55 56 if(tmpx >=0 && tmpx< h && tmpy>=0 && tmpy< w && map[tmpx][tmpy]>=0 ) 57 { 58 x=tmpx; 59 y=tmpy; 60 61 s++; 62 63 break; 64 65 66 } 67 68 69 } 70 71 72 73 }//while 74 75 return s; 76 77 78 }
3.BFS()
这个方法主要是用于求取图中的'S'和'E'之间的最短路径的最短步数的,
其实就是普通的BFS实现就可以了,但是为了方便起见,可以自己简单的写一个
queue来替代模板函数的。
许多人写了一个Point的结构体其中包含了当前节点的坐标x,y 以及上从S结点到当前结点所需要的步数的记载,
又为了很好地模拟BFS基本实现方法创建了一个vis的数组用于标示图中的某一个结点是否被访问过。
LZ觉得这样比较麻烦的,
LZ是这样想的,最短路径是在最后求取的,即Sample Out 最后输出的数值,
这样的话,就可以借助于修改map这个二维数组本身来实现它的vis的功能
而且同样也可以使用map[][]对应的二维数组的x,y结点所确定下来的数值来记录从S点到当前结点所走的步数的。
而且可以仅仅使用一个q这一个int来实现记录当前数组的(x,y)坐标的值。
这样的话,是要声明一个queue<int>Q;每次将q进行压入队列的话,就可以实现将一个坐标点(x,y)进行压入队列了。
即,在初始化的时候,应该将Sx, Sy 也就是S在map数组中的坐标点(x,y)
首先压入到队列中:
int q = Sx*(h-1)+Sy;
Q.push(q);
然后在出队的时候:
q = Q.front();
Sx = q/(h-1);
Sy = q%(h-1);
这样的话节省了很多的空间,又因为Sx的范围是[0,h-1)
所以q的值最大也会被限定在h*h的范围内,而h*h的是一定是小于 40*40=1600的,所以q设定为int即可。
然后实现map二维数组的重复使用,当然这会改了原图,也就是使得图所对应的二维数组与初始化图的时候有所不同。
但是,BFS是最后一个调用的方法,所以即便图被修改了也不会影响到前面两个方法的调用的。
接下来是我的实现代码:
map在初始化的时候已经设定定好了,
在这个方法里面又将map[Ex][Ey]=0
所以map[x][y] =0 则代表(x,y)对应的结点未被访问过。
反之,则被访问过或是不存在路径
又根据BFS的访问规则:一个结点不能重复被访问,所以一个结点(x,y)在被访问的时候,
将map[x][y]的数值置为S到该点的步数了,那么它将不为0,
1.map[x][y]>0代表的是该结点(x,y)已经访问过
2.map[x][y]=0代表的是(x,y)未被访问过
3.map[x][y]<0代表的是(x,y)这一坐标在途中是'#'是不可走的。
所以最终map[Ex][Ey]中的数值对应的是S到E之间所走过的最短路径的数值
1 int BFS() 2 { 3 4 int tmpx,tmpy; 5 6 7 int p; 8 int tmp; 9 queue<int>Q; 10 11 map[Ex][Ey] = 0; 12 map [Sx][Sy] = 1; 13 14 15 p = Sx*(h-1) +Sy; 16 Q.push(p); 17 18 while(map[Ex][Ey]==0 )19 { 20 p = Q.front(); 21 Q.pop(); 22 23 for(int i = 0; i < 4; i++) 24 { 25 26 tmpx = p/(h-1)+dir[i][0]; 27 tmpy = p%(h-1)+dir[i][1]; 28 29 if(map[tmpx][tmpy]==0&&tmpx>=0 && tmpx < h && tmpy >= 0 && tmpy <w)
//if the value of map[x][y]=0 ,it presents that map is the first visited 30 { 31 map[tmpx][tmpy] = map[p/(h-1)][p%(h-1)]+1; 32 //this is use to counter the steps 33 //and after it plus 1 , map[][] is visited 34 35 36 if(tmpx==Ex&&tmpy==Ey) 37 return map[Ex][Ey]; 38 39 tmp = tmpx*(h-1) + tmpy; 40 Q.push(tmp); 41 42 }//if 43 } 44 } 45 46 47 48 }
下面折叠的代码是整个程序的实现代码:
以及在程序调试的时候注释掉的用于打印结点行走轨迹的代码:
1 #include<stdio.h> 2 #include<queue> 3 4 using namespace std; 5 6 int DFS(int); 7 int BFS(); 8 void InitGraph(); 9 10 int map[42][42]; 11 12 char line[42]; 13 14 int dir[4][2] ={{-1,0},{0,-1},{1,0},{0,1}}; 15 16 int turn[4][2]={{1,3},{3,1},{3,1},{3,1}}; 17 18 int Sx,Sy,Ex,Ey; 19 int h,w; 20 21 int x,y; 22 23 int d; 24 25 void InitGraph() 26 { 27 int i,j; 28 29 30 31 for(i = 0; i < h; i++) 32 { 33 scanf("%s", line); 34 getchar(); 35 36 for(j = 0 ; j < w; j++) 37 { 38 39 40 if(line[j]=='#') 41 { 42 map[i][j] = -1; 43 } 44 45 else if(line[j]=='S') 46 { 47 Sx = i; 48 Sy = j; 49 map[i][j] = -1; 50 } 51 else if(line[j]=='E') 52 { 53 Ex = i; 54 Ey = j; 55 map[i][j] = 2; 56 } 57 58 else if(line[j]=='.') 59 { 60 map[i][j] = 0; 61 } 62 } 63 64 } 65 66 if(Sx == 0)//on the 0 direction should be 2 67 { 68 d = 2; 69 70 71 } 72 73 else if(Sx == h -1) //on the 2 direction should be 0 74 { 75 d = 0; 76 77 } 78 79 else if(Sy == 0) // on the 1 , direction should be 3 80 { 81 d = 3; 82 83 84 85 } 86 87 else if(Sy == w-1)//on the 3 , direction should be 1 88 { 89 d = 1; 90 91 } 92 93 94 95 return; 96 } 97 98 99 int DFS(int direction) 100 { 101 102 103 int s=2; 104 //this is used to counter the steps 105 106 int tmpx,tmpy; 107 108 int i; 109 int tmp; 110 111 tmp =d; 112 113 114 115 switch(d) 116 { 117 case 0: 118 { 119 x = Sx-1; 120 y = Sy; 121 break; 122 } 123 case 1: 124 { 125 x = Sx; 126 y = Sy-1; 127 break; 128 } 129 case 2: 130 { 131 x = Sx+1; 132 y = Sy; 133 break; 134 } 135 case 3: 136 { 137 x = Sx; 138 y = Sy+1; 139 break; 140 } 141 } 142 143 144 // printf("Sx =%d Sy =%d\n", Sx, Sy); 145 146 // printf("Ex = %d Ey = %d\n", Ex, Ey); 147 148 // printf("step: 1 :(%d,%d)\n", Sx,Sy); 149 150 // printf("step: %d :(%d,%d)\n", s,x,y); 151 152 153 while(map[x][y]==0) 154 { 155 156 for(i = 0; i < 4; i++) 157 { 158 // printf("before : %d\n", tmp); 159 160 tmp = (tmp+turn[i][direction])%4; 161 162 // printf(" after :%d\n\n", tmp); 163 164 tmpx = x+dir[tmp][0]; 165 tmpy = y+dir[tmp][1]; 166 167 if(tmpx >=0 && tmpx< h && tmpy>=0 && tmpy< w && map[tmpx][tmpy]>=0 ) 168 { 169 x=tmpx; 170 y=tmpy; 171 172 s++; 173 174 // printf("step: %d :(%d,%d)\n", s,x,y); 175 176 177 break; 178 179 180 } 181 182 183 } 184 185 186 187 }//while 188 189 return s; 190 191 192 } 193 194 int BFS() 195 { 196 197 int tmpx,tmpy; 198 199 200 int p; 201 int tmp; 202 queue<int>Q; 203 204 map[Ex][Ey] = 0; 205 map [Sx][Sy] = 1; 206 207 208 p = Sx*(h-1) +Sy; 209 Q.push(p); 210 211 while(map[Ex][Ey]==0 ||!Q.empty()) 212 { 213 p = Q.front(); 214 Q.pop(); 215 216 for(int i = 0; i < 4; i++) 217 { 218 219 tmpx = p/(h-1)+dir[i][0]; 220 tmpy = p%(h-1)+dir[i][1]; 221 // printf("i: %d\n",i); 222 223 if(map[tmpx][tmpy]==0&&tmpx>=0 && tmpx < h && tmpy >= 0 && tmpy <w) 224 //if the value of map[x][y]=0 ,it presents that map is the first visited 225 { 226 map[tmpx][tmpy] = map[p/(h-1)][p%(h-1)]+1; 227 //this is use to counter the steps 228 //and after it plus 1 , map[][] is visited 229 230 // printf("before step: (%d,%d)\n", p/(h-1), p%(h-1)); 231 232 if(tmpx==Ex&&tmpy==Ey) 233 return map[Ex][Ey]; 234 235 tmp = tmpx*(h-1) + tmpy; 236 Q.push(tmp); 237 238 // printf("step : %d (%d ,%d) push in queue\n\n",map[tmpx][tmpy], tmpx, tmpy); 239 240 241 }//if 242 } 243 } 244 245 246 247 } 248 249 int main() 250 { 251 int n; 252 253 scanf("%d",&n); 254 while(n--) 255 { 256 scanf("%d%d", &w, &h); 257 InitGraph(); 258 259 printf("%d %d",DFS(0),DFS(1)); 260 printf(" %d\n", BFS()); 261 } 262 return 0; 263 }