具体怎么求解迷宫问题,在代码的注释中已详细叙述,这里不再阐述,直接上代码
头文件maze.h
//头文件只被编译一次 #pragma once //宏定义二维数组的行数 #define MAX_ROW 6 #define MAX_COL 6 //定义栈中元素的数据类型 #define SeqStackType Point //顺序表实现栈时定义结构体 typedef struct SeqStack { //用于存放栈的元素,之所以定义为指针类型而不是指定大小的数组类型,是为了方便动态开辟内存 SeqStackType* data; //用来表示栈的有效元素个数 size_t size; //用来表示栈首次申请的空间的最大长度 size_t capacity; }SeqStack; //定义关于迷宫的结构体 typedef struct Maze { //用二维数组表示迷宫地图 int map[MAX_ROW][MAX_COL]; }Maze; //定义迷宫地图的每个位置,由二维数组的行和列决定 typedef struct Point { int row; int col; }Point; /*==========函数声明==========*/ void MazeInit(Maze* maze); void MazePrint(Maze* maze); int CanStay(Maze* maze,Point cur); void Mark(Maze* maze,Point cur); int IsExit(Maze* maze,Point cur,Point entry); void _GetPath(Maze* maze,Point cur,Point entry); void SeqStackInit(SeqStack* stack); void SeqStackDestroy(SeqStack* stack); void SeqStackPrintChar(SeqStack* stack,char* msg); void SeqStackPush(SeqStack* stack,SeqStackType value); SeqStackType* SeqStackResize(SeqStack* stack); int SeqStackPop(SeqStack* stack); int SeqStackTop(SeqStack* stack,SeqStackType* value); void SeqStackPrintPoint(SeqStack* stack); void _GetShortPath(Maze* maze,Point cur,Point entry,SeqStack* cur_stack,SeqStack* short_stack); void SeqStackCopy(SeqStack* cur_stack,SeqStack* short_stack); int CanStayWithCircle(Maze* maze,Point prev,Point cur); void MarkWithCircle(Maze* maze,Point prev,Point cur); void MazeShortPathWithCircle(Maze* maze); void _GetShortPathWithCircle(Maze* maze,Point prev,Point cur,Point entry,SeqStack* cur_stack,SeqStack* short_stack);
关于在迷宫求解中用到的顺序表实现栈的相关操作的文件seqstack.c
#include<stdio.h> #include<unistd.h> #include<stdlib.h> #include"maze.h" /*===============基于顺序表实现栈的相关操作=========*/ //1.初始化 //思路:将有效元素个数size初始化为0,动态开辟内存的最大长度capacity设一个指定值1000,动态开辟data内存空间 void SeqStackInit(SeqStack* stack) { //非法输入 if(stack==NULL) return; //初始化有效元素个数为0 stack->size=0; //初始化动态开辟内存的最大长度为1000 stack->capacity=1000; //动态开辟空间 stack->data=(SeqStackType*)malloc((stack->capacity)*sizeof(SeqStackType)); } //2.销毁 //思路:将有效元素个数size置为0,动态开辟内存的最大长度capacity置为0,释放动态开辟的内存data void SeqStackDestroy(SeqStack* stack) { //非法输入 if(stack==NULL) return; //将栈中有效元素个数置为0 stack->size=0; //将最大长度置为0 stack->capacity=0; //释放动态开辟的内存data free(stack->data); } //3.入栈 //思路:分为两种情况:1.初始化动态开辟的内存空间已满时需要扩容再入栈2.初始化动态开辟的内存未满时,直接入栈 void SeqStackPush(SeqStack* stack,SeqStackType value) { //非法输入 if(stack==NULL) return; //判断栈是否已满 if(stack->size>=stack->capacity) { //扩容 stack->data=SeqStackResize(stack); } //入栈操作 stack->data[stack->size]=value; stack->size++; } //扩容 //思路:1.将stack->capacity设置的更大一点2.开辟新的空间3.将原有数据搬运到新的内存空间 SeqStackType* SeqStackResize(SeqStack* stack) { //非法输入 if(stack==NULL) return NULL; //判断是否已满 if(stack->size<stack->capacity) return stack->data; //已满时,进行扩容 stack->capacity=2*(stack->capacity)+1; //开辟新的空间 SeqStackType* new_stack=(SeqStackType*)malloc((stack->capacity)*sizeof(SeqStackType)); //将原有数据进行搬运 size_t i=0; for(i=0;i<stack->size;i++) { new_stack[i]=stack->data[i]; } //释放旧内存空间 free(stack->data); //返回新开辟的空间位置 return new_stack; } //4.出栈 //思路:将有效元素个数减1即可 int SeqStackPop(SeqStack* stack) { //非法输入 if(stack==NULL) return 0; //返回0表示出栈失败 //空栈时无法出栈 if(stack->size==0) return 0; stack->size--; //出栈成功返回1 return 1; } //5.取栈顶元素 //思路:让该函数返回两个有效信息:1.是否可以出栈,该参数作为返回参数2.出栈的元素,该参数作为输出参数返回 int SeqStackTop(SeqStack* stack,SeqStackType* value) { //非法输入 if(stack==NULL||value==NULL) return 0; //返回0表示出栈失败 //空栈 if(stack->size==0) return 0; //取栈顶元素 *value=stack->data[stack->size-1]; return 1; //返回1表示出栈成功 }
关于迷宫求解的4种问题:
1. 简单迷宫是否存在路径的递归算法
/*=============迷宫求解问题1:不考虑多条路径的最短路问题,只要找到一条出路即可=========*/ /*=============思路:递归实现栈进行操作============*/ //1.初始化 //思路:将用二维数组表示的迷宫地图初始化即可 void MazeInit(Maze* maze) { //非法输入 if(maze==NULL) return; //先创建一个二维数组 int data[MAX_ROW][MAX_COL]={ {0,1,0,0,0,0}, {0,1,1,1,0,0}, {0,1,0,1,0,0}, {0,1,0,0,0,0}, {0,1,1,0,0,0}, {0,0,1,0,0,0}, }; //将二维数组data中的值赋给maze->map这个迷宫地图的二维数组 int i=0; for(;i<MAX_ROW;i++) { int j=0; for(;j<MAX_COL;j++) { maze->map[i][j]=data[i][j]; } } } //2.打印迷宫地图 void MazePrint(Maze* maze) { //非法输入 if(maze==NULL) return; //打印 int i=0; for(;i<MAX_ROW;i++) { int j=0; for(;j<MAX_COL;j++) { printf("%2d",maze->map[i][j]); } printf("\n"); } } //3.递归求解迷宫问题 //思路:利用函数栈递归实现迷宫问题(约定二维数组中的0表示墙,1表示可以走的路) //1.检查要走的点是否为落脚点 //2.若为落脚点,就给该点作为标记,表示已走(约定,已走的点标记为2) //3.判断该点是否为出口,若为出口,则return,表示走出去了 //4.若该点不是出口,则顺时针探测当前点的4个相邻的点,递归调用该函数自身,循环下去 void GetPath(Maze* maze,Point entry) { //非法输入 if(maze==NULL) return; //使用下面函数实现递归求解迷宫问题 _GetPath(maze,entry,entry); } void _GetPath(Maze* maze,Point cur,Point entry) { printf("%2d %2d\n",cur.row,cur.col); //非法输入 if(maze==NULL) return; //1.判断当前点cur是否能称为落脚点,即该点在迷宫地图之内并且该点在二维数组中对应的值为1则表示可以落脚 if(!CanStay(maze,cur)) return; //2.cur可以落脚时,将cur点对应的二维数组的值标记为2 Mark(maze,cur); //3.判断当前点是否为出口,若为出口,则return if(IsExit(maze,cur,entry)) { printf("找到了一条路!\n"); return; } //4.若cur不是出口时,顺时针探测cur相邻的四个点 //(1)cur的上方点 Point up=cur; up.row-=1; _GetPath(maze,up,entry); //(2)cur的右方点 Point right=cur; right.col+=1; _GetPath(maze,right,entry); //(3)cur的下方点 Point down=cur; down.row+=1; _GetPath(maze,down,entry); //(4)cur的左方点 Point left=cur; left.col-=1; _GetPath(maze,left,entry); } //判断是否能成为落脚点的函数CanStay //思路:1.判断该点cur是否在迷宫地图之内 // 2.判断该点cur对应的在二维数组中的值是否为1,若为1则表示可以落脚 int CanStay(Maze* maze,Point cur) { //非法输入 if(maze==NULL) return; //1.判断cur是否在迷宫地图之内 if(cur.row<0||cur.row>MAX_ROW||cur.col<0||cur.col>=MAX_COL) return 0; //返回0表示不能成为落脚点 //2.判断cur对应的在迷宫地图的二维数组中的值是否为1 if(maze->map[cur.row][cur.col]==1) return 1; //返回1表示能成为落脚点 return 0; } //对能成为落脚点的点做标记的函数Mark //思路:将该点对应在二维数组中的值修改为2即可 void Mark(Maze* maze,Point cur) { //非法输入 if(maze==NULL) return; //修改值 maze->map[cur.row][cur.col]=2; } //判断是否能成为出口的函数IsExit //思路:1.若该点时入口点,则返回0 // 2.若该点在迷宫地图即二维数组的边界处,则可以作为出口点,返回1 int IsExit(Maze* maze,Point cur,Point entry) { //非法输入 if(maze==NULL) return; //1.判断cur点是否为入口点 if(cur.row==entry.row&&cur.col==entry.col) return 0; //2.判断cur是否在二维数组的边界处 if(cur.row==0||cur.row==MAX_ROW-1||cur.col==0||cur.col==MAX_COL-1) return 1; return 0; }
2. 简单迷宫是否存在路径的非递归算法
/*==============方法2:非递归求解迷宫问题============*/ //整体思路:需要手动创建栈并进行入栈出栈操作,栈中保存的是走出去的这条路走过的路径 //1.创建一个栈并将其初始化 //2.判定入口能不能落脚,若不能,则表示入口非法 //3.若能,则标记入口点,并且将入口点入栈 //4.进入循环,获得当前栈中的栈顶元素 //5.判定当前栈顶元素是不是出口,若是出口,则直接返回 //6.若不是出口,则约定按照顺时针方向取相邻点,判定该点能不能落脚,若能,就标记并进行入栈,立刻进入下一次循环 //7.若四个相邻点都不能落脚,就出栈当前点,相当于进行回溯 void GetPathByLoop(Maze* maze,Point entry) { //非法输入 if(maze==NULL) return; //1.创建一个栈并初始化 SeqStack stack; SeqStackInit(&stack); //2.判定入口能不能落脚?使用之前定义的CanStay()函数判断 if(!CanStay(maze,entry)) { //不能落脚,则直接return,表示入口非法 } //3.来到这儿,表示入口能落脚,对入口进行标记并且将入口点入栈 Mark(maze,entry); SeqStackPush(&stack,entry); //4.进入循环,并获取当前栈的栈顶元素 while(1) { //获取栈顶元素 SeqStackType cur; int ret=SeqStackTop(&stack,&cur); //通过取栈顶元素判断当前栈是否为空,若为空,则表示无法走出迷宫,若不是,则循环 if(ret==0) return; //5.判断当前点是不是出口,用之前写的IsExit()函数判断 if(IsExit(maze,cur,entry)) { printf("找到一条路径!\n"); SeqStackPrintPoint(&stack); //找到出口,直接return return; } //6.还未找到出口,则顺时针方向去寻找cur点的四个相邻点 //(1)当前点的上方点 Point up=cur; up.row-=1; //判断up点能不能落脚 if(CanStay(maze,up)) { //对up进行入栈 SeqStackPush(&stack,up); //对up进行标记 Mark(maze,up); //继续循环 continue; } //(2)当前点的右方点 Point right=cur; right.col+=1; //判断right点能不能落脚 if(CanStay(maze,right)) { //对right进行入栈 SeqStackPush(&stack,right); //对right进行标记 Mark(maze,right); //继续循环 continue; } //(3)当前点的下方点 Point down=cur; down.row+=1; //判断down点能不能落脚 if(CanStay(maze,down)) { //对down进行入栈 SeqStackPush(&stack,down); //对down进行标记 Mark(maze,down); //继续循环 continue; } //(4)当前点的左方点 Point left=cur; left.col-=1; //判断left点能不能落脚 if(CanStay(maze,left)) { //对left进行入栈 SeqStackPush(&stack,left); //对left进行标记 Mark(maze,left); //继续循环 continue; } //7.一旦四个相邻点都不能落脚,则对当前点进行出栈,相当于回溯 SeqStackPop(&stack); } }
3. 多出口迷宫的最短路问题
/*====================若迷宫有多条出路,找到其中最短的路径===============*/ //思路:1.找到所有路径 2.在所有路径中找到最短路 //0.定义两个栈,分别为存当前路径的栈cur_stack、存最短路径的栈short_satck //1.判断当前点是否能落脚? //2.若能落脚,对当前点进行标记并将其入栈到当前路径的栈cur_stack //3.判断当前点是否为出口,若为出口,则表示找到了一条路径,在此情况下,分为两种情况: //(1)cur_stack.size<short_stack.size或者short_stack栈为空栈时,将cur_stack栈中元素替换掉short_stack栈中元素 //(2)cur_stack.size>short_stack.size时,进行回溯(即寻找下一条路径),回溯即表示把cur_stack栈进行出栈 //4.若不是出口,约定顺时针方向探测当前点的4个邻近点 //5.若4个方向都探测结束,就进行回溯,即对当前栈cur_stack进行出栈,回溯到上一点 //先对迷宫地图初始化,设计多条出口 //思路:将用二维数组表示的迷宫地图初始化即可 void MazeShortPathInit(Maze* maze) { //非法输入 if(maze==NULL) return; //先创建一个二维数组 int data[MAX_ROW][MAX_COL]={ {0,1,0,0,0,0}, {0,1,1,1,0,0}, {1,1,0,1,0,0}, {0,1,0,1,1,0}, {0,1,1,0,1,1}, {0,0,1,0,0,0}, }; //将二维数组data中的值赋给maze->map这个迷宫地图的二维数组 int i=0; for(;i<MAX_ROW;i++) { int j=0; for(;j<MAX_COL;j++) { maze->map[i][j]=data[i][j]; } } } void GetShortPath(Maze* maze,Point entry) { //非法输入 if(maze==NULL) return; //0.定义两个栈并初始化 SeqStack cur_stack; SeqStack short_stack; SeqStackInit(&cur_stack); SeqStackInit(&short_stack); //利用一个函数进行递归操作 SeqStackPrintPoint(&short_stack); } void _GetShortPath(Maze* maze,Point cur,Point entry,SeqStack* cur_stack,SeqStack* short_stack) { //非法输入 if(maze==NULL) return; //1.判断当前点cur是否可以落脚 if(!CanStay(maze,cur)) return; //2.若能落脚,对当前点进行标记并将其入栈到cur_stack栈中 Mark(maze,cur); SeqStackPush(cur_stack,cur); //3.判断是否为出口,若为出口,再关于cur_satck和short_satck进行讨论 if(IsExit(maze,cur,entry)) { printf("找到了一条出路\n"); SeqStackPrintPoint(cur_stack); if(cur_stack->size<short_stack->size||short_stack->size==0) { printf("找到了一条相对较短的路\n"); //将cur_stack中的元素复制到short_stack中 SeqStackCopy(cur_stack,short_stack); SeqStackPrintPoint(short_stack); } //(2)不管是否找到一条较短路,都需要将cur_stack进行出栈,从而寻找下一条出路 SeqStackPop(cur_stack); return; } //4.若不是出口,则按照顺时针的方向探测当前点cur的4个邻近点 Point up=cur; up.row-=1; _GetShortPath(maze,up,entry,cur_stack,short_stack); Point right=cur; right.col+=1; _GetShortPath(maze,right,entry,cur_stack,short_stack); Point down=cur; down.row+=1; _GetShortPath(maze,down,entry,cur_stack,short_stack); Point left=cur; left.col-=1; _GetShortPath(maze,left,entry,cur_stack,short_stack); //5.当4个方向都探测完都没有找到落脚点,则对cur_stack进行出栈,相当于进行回溯 SeqStackPop(cur_stack); } // 2.根据要从该栈当中复制的源栈from的元素个数,对to重新申请内存 // 3.数据元素的拷贝 void SeqStackCopy(SeqStack* from,SeqStack* to) { //1.释放to SeqStackDestroy(to); //2.根据from的元素个数,对to重新申请内存 to->size=from->size; to->capacity=from->capacity; to->data=(SeqStackType*)malloc(to->capacity*sizeof(SeqStackType)); //3.数据拷贝 size_t i=0; for(i=0;i<to->size;i++) { to->data[i]=from->data[i]; } }
4. 带环的多出路迷宫的最短路问题
/*=================关于迷宫求解问题4:带环迷宫================*/ //1.判断当前点是否能落脚? //2.标记当前点,并将点钱点插入到cur_stack栈中 //3.判定当前点是否为出口,若是,cur_stack与short_stack比较,把较短的路径存入到short_stack栈中 //4.若不是出口,则顺时针探测当前点的4个方向 //5.若探测完4个方向都不能作为落脚点,则对cur_stack进行出栈,相当于进行回溯 //先对迷宫地图初始化,设计带环的有多条出口的迷宫 //思路:将用二维数组表示的迷宫地图初始化即可 void MazeShortPathWithCircleInit(Maze* maze) { //非法输入 if(maze==NULL) return; //先创建一个二维数组 int data[MAX_ROW][MAX_COL]={ {0,1,0,0,0,0}, {0,1,1,1,0,0}, {1,1,0,1,0,0}, {0,1,1,1,1,0}, {0,1,1,0,1,1}, {0,0,1,0,0,0}, }; //将二维数组data中的值赋给maze->map这个迷宫地图的二维数组 int i=0; for(;i<MAX_ROW;i++) { int j=0; for(;j<MAX_COL;j++) { maze->map[i][j]=data[i][j]; } } } void GetShortPathWithCircle(Maze* maze,Point prev,Point entry) { //创建两个栈并初始化 SeqStack cur_stack; SeqStack short_stack; SeqStackInit(&cur_stack); SeqStackInit(&short_stack); //利用下面的函数,实现递归调用 _GetShortPathWithCircle(maze,prev,entry,entry,&cur_stack,&short_stack); printf("==============最短路=============\n"); SeqStackPrintPoint(&short_stack); } //传入三个Point类型的参数,分别是当前点cur、当前点的前一个点prev以及入口点entry,传入prev是关于落脚点和标记点的考量 void _GetShortPathWithCircle(Maze* maze,Point prev,Point cur,Point entry,SeqStack* cur_stack,SeqStack* short_stack) { //非法输入 if(maze==NULL) return; //1.判断当前点cur是否能落脚 if(!CanStayWithCircle(maze,prev,cur)) { //不能落脚 return; } //2.能落脚后标记当前点并将当前点cur入栈到cur_stack中 MarkWithCircle(maze,prev,cur); SeqStackPush(cur_stack,cur); //3.判断是否为出口 if(IsExit(maze,cur,entry)) { //(1)若为出口,表示找到了一条出路,则cur_stack与short_stack进行比较 printf("找到了一条出路\n"); SeqStackPrintPoint(cur_stack); //(2)cur_stack栈中元素比short_stack中的元素少或short_stack为空栈时,将cur_stack替换short_stack if(cur_stack->size<short_stack->size||short_stack->size==0) { //找到了一条相对较短的路 printf("找到了一条相对较短的路\n"); //两个栈进行拷贝,利用之前的函数SeqStackCopy SeqStackCopy(cur_stack,short_stack); SeqStackPrintPoint(short_stack); } //(3)不管当前栈cur_stack是否为较短路,都将其进行出栈操作(相当于回溯),继续寻找下一条出路 SeqStackPop(cur_stack); return; } //4.若不是出口,则以当前点cur为基准点,顺时针方向探测当前点cur的4个方向,利用函数递归 Point up=cur; up.row-=1; _GetShortPathWithCircle(maze,cur,up,entry,cur_stack,short_stack); Point right=cur; right.col+=1; _GetShortPathWithCircle(maze,cur,right,entry,cur_stack,short_stack); Point down=cur; down.row+=1; _GetShortPathWithCircle(maze,cur,down,entry,cur_stack,short_stack); Point left=cur; left.col-=1; _GetShortPathWithCircle(maze,cur,left,entry,cur_stack,short_stack); //5.如果4个方向都探测过后,则函数调用结束,并对cur_stack手动出栈,即进行回溯 SeqStackPop(cur_stack); } //判定落脚点的新规则 //思路:1.当前点是否在迷宫地图内 // 2.当前点是不是墙 // 3.当前点对应的值为1,则直接落脚 // 4.当前点已经走过,则比较cur和prev对应的值的大小(下面具体举例来根据它们的大小判断是否能落脚的规则); // (1)cur_value:7 prev_value:5 应该落脚 // (2)cur_value:6 prev_value:5 不应该落脚 // (3)cur_value:5 prev_value:5 不应该落脚 // (4)cur_value:4 prev_value:5 不应该落脚 // 综上分析,应该落脚的可能是:cur_value>prev_value+1 int CanStayWithCircle(Maze* maze,Point prev,Point cur) { if(maze==NULL) return 0; //1.当前点在迷宫地图外 if(cur.row<0||cur.row>=MAX_ROW||cur.col<0||cur.col>=MAX_COL) { return 0; } //2.当前点cur是墙时 int cur_value=maze->map[cur.row][cur.col]; if(cur_value==0) { return 0; } //3.当前点cur对应的值为1时,直接落脚 if(cur_value==1) { return 1; } //4.当前点被走过,cur_value与prev_value进行比较 int prev_value=maze->map[prev.row][prev.col]; if(cur_value>(prev_value+1)) { //可以落脚 return 1; } return 0; } //标记当前点的新规则 void MarkWithCircle(Maze* maze,Point prev,Point cur) { //非入口点时,当前点cur标记为当前点的前一点prev的prev_value加1 int prev_value=maze->map[prev.row][prev.col]; maze->map[cur.row][cur.col]=prev_value+1; //针对入口点,单独处理,将其标记为2 if(prev.row==-1&&prev.col==-1) { maze->map[cur.row][cur.col]=2; } }
测试代码如下:
/*=================测试代码块===================*/ //1.测试初始化及其打印 void Test_MazePrint() { Maze maze; MazeInit(&maze); MazePrint(&maze); } //2.测试递归方法的迷宫问题 void Test_GetPath() { Maze maze; Point entry={0,1}; MazeInit(&maze); GetPath(&maze,entry); MazePrint(&maze); printf("===================\n"); } //3.测试非递归方法的迷宫问题 //先写一个打印数据元素类型为Point的栈的元素,用于测试 void SeqStackPrintPoint(SeqStack* stack) { //非法输入 if(stack==NULL) return; int i=0; for(i=0;i<stack->size;i++) { printf("%d %d\n",stack->data[i].row,stack->data[i].col); } } //4.测试非递归求解迷宫的函数 void Test_GetPathByLoop() { Maze maze; Point entry={0,1}; MazeInit(&maze); GetPathByLoop(&maze,entry); MazePrint(&maze); } //5.测试多条路径求最短路的函数 void Test_GetShortPath() { Maze maze; Point entry={0,1}; MazeShortPathInit(&maze); GetShortPath(&maze,entry); } //6.测试带环的多条路径求最短路的函数 void Test_GetShortPathWithCircle() { Maze maze; Point entry={0,1}; Point prev={-1,-1}; MazeShortPathWithCircleInit(&maze); GetShortPathWithCircle(&maze,prev,entry); } /*==============主函数===========*/ int main() { Test_MazePrint(); Test_GetPath(); Test_GetPathByLoop(); Test_GetShortPath(); Test_GetShortPathWithCircle(); return 0; }