求解多出口迷宫的最短路径,是在基于求解简单迷宫是否存在路径的问题的基础上的一个提高。我们首先需要认识到的是,不论是求解简单迷宫问题,还是复杂迷宫的问题,我们都需要基于栈,使用回溯法来解决问题。
- 首先,我们先来定义一个多出口的迷宫:
图中0表示墙,即无法落脚;1表示可以落脚;之后我们会用2来标记走过的路。
我们默认出口为四个边界上的点。若入口也为边界点,则出口与入口不能重合。
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,0,0,0}, {0,0,1,0,0,0}, {0,0,1,0,0,0} }; void MazeInitShortPath(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 CanStay(Maze* maze,Point pt){//判断当前点是否能落脚 if(maze == 0) return 0; if(pt.row < 0 || pt.row >= MAX_ROW || pt.col < 0 || pt.col >= MAX_COL){//迷宫边界外,不能落脚 return 0; } int value = maze->map[pt.row][pt.col]; if(value == 1){//边界内,且可落脚 return 1; } return 0; } void Mark(Maze* maze,Point cur){//标记走过的路径 maze->map[cur.row][cur.col] = 2; } 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){//将from中的数据全部拷贝至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 _GetShortPath(Maze* maze,Point cur,Point entry,SeqStack* cur_path,SeqStack* short_path){//GetShortPath的辅助函数 printf("cur:(%d,%d)\n",cur.row,cur.col); //1.判断当前点能否落脚 if(!CanStay(maze,cur)){ return; } //2.若能落脚,给当前位置做一个标记 //同时将当前点插入到cur_path Mark(maze,cur); 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; _GetShortPath(maze,up,entry,cur_path,short_path); Point right = cur; right.col += 1; _GetShortPath(maze,right,entry,cur_path,short_path); Point down = cur; down.row += 1; _GetShortPath(maze,down,entry,cur_path,short_path); Point left = cur; left.col -= 1; _GetShortPath(maze,left,entry,cur_path,short_path); //若四个方向都递归的探测过了,则可进行出栈,同时回溯到上一个点 SeqStackPop(cur_path); return; } void GetShortPath(Maze* maze,Point entry){ SeqStack cur_path; SeqStack short_path; SeqStackInit(&cur_path); SeqStackInit(&short_path); _GetShortPath(maze,entry,entry,&cur_path,&short_path); SeqStackDebugPrint(&short_path,"最短路径为"); return; }
通过代码我们可以认识到,由于多出口迷宫存在着多条路径,所以我们需要定义两个栈,一个栈中存放着当前走过的路径的数据,另一个栈中存放着最短路径的数据。每找到一条路径,就将当前路径的长度与最短路径的长度作比较,若存放最短路径的栈为空栈,或者当前路径的长度小于最短路径的长度,就用存放着当前路径的栈中的数据替代存放最短路径的栈中的数据。当所有路径都找到以后,此时存放最短路径的栈中的数据即可表示该迷宫的最短路径。
结果演示: