具体怎么求解迷宫问题,在代码的注释中已详细叙述,这里不再阐述,直接上代码
头文件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;
}