在前面的博客中,我们已经解决了简单迷宫的求解问题以及多出口(不带环)迷宫求最短路径的问题。求带环的多出口迷宫的最短路径,需要我们在之前解决问题的思想上做出调整和改进。
- 首先,我们先来定义一个带环的多出口迷宫:
图中黄色标记为一个环,0表示墙,即无法落脚;1表示可以落脚;之后我们会用2来标记走过的路。
我们默认出口为四个边界上的点。若入口也为边界点,则出口与入口不能重合。
- 解题思路的改进:
(1)CanStay函数。在之前的代码中,我们判断当前点是否能落脚,实现判断当前点是否在边界外,若在边界外则不能落脚;若在边界内且当前点的值为1,则可以落脚。但在求带环的多出口迷宫的最短路径时,我们还需要判断cur_value是否大于pre_value+1,若满足则应该落脚,若不满足则不应该落脚。
(2)Mark函数。在之前的代码中,我们将走过的路径点的值全部改为2,表示这个点已经走过。但在求带环的多出口迷宫的最短路径的问题时,我们不能简单的将走过的路径的点全部标记为一个特定的值。
如上图所示,根据我们定义的按顺时针方向探测相邻点,那么该迷宫找到的第一条路径应该如图中蓝色数字标示所示。当找到一条路径后,进行出栈回溯,继续寻找下一条路径。
此时就又新找到了一条路径。然后进行出栈回溯,继续新一轮的探测:
当按顺时针方向进行探测时,走到图中红色圈出来的点后,就会发现,此时周围相邻的四个点要么无法落脚,要么已经走过,所以进行出栈回溯操作,继续寻找下一条路径。
当回溯到上一个点后,按照顺时针探测的规则,下一个要走的点即为图中圈出来的点,当走到下一个点时,就相当于又找到了一条路径。相同地,进行出栈回溯,寻找下一条路径。此时会回溯到入口点,然后进行新一轮的探测。
当回到入口点下面这个点时,向下进行探测时,发现下一个点已经被标记过,而右边的点不能落脚,所以探测就结束了。然而图中我们圈出来的这条路径,却没有被找到!
这就说明,在求带环的多出口迷宫的最短路径上,我们不能单纯地将已经走过的点标记为同一个特定值,而是应该采用新的方法:我们规定,将能落脚的入口点标记为2,之后的每个点都为上一个点的值+1,这样我们就得到了第一条路径的标记图:
然后出栈回溯,寻找第二条路径:
此时就找到了第二条路径。出栈回溯,继续寻找下一条路径:
当走到图中标记为10的位置时,探测发现周围四个相邻点的值。探测上边的点时,发现3>10+1这个条件不满足,所以不应该落脚;探测右边和左边的点值为0,不能落脚,所以回溯到标记点为9的位置,继续新一轮的探测,发现当前位置左边的点满足标记条件,则直接落脚,并且此时又找到了一条路径。
然后进行出栈回溯,回到标记为3的位置,按照顺时针方向,向下探测,发现10>3+1,所以可以落脚,并且将值改为4,之后的步骤同理:
当走到图中圈出来的点时,向上探测,发现值为0不能落脚;向左探测发现7>6+1不成立,所以不能落脚;向下探测发现9>6+1成立,所以往下走,并修改标记值:
这是就又找到了一条新的路径。以此方法类推,便能找到所有的路径。
(3)就如求解多出口迷宫(不带环)问题一样,由于多出口迷宫存在着多条路径,所以我们需要定义两个栈,一个栈中存放着当前走过的路径的数据,另一个栈中存放着最短路径的数据。每找到一条路径,就将当前路径的长度与最短路径的长度作比较,若存放最短路径的栈为空栈,或者当前路径的长度小于最短路径的长度,就用存放着当前路径的栈中的数据替代存放最短路径的栈中的数据。当所有路径都找到以后,此时存放最短路径的栈中的数据即可表示该迷宫的最短路径。
maze.h:
#pragma once #include <stdio.h> #include<stdlib.h> #include<stddef.h> #define MAX_ROW 6 #define MAX_COL 6 typedef struct Point{ int row; int col; }Point; typedef Point SeqStackType; typedef struct SeqStack{ SeqStackType *data; size_t size; size_t capacity; }SeqStack; typedef struct Maze{ int map[MAX_ROW][MAX_COL]; }Maze;
maze.c:
#include "maze.h" int map[MAX_ROW][MAX_COL]={ {0,1,0,0,0,0}, {0,1,1,1,0,0}, {0,1,0,1,1,1}, {1,1,1,1,0,0}, {0,0,1,0,0,0}, {0,0,1,0,0,0} }; void MazeInitShortPathWithCycle(Maze* maze){ if(maze == NULL) return; size_t i = 0; for(;i < MAX_ROW;i++){ size_t j = 0; for(;j < MAX_COL;j++){ maze->map[i][j] = map[i][j]; } } return; } void SeqStackDebugPrint(SeqStack* stack,const char* msg){ printf("%s\n",msg); if(stack == NULL) return; size_t i = 0; for(;i < stack->size;i++){ printf("(%d,%d)\n",stack->data[i].row,stack->data[i].col); } printf("\n"); return; } void MazePrint(Maze* maze){ if(maze == NULL) return; size_t i = 0; for(;i < MAX_ROW;i++){ size_t j = 0; for(;j < MAX_COL;j++) printf("%2d ",maze->map[i][j]); printf("\n"); } return; } int CanStayWithCycle(Maze* maze,Point cur,Point pre){ if(maze == 0) return 0; //判断当前点是否在地图上 if(cur.row < 0 || cur.row >= MAX_ROW || cur.col < 0 || cur.col >= MAX_COL){ return 0; } //判断是否为墙 int cur_value = maze->map[cur.row][cur.col]; int pre_value = maze->map[pre.row][pre.col]; if(cur_value == 0){ return 0; } //若当前点是1,可直接落脚 if(cur_value == 1) return 1; //若当前点已走过,比较cur_value和pre_value的大小关系 //若cur_value > pre_value+1,则应该落脚;否则不应该落脚 if(cur_value > pre_value+1) return 1; return 0; } void MarkWithCycle(Maze* maze,Point cur,Point pre){ if(pre.row == -1 && pre.col == -1){ //单独考虑非法点的情况 maze->map[cur.row][cur.col] = 2; return; } int pre_value = maze->map[pre.row][pre.col]; maze->map[cur.row][cur.col] = pre_value + 1; } int IsExit(Maze* maze,Point cur,Point entry){ (void)maze; //1.判断当前点是不是入口,若为入口,则不是出口 if(cur.row == entry.row && cur.col == entry.col){ return 0; } //2.如果当前点在地图边界上,说明是出口 if(cur.row == 0 || cur.row == MAX_ROW-1 || cur.col == 0 || cur.col == MAX_COL-1){ return 1; } return 0; } void SeqStackAssgin(SeqStack* from,SeqStack* to){ //释放to中的原有内存 SeqStackDestroy(to); //根据from中的元素个数确定内存申请的大小,给to重新申请一个足够的内存 to->size = from->size; to->capacity = from->capacity; to->data = (SeqStackType*)malloc(to->capacity * sizeof(SeqStackType)); //再进行数据拷贝 size_t i = 0; for(;i < from->size;i++){ to->data[i] = from->data[i]; } } void _GetShortPathWithCycle(Maze* maze,Point cur,Point pre,Point entry,SeqStack* cur_path,SeqStack* short_path){ printf("cur:(%d,%d)\n",cur.row,cur.col); //1.判断当前点能否落脚 if(!CanStayWithCycle(maze,cur,pre)){ return; } //2.若能落脚,给当前位置做一个标记 //同时将当前点插入到cur_path MarkWithCycle(maze,cur,pre); SeqStackPush(cur_path,cur); //3.若当前点为出口,说明找到了一条出口 if(IsExit(maze,cur,entry)){ printf("找到了一条路径\n"); if(cur_path->size < short_path->size || short_path->size == 0){ //将当前路径与short_path中的路径对比,若当前路径比short_path短或short_path本身为空栈,则用当前路径替换short_path SeqStackAssgin(cur_path,short_path); printf("找到了一条相对较短的路径\n"); } //若当前路径没有比short_path短,就尝试找其他路径 SeqStackPop(cur_path); return; } //4.若当前点不是出口,则按顺时针方向探测四个相邻的点,递归式调用函数自身,递归式更新cur节点 //(每次递归时,cur都是下一次要走的点,这个点能否落脚,交给递归函数作判断) Point up = cur; up.row -= 1; _GetShortPathWithCycle(maze,up,cur,entry,cur_path,short_path); Point right = cur; right.col += 1; _GetShortPathWithCycle(maze,right,cur,entry,cur_path,short_path); Point down = cur; down.row += 1; _GetShortPathWithCycle(maze,down,cur,entry,cur_path,short_path); Point left = cur; left.col -= 1; _GetShortPathWithCycle(maze,left,cur,entry,cur_path,short_path); //若四个方向都递归的探测过了,则可进行出栈,同时回溯到上一个点 SeqStackPop(cur_path); return; } void GetShortPathWithCycle(Maze* maze,Point entry){ SeqStack cur_path; SeqStack short_path; SeqStackInit(&cur_path); SeqStackInit(&short_path); Point pre = {-1,-1}; _GetShortPathWithCycle(maze,entry,pre,entry,&cur_path,&short_path); SeqStackDebugPrint(&short_path,"最短路径为"); return; }
test.c:
#include "maze.h" #define PRINT_HEADER printf("\n============%s============\n",__FUNCTION__); void Test(){ PRINT_HEADER; Maze maze; SeqStack stack; MazeInitShortPathWithCycle(&maze); Point entry = {0,1}; GetShortPathWithCycle(&maze,entry); MazePrint(&maze); } int main(){ Test(); return 0; }
结果演示: