迷宫问题分析
用二维码创建一个m*n的迷宫地图,1表示通路,0表示障碍,从迷宫中寻找出路。
迷宫问题大致有三种情况实现:
- 使用递归和非递归方法实现简单的迷宫问题
- 如果迷宫有多条出路,求最短出路
- 针对复杂迷宫问题寻找最短出路
下面对其依次进行分析:
1、使用递归和非递归方法实现简单的迷宫问题
这里的简单迷宫问题就是只有一条出路,且出路不带环,实现它的主要思路就是应用回溯的思想,他的具体步骤可以总结如下:
1)、判定当前点是否能落脚
2)、给当前位置做标记(若能落脚)
3)、若当前点为出口,就找到一条出路,探测结束
4)、若当前点不是出口,就按照顺时针探测四个相邻的点,递归的调用函数自身,递归时,更新cur点(每次递归时,点都是下一次要走的点,这个点究竟能不能走,交给递归函数作判断)
而使用递归和非递归两种方法的主要区别主要在与,使用递归时,操作系统会自动给其维护一个栈,而不需要我们手动维护,而使用非递归,就需要自己创建一个栈,用来存放出路各点。
2、如果迷宫有多条出路,求最短出路
当迷宫有多条的出路的时候,并且要找到其最短的出路,那么我们就需要维护两个栈,一个用于保存找到的出路,另外一个用于保存目前最短的那个出路,每次找到一条出路的时候,都跟目前最短的那个出路进行比较,如果比其短,就保存在用于保存最短出路的栈里,当找完所有出路后,然后返回第二个栈,就找到了最短出路
3、针对复杂迷宫问题寻找最短出路
复杂迷宫问题就是有多条出路,且出路带环,需要解决出路带环问题,和简单迷宫问题的区别就在于,判定是否能落脚和标记规则改变,其他的思路都是大致相同的。
判定是否能够落脚规则:
1)、当前点是否在地图上
2)、当前点是不是墙
3)、如果当前点是1,就说明可以落脚
4)、当前点如果走过,比较cur对应的值和pre对应的值的大小关系,在取出pre_value之前,要判定pre点是否是一个非法点(cur和pre分别是当前点和前一个点的坐标)
标记规则不再是将已经做过的路标记为2了,而是标记为pre+1
代码实现如下:
//maze.c
#include <stdio.h>
#include "maze.h"
#include "SqStack.h"
void MazeInit(Maze *maze){
int map[MAX_ROW][MAX_COL] = {
{0,1,0,0,0,0},
{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,0,0,0,0}};
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];
}
}
}
void MazePrint(Maze *maze){
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");
}
}
///////////////////////////////////////////////
/////Round1 使用递归的方式来解决迷宫的问题
//////////////////////////////////////////////
void Mark(Maze *maze, Point cur){
maze -> map[cur.ROW][cur.COL] = 2;
}
int IsExit(Maze *maze, Point cur, Point entry){
(void)maze;
//1、当前点不是入口,如果是入口,说明肯定不是出口
//2、如果当前点在地图边界上,也说明是出口
if(cur.ROW == entry.ROW && cur.COL == entry.COL){
return 0;
}
if(cur.ROW == 0 || cur.COL == MAX_ROW - 1 || cur.COL == 0 || cur.COL == MAX_COL - 1){
return 1;
}
return 0;
}
//判定pt这个点是否能够落脚
//如果能落脚返回1,否则返回0
int CanStay(Maze *maze, Point pt){
//1、若pt在地图外,不能落脚
//2、pt在地图内,若这个位置为1能落脚,若为0,2,不能让落脚
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 _GetPath(Maze *maze, Point cur, Point entry){
printf("cur:%d,%d\n", cur.ROW, cur.COL);
//1、判定当前点是否能落脚
//2、给当前位置做标记(若能落脚)
//3、若当前点为出口,就找到一条出路,探测结束
//4、若当前点不是出口,就按照顺时针探测四个相邻的点,递归的调用函数自身,递归时,更新cur点(每次递归时,点都是下一次要走的点,这个点究竟能不能走,交给递归函数作判断)
if(!CanStay(maze, cur)){
return;
}
Mark(maze, cur);
if(IsExit(maze, cur, entry)){
printf("找到一条出路!\n");
return;
}
Point up = cur;
up.ROW -= 1;
_GetPath(maze, up, entry);
Point right = cur;
right.COL += 1;
_GetPath(maze, right, entry);
Point down = cur;
down.ROW += 1;
_GetPath(maze, down, entry);
Point left = cur;
left.COL -= 1;
_GetPath(maze, left, entry);
}
void GetPath(Maze *maze, Point entry){
if(maze == NULL){
return;//非法输入
}
//辅助递归
_GetPath(maze,entry,entry);
}
////////////////////////////////////////////////////////////////
////Round2 使用非递归的方式来解决迷宫问题
///////////////////////////////////////////////////////////////
void GetPathByLoop(Maze *maze, Point entry){
//1、创建一个栈,并且初始化,这个栈保存着当前路径
SqStack stack;
InitSqStack(&stack);
//2、判断入口点能不能落脚,如果不能,说明参数非法
if(!CanStay(maze, entry)){
return;//入口点非法
}
//3、标记入口点,并且将入口点入栈
Mark(maze, entry);
SqStackPush(&stack, entry);
//4、进入循环,获取当前的栈顶元素(栈顶元素一定能落脚)
while(1){
Point cur;
int ret = SqStackTop(&stack, &cur);
if(ret == 0){
return;//栈为空说明回溯结束
}
//5、判定这个当前点是否能落脚
//6、能落脚就进行标记
//7、判定这个点是不是出口,如果是出口直接函数返回
if(IsExit(maze, cur, entry)){
printf("找到一条出路\n");
return;
}
//8、按照顺时针方向取相邻点,判定相邻点是否能落脚,如果能落脚,就标记并且入栈,立刻进入下一轮循环
Point up = cur;
up.ROW -= 1;
if(CanStay(maze, up)){
Mark(maze, up);
SqStackPush(&stack, up);
continue;
}
Point right = cur;
right.COL += 1;
if(CanStay(maze, right)){
Mark(maze, right);
SqStackPush(&stack, right);
continue;
}
Point down = cur;
down.ROW += 1;
if(CanStay(maze, down)){
Mark(maze, down);
SqStackPush(&stack, down);
continue;
}
Point left = cur;
left.COL -= 1;
if(CanStay(maze, left)){
Mark(maze, left);
SqStackPush(&stack, left);
continue;
}
//9、如果四个相邻的点都不能落脚,就出栈当前点,相当于进行回溯
SqStackPop(&stack);
}
return;
}
//////////////////////////////////////////////////////////////////
//Round3 如果迷宫有多条路径,找到其中最短的路径
/////////////////////////////////////////////////////////////////
void MazeInitShortPath(Maze *maze){
int map[MAX_ROW][MAX_COL] = {
{0,1,0,0,0,0},
{0,1,1,1,0,0},
{0,1,0,1,1,1},
{0,1,0,0,0,0},
{1,1,0,0,0,0},
{0,0,0,0,0,0}
};
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 _GetShortPath(Maze *maze, Point cur, Point entry, SqStack *cur_path, SqStack *short_path){
//1、判定当前点是否能落脚
if(!CanStay(maze, cur)){
return ;
}
//2、如果能落脚,就对当前点进行标记,同时把当前点插入到cur_path
Mark(maze, cur);
SqStackPush(cur_path, cur);
//3、判定当前点是否是出口
if(IsExit(maze, cur, entry)){
printf("找到一条路径!\n");
if(cur_path -> size < short_path -> size || short_path -> size == 0){
printf("找到了一条比较短的路径!\n");
SqStackAssgin(cur_path, short_path);
}else{
//a)如果当前点是出口,说明当前找到一条路径,就拿当前路径和short_path中的路径进行对比,如果当前路径比short_path短,或者short_path本身是一个
//空栈,就用当前路径代替short_path,代替完毕之后,也是需要回溯的,尝试找其他路劲
//b)如果当前路径没有比short_path短就要尝试去找其他路径(进行回溯)在回溯之前,也要把cur_path栈顶元素也出栈
SqStackPop(cur_path);
return;
}
}
//4、如果当前点不是出口,尝试探测四个方向
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);
//5、如果四个方向都递归的探测过了,就可以进行出栈(指当前函数栈帧结束,同时cur_path也要进行出栈),回溯到上一个点
SqStackPop(cur_path);
return;
}
//递归版本
void GetShortPath(Maze *maze, Point entry){
//保存当前找到的路径
SqStack cur_path;
//保存最短路径
SqStack short_path;
InitSqStack(&cur_path);
InitSqStack(&short_path);
_GetShortPath(maze, entry, entry, &cur_path, &short_path);
//打印栈中的内容
SqStackDebugPrint(&short_path, "最短路径是");
}
/////////////////////////////////////////////////////////////////
//Round4 针对复杂的迷宫地图找出其中的最短路径,这里的复杂不关指迷宫有
//多个出口,同时路径上肯能带环
/////////////////////////////////////////////////////////////////
void MazeInitShortPathWithCycle(Maze *maze){
int map[MAX_ROW][MAX_COL] = {
{0,1,0,0,0,0},
{0,1,1,1,0,0},
{0,1,0,1,1,1},
{0,1,1,1,0,0},
{1,1,0,0,0,0},
{0,0,0,0,0,0}
};
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;
}
//判定是否能落脚
int CanStayWithCycle(Maze *maze, Point cur, Point pre){
//1、当前点是否在地图上
if(cur.ROW < 0 || cur.ROW >= MAX_ROW || cur.COL < 0 || cur.COL >= MAX_COL){
return 0;
}
//2、当前点是不是墙
int cur_value = maze -> map[cur.ROW][cur.COL];
if(cur_value == 0){
return 0;
}
//3、如果当前点是1,这是可以直接落脚
if(cur_value == 1){
return 1;
}
//4、当前点如果已经走过了,比较cur对应的值和pre对应的值的大小关系,在取出pre_value之前,要判定pre点是否是一个非法点
//cur_value > pre_value + 1,就应该落脚
//if(pre.ROW == -1 && pre.COL == -1){
//pre如果是非法点,就只有一种情况
//但是这种情况又不需要考虑,因为如果pre为非法点,说明cur就是入口点,判定入口点是否能落脚,和pre没关系,直接判定cur_value是0,还是1就行,而这样的逻辑,已经在刚刚判定过了
//}
int pre_value = maze -> map[pre.ROW][pre.COL];
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){
//针对入口点进行标记,此时的pre点是非法点
//不能根据pre_value + 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;
}
void _GetShortPathWithCycle(Maze *maze, Point cur, Point pre, Point entry, SqStack *cur_path, SqStack *short_path){
//1、判定当前点是否能落脚(判定规则变了)
if(!CanStayWithCycle(maze, cur, pre)){
return;
}
//2、标记当前前点(标记规则也变了),并且当前点插入到cur_path中
MarkWithCycle(maze, cur, pre);
SqStackPush(cur_path, cur);
//3、判定当前点是否为出口
if(IsExit(maze, cur, entry)){
printf("找到一条路径!\n");
if(cur_path -> size < short_path -> size || short_path -> size == 0){
printf("找到了一条比较短的路径!\n");
SqStackAssgin(cur_path, short_path);
}
SqStackPop(cur_path);
return;
}
//如果是出口,要拿cur_path和short_path进行对比,把比较短的路径保存在short_path中
//4、如果不是出口,以当前点为基准,按顺时针探测四个方向
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);
//5、如果四个方向都探测过,就出栈回溯
SqStackPop(cur_path);
return;
}
//实现一个递归版本
void GetShortPathWithCycle(Maze *maze, Point entry){
SqStack cur_path;
SqStack short_path;
InitSqStack(&cur_path);
InitSqStack(&short_path);
Point pre = {-1, -1};
_GetShortPathWithCycle(maze, entry, pre, entry, &cur_path, &short_path);
SqStackDebugPrint(&short_path, "最短路径为");
}
//////////////////////////////////////////////////////////////////
//////////////////////////////TEST///////////////////////////////
////////////////////////////////////////////////////////////////
#if 1
#include <stdio.h>
#define TESTHEADER printf("######################%s#####################\n",__FUNCTION__)
void TestRound1(){
TESTHEADER;
Maze maze;
MazeInit(&maze);
// MazePrint(&maze);
Point entry = {0, 1};
GetPath(&maze, entry);
MazePrint(&maze);
//MazePrint(&maze);
}
void TestRound2(){
TESTHEADER;
Maze maze;
MazeInit(&maze);
Point entry = {0, 1};
GetPathByLoop(&maze, entry);
MazePrint(&maze);
}
void TestRound3(){
TESTHEADER;
Maze maze;
MazeInitShortPath(&maze);
Point entry = {0, 1};
GetShortPath(&maze, entry);
MazePrint(&maze);
}
void TestRound4(){
TESTHEADER;
Maze maze;
MazeInitShortPathWithCycle(&maze);
Point entry = {0, 1};
GetShortPathWithCycle(&maze, entry);
MazePrint(&maze);
}
int main(){
TestRound1();
TestRound2();
TestRound3();
TestRound4();
}
#endif
//maze.h
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_ROW 6
#define MAX_COL 6
typedef struct Maze{
int map[MAX_ROW][MAX_COL];
}Maze;
//SqStack.h
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#define FOR_MAZE
#ifdef FOR_MAZE
//#define FOR_MAZE
typedef struct Point{
int ROW;
int COL;
}Point;
typedef Point SqStackType;
#else
typedef char SqStackType;
#endif
typedef struct SqStack{
SqStackType* data;
size_t size;
size_t capacity; //MAXSIZE的替代品,data这段内存中能容纳的元素个数
}SqStack;
void InitSqStack(SqStack *sq);//初始化栈
void SqStackPush(SqStack *sq, SqStackType value);//进栈
void SqStackPop(SqStack *sq);
//返回两个信息:执行成功失败;栈顶元素的值是多少
int SqStackTop(SqStack *sq, SqStackType* value);
void SqStackDestroy(SqStack *sq);
void SqStackAssgin(SqStack *from, SqStack *to);
void SqStackDebugPrint(SqStack *sq, const char *msg);
//SqStack.c
#include <stdio.h>
#include "SqStack.h"
void InitSqStack(SqStack *sq){
if(sq == NULL){
return;//非法输入
}
sq -> size = 0;
sq -> capacity = 1000;
sq -> data = (SqStackType*)malloc(sq -> capacity *sizeof(SqStackType));
}
//栈的扩容策略
void SqStackReSize(SqStack *sq){
if(sq == NULL){
return;
}
if(sq -> size < sq -> capacity){
return;
}
sq -> capacity = sq -> capacity * 2 + 1;//capacity*2(是C++里面的一个方法) + 1(为了方式capacity为0)
SqStackType* new_ptr = (SqStackType*) malloc(sq -> capacity *sizeof(SqStackType));
size_t i = 0;
for(; i < sq -> size; ++i){
new_ptr[i] = sq -> data[i];
}
free(sq -> data);
sq -> data = new_ptr;
}
//入栈
void SqStackPush(SqStack *sq, SqStackType value){
if(sq == NULL){
return;//非法输入
}
if(sq -> size >= sq -> capacity){//栈满进行扩容
SqStackReSize(sq);
return;
}
sq -> data[sq -> size++] = value;
}
//出栈
void SqStackPop(SqStack *sq){
if(sq == NULL){
return;
}
if(sq -> size == 0){
return;
}
--sq -> size;
}
//取栈顶元素,成功返回1,失败返回0
int SqStackTop(SqStack *sq, SqStackType* value){
if(sq == NULL){
return 0;//非法输入
}
if(sq -> size == 0){
return 0;//栈为
}
*value = sq -> data[sq -> size -1];
return 1;
}
//销毁栈
void SqStackDestroy(SqStack* sq){
if(sq == NULL){
return;//非法输入
}
free(sq -> data );
sq -> size = 0;
sq -> capacity = 0;
}
void SqStackAssgin(SqStack *from, SqStack *to){
//为了保证to里面的内存能够足够的容纳from中的元素
//就采用以下策略:
//1、释放to中的原有内存
SqStackDestroy(to);
//2、根据from元素的个数,确定内存申请大小,再给to重新申请一个足够的内存
to -> size = from -> size;
to -> capacity = from -> capacity;
to -> data = (SqStackType*)malloc(to -> capacity * sizeof(SqStackType));
//3、进行数据拷贝
size_t i = 0;
for(; i < from -> size; ++i){
to -> data[i] = from -> data[i];
}
}
//此函数仅用于迷宫问题中,用于调试,通常意义下,栈是不允许遍历的
//但是如果是进行调试或者测试,这个是例外
//因此在这里的函数虽然进行了调试,但是仅用于调试
//为了能够从入口到出口的顺序来打印栈中的内容
void SqStackDebugPrint(SqStack *sq, const char *msg){
printf("[%s]\n", msg);
size_t i = 0;
for(; i < sq -> size; ++i){
printf("(%d, %d)\n",sq -> data[i].ROW, sq -> data[i].COL);
}
printf("\n");
}