思路:
1. 问题求解需要用到数据结构——栈,所以需要栈的定义、表示和实现
1) 栈的定义
栈所需要的数据元素的定义
2) 栈的表示
栈的存储结构使用什么?顺序or链式?在此采用链式
栈的存储结构的描述,用类型定义来描述。
3) 栈的实现
栈的基本操作的函数,需要自己写。
4) 但这些实现之前还需要一些常量、类型的定义。
2. 问题的求解,即算法,算法思路详见代码。
- 如果当前位置“可通”,则纳入“当前路径”,并继续朝下一个位置探索,即切换下一个位置为当前位置
- 如果当前位置不可通,则应沿“来向”退回到前一通道块,然后朝其它方向继续探索
- 如果该通道块的四周方块均不可通过,则应从当前路径上删除该通道块
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
//#include <malloc.h> //动态存储分配函数头文件,用于栈的存储空间的分配
#include <sys/malloc.h> //mac需要这个头文件
//数据结构——栈需要使用的常量及类型
#define STACK_INIT_SIZE 100
#define STACKINCREMENT 10
typedef int Status;
#define OK 1
#define ERROR 0
// #define OVERFLOW -2
//算法——迷宫求解需要使用的常量和类型
#define RANGE 100 //迷宫的最大长度和宽度
#define ROW 10 //迷宫的行数
#define COL 10 //迷宫的列数
typedef int DirectiveType; //下一个通道的方向
typedef struct{
int row;
int col;
}PosType; //坐标(row,col)类型
typedef struct{
int m, n; //迷宫的行数和列数
int arr[RANGE][RANGE]; //迷宫的数组(只是有这么大,但是并没有都使用)
}MazeType; //迷宫的类型,为什么需要迷宫的类型,因为我们在解决问题之前,需要向算法的函数实现传入迷宫,而迷宫就要在电脑中有它的表示,也就是迷宫的数据结构。
// 栈的定义、表示和实现
// 栈的定义,即数据元素的类型(SElemType,在程序中需自己定义),存储在栈中的应该是【从入口到当前位置前一块】的【已经走过的路径中的每一块】(为什么是当前位置的前一块,因为当前位置正在进行判定,还没有加入到这个路径中)
typedef struct{
int step; //这是第几步,即该位置在路径中的序号
PosType seat; //该位置的坐标
DirectiveType di; //从该位置往下一个要探寻的位置的方向
}SElemType;
//栈的表示,即栈的存储结构的描述,用类型定义(数据类型)来描述,这里使用链栈,所以是链栈的描述
typedef struct{
SElemType *base; //栈底
SElemType *top; //栈顶
int stacksize; //栈分配的存储空间的大小
}SqStack; //顺序栈类型,定义顺序栈(虽然用了指针,但仍然是顺序栈,即数组也可以用指针!)
//栈的实现,即栈的基本操作
//初始化栈,即让栈的各成分变量均有值,即为栈分配存储空间并初始化各成分变量。
Status InitStack(SqStack &S){
S.base = (SElemType *)malloc(STACK_INIT_SIZE * sizeof(SElemType)); //是顺序栈,因为是用数组来描述的,并且采用的是动态数据,即动态分配存储空间。
if(!S.base){
exit(OVERFLOW);
}
S.top = S.base;
S.stacksize = STACK_INIT_SIZE;
return OK;
}
//当栈非空时,取栈顶元素
Status GetTop(SqStack S, SElemType &e){
if(S.top == S.base){ //判断栈是否为空
return ERROR;
}
e = *(S.top - 1);
return OK;
}
//入栈
Status PushStack(SqStack &S, SElemType e){
//只要是要进行插入操作,都需要:1.检查参数2.检查空间3.插入元素4.修改长度等
//检查空间
if(S.top - S.base >= S.stacksize){
S.base = (SElemType *)realloc(S.base, (S.stacksize + STACKINCREMENT) * sizeof(SElemType));
if(!S.base){
exit(OVERFLOW);
}
S.top = S.base + S.stacksize;
S.stacksize += STACKINCREMENT;
}
*(S.top++) = e;
return OK;
}
//出栈
Status PopStack(SqStack &S, SElemType &e){
//删除元素之前都需要判断是否为空
if(S.top == S.base){
return ERROR;
}
e = *(--S.top);
return OK;
}
//判断栈是否为空
Status StackEmpty(SqStack S){
if(S.top == S.base){
return OK; //1
}
return ERROR; //0
}
//销毁栈,就是要释放顺序栈的数组所占用的空间,也就是free()释放动态分配的存储空间【free只能释放动态分配malloc、calloc、realloc的空间,不能释放非动态分配的空间】,并且让栈的top、base为NULL、stacksize为0。
Status DestroyStack(SqStack &S){
free(S.base); //释放S.base所指向的存储空间,还给系统。S.base所指向的空间是malloc或者realloc动态分配的,所以整个数组都会被释放。
S.top = S.base = NULL;
S.stacksize = 0;
return OK;
}
//算法的实现
//初始化迷宫
Status InitMaze(MazeType &maze, int a[ROW][COL], int row, int col){ //row和col是迷宫的真实的行数和列数,应该是10
int i, j;
//设置迷宫maze的初值,包括加上边缘一圈的值
for(i = 1; i < row; i++){
for(j = 1; j < col; j++){
maze.arr[i][j] = a[i][j];
}
}
//加上围墙
for(i = 0; i < row; i++){
maze.arr[0][i] = maze.arr[row - 1][i] = 1; //第0行和第9行为1
}
for(j = 0; j < col; j++){
maze.arr[j][0] = maze.arr[j][col - 1] = 1; //第0列和第9列为1
}
return OK;
}
//判断当前位置是否可以通过,传入参数是迷宫类型的变量,以及当前位置的坐标。
Status Pass(MazeType maze,PosType curpos){
//当节点为0时,表示有路
if(maze.arr[curpos.row][curpos .col] == 0){ //只有等于0才表示可通,为1表示障碍物
return OK;
}else {
return ERROR;
}
}
//留下标记,表示这个位置已经探索过,已经走过,不管这个位置之后还在不在路径中(栈中),都不要再走这一块了。
//留下标记,也就是把maze类型变量的成员的数组中这个位置设成2,表示已经探索过,和Pass函数一样
//注意这里的2,只有在栈中的元素才有资格被标记成2,之后在打印迷宫的时候,2就是要打印的通路。因为他现在入栈,被打成2,若他之后出栈了,就会被打成其他的,若他不出栈,就不会变,所以2就是路径中的块的标志。
Status FootPrint(MazeType &maze,PosType curpos){
maze.arr[curpos.row][curpos .col] = 2; //走过且走得通
return OK;
}
//创建元素e
SElemType CreateSElem(int step, PosType pos, int di){
SElemType e;
e.step = step;
e.seat = pos;
e.di = di;
return e;
}
//判断是不是出口
Status PosEqual(PosType pos1, PosType pos2){
if(pos1.row == pos2.row && pos1.col == pos2.col){
return OK;
}else
return ERROR;
}
//获取下一个当前位置
PosType NextPos(PosType curpos ,DirectiveType di){
PosType pos = curpos ;
switch(di){
case 1:pos.col++;break; //右
case 2:pos.row++;break; //下
case 3:pos.col--;break; //左
case 4:pos.row--;break; //上
}
return pos;
}
//标记走不通,迷宫数组中,3表示走不通
//留下不能通过的标记
Status MarkPrint(MazeType &maze,PosType curpos){
maze.arr[curpos.row][curpos.col] = 3;
return OK;
}
//打印迷宫
//打印路径
void PrintMaze(MazeType maze,int row,int col){
int i, j;
printf(" ");
for (i = 0; i < col; i++) //打印列数名
printf("%d ", i);
printf("\n");
for (i = 0; i < row; i++){
printf("%d", i); //打印行数名
for (j = 0; j < col; j++){
//printf("%d ",maze.arr[i][j]);
switch (maze.arr[i][j]){
case 0:printf(" ");break; //没走过,但是通路
case 1:printf("■ ");break; //墙,障碍物
case 2:printf("# ");break; //走过且走得通
case 3:printf("* ");break; //走过但走不通,死胡同
default:break;
}
}
printf("\n");
}
}
//求解迷宫maze中,从入口start到出口end的一条路径的函数,这是本题算法最关键的部分
Status MazePath(MazeType &maze, PosType start, PosType end){
//在该算法中,因为要用到栈,所以先定义一个栈并初始化。
SqStack S;
InitStack(S);
SElemType e;
//1.先将起点设置为当前位置
PosType curpos = start;
//2.当前步数设置为1,即搜索第一步
int curstep = 1;
//开始循环搜索每一步,直到栈为空,即没有找到一条路径,否则都要继续循环找路径。只要没有路径,就找不到路径,就一定会返回到起点start,并且起点start也会出栈,使栈为空,所以栈为空可以作为找不到路径的标志。只要栈不为空,就说明还有可能找得到路径,就要继续循环找。
do{
//1.如果当前位置可以通过,即是通道块(不是障碍物),并且是未曾走过的路径,那么当前位置入栈,成为路径中的一步。
if(Pass(maze, curpos)){
FootPrint(maze, curpos); //留下标记,表示这一步已经到达过,之后要探索路径就不要探索这一块了,因为如果第一次到达这一块都没有走通,那之后到达这一块也不能走通。【这里需要理解一下!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!】
//当前位置入栈,但要入栈,要先把当前位置变成一个数据元素的结构才可以
e = CreateSElem(curstep, curpos, 1); //创建元素
PushStack(S, e);
//判断是否到达终点了,如果到达了,说明找到了路径,直接返回
if(PosEqual(curpos, end)){
return OK;
}
curpos = NextPos(curpos, 1); //获取下一个当前位置,下一个当前位置就是当前栈顶元素(即路径的最后一个通道块)的di方向的位置。但是因为这里正好在进栈之后,所以di方向一定就是右边,所以直接传入参数1
curstep++; //因为一个已经进栈,所以步数+1
}
else{ //当前位置不可通
//判断栈是否为空,为空则表示找不到路径(在此为空没有操作,因为到最后do循环退出后,就是统一的栈为空找不到路径的操作),不为空才有操作
if(!StackEmpty(S)){
PopStack(S, e);
//判断出栈的这个元素有无其他可以探索的方向
while(e.di == 4 && !StackEmpty(S)){ //四个方向都已探寻过,且仍然有路径可以走(栈非空)
MarkPrint(maze, e.seat); //标记这个方向走不通
PopStack(S, e); //继续出栈
}
//出栈直到有未探索的方向,或者栈为空
//在此栈为空没有操作,因为到最后do循环退出后,就是统一的栈为空找不到路径的操作
if(e.di < 4){
e.di++; //因为在一个e入栈之前,di都是被初始化为1,之后就将curpos设置为e的右边的那一个位置,这时候di仍为1。所以在这里,之后每次要改变探索方向时,都要先+1,再探索。判断的时候,1,表示刚探索过右;2,表示刚探索过下;3,表示刚探索过左;4,表示刚探索过上。所以di为4就表示都探索过了,并且要探索下一个时,要先将di+1。
PushStack(S, e); //因为有可以探索的方向,所以出栈的又重新入栈
curpos = NextPos(e.seat, e.di); //注意这里是e.seat,而不是curpos
}
}
}
}while(!StackEmpty(S));
return ERROR; //栈为空,才会走到这,否则在do循环中就会返回。也就是没找到路径,才会走到这一步,故返回ERROR,0,表示没找到。
}
Status main(){
int i, j;
PosType start ,end; //开始,终点坐标
MazeType maze;
int a[ROW][COL] = {
{1,1,1,1,1,1,1,1,1,1},
{1,0,0,0,1,0,0,1,0,1},
{1,0,0,1,0,1,0,1,1,1},
{1,1,0,0,0,1,0,0,0,1},
{1,0,1,1,0,0,1,1,0,1},
{1,1,0,0,1,0,0,0,1,1},
{1,0,1,0,0,0,1,1,0,1},
{1,0,1,0,1,1,1,1,0,1},
{1,1,0,0,0,0,0,0,0,1},
{1,1,1,1,1,1,1,1,1,1}
}; //迷宫数组,是一个二维数组,1表示障碍物,0表示非障碍物,10*10
printf("\n原始迷宫如下:\n");
printf("(其中‘1’表示墙,‘0’表示通道)\n");
for(i = 0; i < 10; i++){
for(j = 0; j < 10; j++){
printf("%d ",a[i][j]);
}
printf("\n");
} //打印迷宫数组
InitMaze(maze,a,ROW,COL); //初始化迷宫,传入参数为迷宫类型的变量、迷宫数组、行数、列数
start.row = 1; //给定迷宫起点坐标(1,1)
start.col = 1;
end.row = 8;
end.col = 8; //给定迷宫终点坐标(8,8),因为第0、9行/列都是围墙
//迷宫和起点终点已经给出了,可以开始寻找路径了
if (MazePath(maze, start, end)){ //如果找到一条路径
printf("\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n");
printf("\n求解迷宫路径如下:\n");
printf("(其中'#'表示求解路径,'*'表示死胡同)\n");
PrintMaze(maze, ROW,COL); //打印迷宫路径
}else
printf("\n从入口(1,1)到出口(8,8)没有通路!\n");
return OK;
}