广度优先搜索--POJ迷宫问题
Description
int maze[5][5] = {
0, 1, 0, 0, 0,
0, 1, 0, 1, 0,
0, 0, 0, 0, 0,
0, 1, 1, 1, 0,
0, 0, 0, 1, 0,
};
它表示一个迷宫,其中的1表示墙壁,0表示可以走的路,只能横着走或竖着走,不能斜着走,要求编程序找出从左上角到右下角的最短路线。
Input
Output
Sample Input
0 1 0 0 0 0 1 0 1 0 0 0 0 0 0 0 1 1 1 0 0 0 0 1 0
Sample Output
(0, 0) (1, 0) (2, 0) (2, 1) (2, 2) (2, 3) (2, 4) (3, 4) (4, 4)
分析:这道题很明显是一道寻找最短路径的问题,那就应该选择广度优先搜索,首先来说说广搜吧,广搜的基本思想是这样的:
从初始状态S开始,利用规则,生成所有可能的状态。构成树的下一层节点,检查是否出现目标状态G,若未出现,就对该层所有状态节点,分别顺序利用规则。生成再下一层的所有状态节点,对这一层的所有状态节点检查是否出现G,若未出现,继续按上面思想生成再下一层的所有状态节点,这样一层一层往下展开。直到出现目标状态为止。
也就是如下图所示:
我们来看一下维基上的代码:
1 std::queue<node*> visited, unvisited; 2 node nodes[9]; 3 node* current; 4 5 unvisited.push(&nodes[0]); //先把root放入unvisited queue 6 7 while(!unvisited.empty()) //只有unvisited不空 8 { 9 current = (unvisited.front()); //目前應該檢驗的 10 11 if(current -> left != NULL) 12 unvisited.push(current -> left); //把左邊放入queue中 13 14 if(current -> right != NULL) //右边压入。因为QUEUE是一个先进先出的结构,所以即使后面再压其他东西,依然会先访问这个。 15 unvisited.push(current -> right); 16 17 visited.push(current); 18 19 cout << current -> self << endl; 20 21 unvisited.pop(); 22 }
首先是有一棵构建好的树,我们从跟节点开始,访问第一层子节点,然后是第二层...这些都应该不难理解。问题是,我们应该怎么初始化这样一棵树,然后怎么记录路径,因为一般给的都只是一个序列,首先你要根据这个序列初始化一棵树然后才能在这棵树的基础上去找。
这里就只考虑迷宫问题吧,首先题目给的是一个二维序列,那么我们就可以直接将这个二维序列看成一棵树,节点就是迷宫路上的每一个格子(非墙),走迷宫的时候,格子间的关系是什么呢?按照题目意思,我们只能横竖走,因此我们可以这样看,格子与它横竖方向上的格子是有连通关系的,只要这个格子跟另一个格子是连通的,那么两个格子节点间就有一条边。如果说本题再修改成斜方向也可以走的话,那么就是格子跟周围8个格子都可以连通,于是一个节点就会有8条边(除了边界的节点)。
下面是记录路径的问题,对于记录路径,我们可以采用回溯法,在每个node里面设一个变量记录它前面的元素,那么在遇到正确结果后就可以以该元素为根往回遍历直到最开始的元素,在每个节点处打印,那这样就把整条路径打印出来了。如:
1 struct node{ 2 int pre; 3 int x; 4 int y; 5 } path[100]; 6 7 void print(int i) {//当前节点 8 if (path[i].pre != -1) {//找到前面那个节点 9 print(path[i].pre); 10 cout << "(" << path[i].x << "," << path[i].y << ")" << endl; 11 } else {//最前面的那个节点 12 cout << "(" << path[i].x << "," << path[i].y << ")" << endl; 13 } 14 }
另外一个方法是采用一个stack来专门记录路径,下面我用一个流程来说明:
--》--》...--》--》
--》--》--》...--》--》...--》--》...--》--》
从流程中我们可以看出来了,就不多说了。。。
第三种方法是在node里面设一个list专门记录路径,但是这个方法要记得在每次走到下一个节点的时候将它的父节点的路径记录加上自己的坐标变成自己的路径记录,就像下面这样:
1 struct node { 2 list<int*> path; 3 int x, y; 4 node(list<int*> fatherList) { 5 path = fatherList; 6 int index[2] = {x, y}; 7 path.push_back(index); 8 } 9 };
那么在找到最后一个的路径的时候就可以直接打印它的path变量。
有人也许会有疑问,为什么广搜找到的路径就是最短了呢?,看看下面这个图你就明白了:
--》
发现数字的规律了吗?数字是分层的,在同一层的数字所代表的路径长度是相同的,一层层的遍历,当某层第一次到达了出口,这层肯定是最短路径所在层啦。
下面是那道题的代码:
1 #include <iostream> 2 3 using namespace std; 4 5 int map[5][5]; 6 7 //相邻四个节点 8 int borderUponX[4] = {0, 0, 1, -1}; 9 int borderUponY[4] = {1, -1, 0, 0}; 10 11 int front = 0, rear = 1; 12 13 struct node{ 14 int pre; 15 int x; 16 int y; 17 } path[100]; 18 19 void print(int i) {//当前节点 20 if (path[i].pre != -1) {//找到前面那个节点 21 print(path[i].pre); 22 cout << "(" << path[i].x << "," << path[i].y << ")" << endl; 23 } else {//最前面的那个节点 24 cout << "(" << path[i].x << "," << path[i].y << ")" << endl; 25 } 26 } 27 28 void bfsSearch(int x, int y) { 29 //开始节点(出发),前面没有节点了 30 path[front].x = x; 31 path[front].y = y; 32 path[front].pre = -1; 33 34 //当front == rear的时候说明已经走完了所以“相邻”节点 35 //且都不通 36 while (front < rear) { 37 for (int i = 0; i != 4; i++) { 38 //相邻节点坐标 39 int pathX = path[front].x + borderUponX[i]; 40 int pathY = path[front].y + borderUponY[i]; 41 42 //不符合的节点(遇到边界或已经走过了) 43 if (pathY < 0 || pathX < 0 || pathX > 4 || pathY > 4 || map[pathX][pathY]) 44 continue; 45 else {//将front的相邻的可以过去的并且是还没有走过的节点加到路径里面 46 map[pathX][pathY] = 1; 47 path[rear].x = pathX; 48 path[rear].y = pathY; 49 path[rear].pre = front; 50 rear++; 51 } 52 if (pathX == 4 && pathY == 4) { 53 //找到了一条路径,又是第一次找到 54 //那么就是最短路径了 55 print(rear - 1); 56 break; 57 } 58 } 59 front++; 60 } 61 } 62 63 int main(int argc, char const *argv[]) 64 { 65 for(int i = 0;i < 5;i++) 66 for(int j = 0;j < 5;j++) 67 cin >> map[i][j]; 68 69 bfsSearch(0,0); 70 return 0; 71 }