C语言——迷宫问题

一、题目分析

迷宫问题本质上本质上是对数组的一个遍历,向上下左右四个方向进行遍历。当上下左右四个方向都无法行动时,将路径进行回溯,找到另一个可以行动的方向,直到找到出口。
显然,在遍历的过程中会有很多的问题:
1、当向前运动时,对上下左右四个方向进行遍历就会找到走过的路径,所以需要对走过的路径进行一个标记。
2、也会出现数组越界的问题,需要对下标进行控制。
3、最后是路径存储的问题。如何将走过的路径进行保存,并且如果路不通的时候,如何将路径删除,保存新的路径。显然栈是一个很好的方式来保存路径。按照后进先出的原则,可以将路径倒退删除,进行回溯。
4、既然选择了栈来保存数据,那么打印的时候又会遇到新的问题。我们需要打印从开始到结束的路径,然而栈的特性是后进先出,打印时便会将路径倒过来打印。所以需要另一个栈,来将存储路径的数据倒一遍,再将新栈打印。
显然C语言对这个问题的实现是有些麻烦的,我们需要自己写一个栈出来,再解决剩下的问题。

二、代码分析

1、主函数
	int N = 0, M = 0;
	scanf("%d%d", &N, &M);
	int** maze=(int**)malloc(sizeof(int*) * N);//malloc一个二维数组
	for (int i = 0; i < N; i++)
	{
		maze[i] = (int*)malloc(sizeof(int) * M);
	}
	for (int i = 0; i < N; i++)//数据输入
	{
		for (int j = 0; j < M; j++)
			scanf("%d", &maze[i][j]);
	}

malloc一个二维数组maze,用来作为迷宫。0代表路可以走通,1代表无法走通。当然,malloc的空间要在使用完成后free掉,避免内存泄漏。

Stack path;//利用栈保存路径
typedef struct Pos//位置坐标
{
	int row;
	int col;
}Pos;

建立一个栈path来存储路径,以及一个坐标结构体来表示位置坐标。栈储存的数据类型即为定义的struct Pos。将栈进行初始化,将坐标设为入口坐标{0,0}。在这里将path定义为了全局变量,方便函数的使用。

	StackInit(&path);//初始化栈
	Pos cur = { 0,0 };//入口位置坐标
	if (GetMazePath(maze, N, M, cur))//判断是否能走到出口
	{
		PrintPath();//打印路径
	}
	else
	{
		printf("no path\n");
	}

	StackDestory(&path);//销毁栈
	for (int i = 0; i < N; i++)
		free(maze[i]);
	free(maze);//释放二维数组
	return 0;

GetMazePath函数是用来判断是否能找到路径,将迷宫maze,行N,列M,入口坐标cur传入,返回布尔值。如果返回值为true,将路径进行打印。PrintPath即为路径打印函数。
将用过的栈进行销毁。

2、GetMazePath

当路走不通的时候,要对路径进行回溯,走到一个岔路口,向另一个方向运动。要采用递归的方式进行回溯。
在这里插入图片描述

在上图中,当走到(1,0)位置的时候,下方向和右方向都可以前行。
如果先向下运动,当我们走到(3,1)位置时便无法运动了。向下向左走会越界,向右数组值为1,无法运动。当然,上方向我们要进行一个标记,避免向回走的情况。如下图所示:
在这里插入图片描述

暂且标记为2,因为只有0才能通,标记为其它数字也无妨。既然在(3,0)无法行动,便要回到其它位置,(2,0)显然无法走通,(1,0)的右边方向是可以的,我们便要回溯到(1,0)这个位置。
并且在运动的时候,代码无法确定此时正在走的路径能能不能到达终点(3,3)处,所以要实时得对路径在栈中进行Push(保存),或者Pop(删除)。

(1)、是否到达终点
StackPush(&path, cur);//保存路径
if (cur.row == N - 1 && cur.col == M - 1)//判断是否到达终端点
	return true;
maze[cur.row][cur.col] = 2;//标记走过的路

在每一次运动时,最先要保存一下路径,之后再判断要不要删除路径。
并且要判断是否到达迷宫的终点(数组右下角),如果以及到达终点就没有继续递归的必要了,直接返回true即可。
以及要对走过的路进行一个标记

(2)、判断下一个方向是否通路
Pos next = cur;//下一个位置坐标
next.row -= 1;//向上走
if (IsPath(maze, N, M, next))
{
	if (GetMazePath(maze, N, M, next))
		return true;
}

next = cur;
next.row += 1;//向下走
if (IsPath(maze, N, M, next))
{
	if (GetMazePath(maze, N, M, next))
		return true;
}

next = cur;
next.col -= 1;//向左走
if (IsPath(maze, N, M, next))
{
	if (GetMazePath(maze, N, M, next))
		return true;
}

next = cur;
next.col += 1;//向右走
if (IsPath(maze, N, M, next))
{
	if (GetMazePath(maze, N, M, next))
		return true;
}
StackPop(&path);//删除数据
return false;//返回false表示无路可走

在遍历中有四种情况:向上,向下,向左,向右。
创建一个新的坐标结构体来代表下一个坐标位置。将cur先赋给next
如果向上走,next.row-=1。传入IsPath函数中,判断上方向是否是通路。
如果是通路,就可以继续走下去,直到到达终点或者无路可走。
下方向:next.row+=1;IsPath判断是否能继续走下去。
左方向:next.col-=1;IsPath判断是否能继续走下去。
右方向:next.col+=1;IsPath判断是否能继续走下去。
要注意的一点是:在每一次进入IsPath进行判断时,要将next还原成cur,再改变next。因为在判断其它方向时,next已经被 改变了,next要在cur的基础上进行移动。

通过以上方法,对路径进行递归。当然这是一个抽象的过程,递归就如同一棵大树一样,会产生很多的枝叶,使得这颗大树枝繁叶茂,让人无法理清逻辑。所以我们可以用笔来画一下这棵树。GetMazePath函数用GMP来表示。
以下面数组为例:在这里插入图片描述
在这里插入图片描述
依次探查四个方向,第一次进入(1,0),第二次进入(2,0),第三次进入(3,0)
,此时四个方向都不通,GetMazePath函数走到最底部,return false。
在这里插入图片描述
向上回溯,并且删掉栈内的路径,找到另一个通路。如上图所示,回溯到(1,0),发现其右侧是通路,向右运动。
在这里插入图片描述
最后不断递归,找到终点后结束,整体返回true。
可以看到递归其实在我们的逻辑中实现出来就像一棵树,但是这棵树要从左向右依次遍历。false就返回不向下走,true就继续向下遍历。
单凭想象我们很难去像清楚,将其图像化、具体化更容易明白。

3、IsPath

在GetMazePath中利用这个函数来判断下一步是否是通路,决定是否向下递归。但是写在GetMazePath函数中太过冗长,条理不够清晰。所以单独胡作为一个函数。

int row = next.row;//行坐标
int col = next.col;//列坐标
if(row>=0&&row<N&&col>=0&&col<M&&maze[row][col]==0)//判断是否通路
	return true;
else
	return false;

首先就是要确保下一步的坐标不能越界,最后要保证maze[row][col]==0,因为走过的路已经被标记为2,所以不会对走过的路判断为true。
通路返回true,不通返回false。

4、PrintPath

在题目分析中可以得知,path栈中的路径虽然被保存了下来,但栈的特性决定了打印的时候路径会倒过来。所以建立一个新的栈rpath来将数据倒一遍,在将rpath中的数据打印即可。

Stack rpath;//建立一个新栈
StackInit(&rpath);//初始化新栈
while (!StackEmpty(&path))
{
	StackPush(&rpath, StackTop(&path));//将数据倒入到rpath中
	StackPop(&path);//每倒一个数据,在path中删除一个数据
}

while (!StackEmpty(&rpath))
{
	Pos pos = StackTop(&rpath);
	printf("(%d,%d)\n",pos.row,pos.col);//打印数据
	StackPop(&rpath);
}
StackDestory(&rpath);//销毁栈

利用StackEmpty接口来进行循环判断,每向rpath中放入一个数据(StackPush),就要在path中删除一个数据(StackPop)。
接着打印数据即可。

三、完整代码

1、栈
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
#include <string.h>
#define capacity 4
typedef struct Pos
{
	int row;
	int col;
}Pos;//位置结构体

typedef Pos StackDataType;
typedef struct Stack
{
	StackDataType* _a;
	int size;//大小
	int capa;//容量
}Stack;//栈

void StackInit(Stack* s);//初始化
void StackDestory(Stack* s);//栈销毁
void StackPush(Stack* s, StackDataType x);//入栈
void StackPop(Stack* s);//出栈
int StackSize(Stack* s);//数据个数
int StackEmpty(Stack* s);//栈是否为空
StackDataType StackTop(Stack* s);//栈顶数据

void StackInit(Stack* s)//栈初始化
{
	assert(s);
	s->_a = (StackDataType*)malloc(sizeof(StackDataType) * capacity);
	s->size = 0;
	s->capa = capacity;
}
void StackDestory(Stack* s)//栈销毁
{
	assert(s);
	free(s->_a);
	s->_a = NULL;
	s->size = 0;
	s->capa = 0;
}
void StackPush(Stack* s, StackDataType x)//入栈
{
	assert(s);
	if (s->capa == s->size)
	{
		s->capa *= 2;
		StackDataType* tmp = (StackDataType*)realloc(s->_a, sizeof(StackDataType) * s->capa);
		if (tmp == NULL)
		{
			perror("StackPush:");
			exit(-1);
		}
		else
		{
			s->_a = tmp;
		}
	}
	s->_a[s->size] = x;
	s->size++;
}
void StackPop(Stack* s)//出栈
{
	assert(s);
	assert(s->size);
	s->size--;
}
int StackSize(Stack* s)//数据个数
{
	assert(s);
	return s->size;
}
int StackEmpty(Stack* s)//栈是否为空
{
	assert(s);
	return !(s->size);
}
StackDataType StackTop(Stack* s)//栈顶数据
{
	assert(s);
	return s->_a[s->size - 1];
}
2、迷宫代码
#include "stack.h"
Stack path;//利用栈保存路径



bool IsPath(int** maze, int N, int M, Pos next)//判断通路函数
{
	assert(maze);
	int row = next.row;
	int col = next.col;
	if(row>=0&&row<N&&col>=0&&col<M&&maze[row][col]==0)
		return true;
	else
		return false;
}

bool GetMazePath(int** maze, int N, int M,Pos cur)//获取路径函数
{
	StackPush(&path, cur);
	if (cur.row == N - 1 && cur.col == M - 1)
		return true;

	maze[cur.row][cur.col] = 2;

	Pos next = cur;
	next.row -= 1;//向上走
	if (IsPath(maze, N, M, next))
	{
		if (GetMazePath(maze, N, M, next))
			return true;
	}

	next = cur;
	next.row += 1;//向下走
	if (IsPath(maze, N, M, next))
	{
		if (GetMazePath(maze, N, M, next))
			return true;
	}

	next = cur;
	next.col -= 1;//向左走
	if (IsPath(maze, N, M, next))
	{
		if (GetMazePath(maze, N, M, next))
			return true;
	}

	next = cur;
	next.col += 1;//向右走
	if (IsPath(maze, N, M, next))
	{
		if (GetMazePath(maze, N, M, next))
			return true;
	}


	StackPop(&path);
	return false;
}


void PrintPath()//打印函数
{
	Stack rpath;
	StackInit(&rpath);
	while (!StackEmpty(&path))
	{
		StackPush(&rpath, StackTop(&path));
		StackPop(&path);
	}

	while (!StackEmpty(&rpath))
	{
		Pos pos = StackTop(&rpath);
		printf("(%d,%d)\n",pos.row,pos.col);
		StackPop(&rpath);
	}
	StackDestory(&rpath);
}

int main()
{
	int N = 0, M = 0;
	scanf("%d%d", &N, &M);
	int** maze=(int**)malloc(sizeof(int*) * N);
	for (int i = 0; i < N; i++)
	{
		maze[i] = (int*)malloc(sizeof(int) * M);
	}
	for (int i = 0; i < N; i++)
	{
		for (int j = 0; j < M; j++)
			scanf("%d", &maze[i][j]);
	}
	StackInit(&path);
	Pos cur = { 0,0 };
	if (GetMazePath(maze, N, M, cur))
	{
		PrintPath();
	}
	else
	{
		printf("no path\n");
	}

	StackDestory(&path);
	for (int i = 0; i < N; i++)
		free(maze[i]);
	free(maze);
	return 0;
}
  • 68
    点赞
  • 49
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值