迷宫问题(深度优先遍历,回溯算法)

感谢各位大佬的光临,希望和大家一起进步,望得到你的三连,互三支持,一起进步

个人主页LaNzikinh-CSDN博客

收入专栏:初阶数据结构_LaNzikinh篮子的博客-CSDN博客

文章目录

  • 前言
  • 一.主要逻辑
  • 二.利用利用深度优先遍历,回溯算法,四方向递归走迷宫
  • 三.栈的操作
  • 四.总代码
  • 总结

前言

前面我们学习了很多数据结果相关的知识,这次我们就来利用之前所学的和c语言来学习一下迷宫问题


迷宫问题_牛客题霸_牛客网 (nowcoder.com)

描述

定义一个二维数组 N*M ,如 5 × 5 数组下所示:


int maze[5][5] = {
0, 1, 0, 0, 0,
0, 1, 1, 1, 0,
0, 0, 0, 0, 0,
0, 1, 1, 1, 0,
0, 0, 0, 1, 0,
};


它表示一个迷宫,其中的1表示墙壁,0表示可以走的路,只能横着走或竖着走,不能斜着走,要求编程序找出从左上角到右下角的路线。入口点为[0,0],既第一格是可以走的路。

数据范围: 2≤𝑛,𝑚≤10 2≤n,m≤10  , 输入的内容只包含 0≤𝑣𝑎𝑙≤1 0≤val≤1 

输入描述:

输入两个整数,分别表示二维数组的行数,列数。再输入相应的数组,其中的1表示墙壁,0表示可以走的路。数据保证有唯一解,不考虑有多解的情况,即迷宫只有一条通道。

输出描述:

左上角到右下角的最短路径,格式如样例所示。

示例1

输入:

5 5
0 1 0 0 0
0 1 1 1 0
0 0 0 0 0
0 1 1 1 0
0 0 0 1 0

复制输出:

(0,0)
(1,0)
(2,0)
(2,1)
(2,2)
(2,3)
(2,4)
(3,4)
(4,4)

说明:

注意:不能斜着走!! 

思路:利用深度优先遍历,回溯算法,四方向递归

首先我们想要来定义一个结构体,在迷宫上有很多点,我们既然要走,就要把先把点拿住

typedef struct Postion
{
	int row;
	int col;
}PT;

一.主要逻辑

我们先要思考这个题目的主要逻辑,就是说我要怎么去实现这个题目?首先第一个就是动态开辟一个二维空间,因为我要先输入一个二维的矩阵。首先要注意int a[n][m]; // vs2022 不支持以及c语言是不支持变长数组的,所以我们要自己开辟一个,利用指针数组去开辟

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]);
	}
}

然后就是输出,还有输出坐标,所以我们需要把坐标存起来,存完之后再输出出去,那我们用什么存勒,这里我们用栈来存放,然后我们后面的思路就是初始化一个栈和一个入口,然后去走迷宫,如果能找到通路就打印坐标,如果找不到就退出,然后就销毁数组和栈

int main()
{
	int N = 0, M = 0;
	while (scanf("%d%d", &N, &M) != EOF)
	{
		// int a[n][m]; // vs2013 不支持
		// 动态开辟二维数组
		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);
		// PrintMaze(maze, N, M);
		PT entry = { 0, 0 };       //入口
		//迷宫走的思路
		if (GetMazePath(maze, N, M, entry))
		{
			//printf("找到通路\n");
			//
			PirntPath(&path);
		}
		else
		{
			printf("没有找到通路\n");
		}

		StackDestory(&path);

		for (int i = 0; i < N; ++i)
		{
			free(maze[i]);
		}
		free(maze);
		maze = NULL;
	}

	return 0;
}

二.利用利用深度优先遍历,回溯算法,四方向递归走迷宫

这个就是这个题目的难点,如何去走这个迷宫?

按上下左右方式去走,深度遍历,先往深处走,如果找不到再回溯过去,直到找到为止

1.做之前先来判断坐标的有效性

只要不是墙,并且坐标大于0坐标没有出迷宫,就都是有效的

//判断该坐标是否为有效可遍历的坐标
bool IsPass(int** maze, int N, int M, PT pos)
{
	if (pos.row >= 0 && pos.row < N
		&& pos.col >= 0 && pos.col < M
		&& maze[pos.row][pos.col] == 0)
	{
		return true;
	}
	else
	{
		return false;
	}
}

然后走迷宫,函数开始是先入栈,不管是不是都先入,如果这个坐标不是路径,后面再删除就可以了,走之前先判断一下,如果走到出口就直接结束,然后探测4个方向,遍历过的位置标记2,防止我再次遍历,向上向下方向遍历,只要改动作标就可以了

//判断路径坐标进行可以进行遍历
bool GetMazePath(int** maze, int N, int M, PT cur)
{
	//直接入栈
	StackPush(&path, cur);

	// 如果走到出口
	if (cur.row == N - 1 && cur.col == M - 1)
		return true;

	// 探测cur位置得上、下、左、右四个方向
	PT next;
	maze[cur.row][cur.col] = 2;  //已经遍历过的(包括当前)位置,标记为2

	//向上遍历
	next = cur;
	next.row -= 1;
	if (IsPass(maze, N, M, next))
	{
		if (GetMazePath(maze, N, M, next))
			return true;
	}

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

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

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

	StackPop(&path);  //不通,这个坐标不能使用,就出栈

	return false; //四个方向都不通,就返回false,递归会回溯,探索其他通路
}

三.栈的操作

收集到的数据不能直接用,我用占存起来了,不能直接释放,因为栈的特征是后进先出,所以我可以搭建两个栈来实现,把我第一个栈的数据导到第二个栈的数据在拿出来就可以了

// 输出栈里面的坐标路径
//要求从最开始的坐标先输出,而栈是后进先出,需要调整输出
void PirntPath(ST* ps)
{
	// path数据倒到rPath
	ST rPath;
	StackInit(&rPath);
	while (!StackEmpty(&path))
	{
		StackPush(&rPath, StackTop(&path));
		StackPop(&path);
	}

	while (!StackEmpty(&rPath))
	{
		PT top = StackTop(&rPath);
		printf("(%d,%d)\n", top.row, top.col);
		StackPop(&rPath);
	}

	StackDestory(&rPath);
}

四.总代码

注意:c语言库里是没有栈的,所以在这之前要自己写一个栈,这也是c语言不好的地方

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
#include<string.h>
//定义位置结构体
typedef struct Postion
{
	int row;
	int col;
}PT;

//栈的实现
typedef PT STDataType;

typedef struct Stack
{
	STDataType* a;   //通过数组实现栈的结构
	int top;
	int capacity;
}ST;
//初始化
void StackInit(ST* ps);
//释放内存、销毁空间
void StackDestory(ST* ps);
// 入栈
void StackPush(ST* ps, STDataType x);
// 出栈
void StackPop(ST* ps);
//取栈顶数据
STDataType StackTop(ST* ps);
//栈的大小
int StackSize(ST* ps);
//判空
bool StackEmpty(ST* ps);
//初始化
void StackInit(ST* ps)
{
	assert(ps);

	ps->a = (STDataType*)malloc(sizeof(STDataType) * 4);
	if (ps->a == NULL)
	{
		printf("malloc fail!\n");
		exit(-1);
	}

	ps->capacity = 4;
	ps->top = 0; //这使得top最终指向的是栈顶的后一个位置。若top=-1,则最终指向的是栈顶。
}
//释放内存、销毁空间
void StackDestory(ST* ps)
{
	assert(ps);
	free(ps->a);
	ps->a = NULL;
	ps->top = ps->capacity = 0;
}
// 入栈
void StackPush(ST* ps, STDataType x)
{
	assert(ps);

	// 满了->增容
	if (ps->top == ps->capacity)
	{
		STDataType* tmp = (STDataType*)realloc(ps->a, ps->capacity * 2 * sizeof(STDataType));
		if (tmp == NULL)
		{
			printf("realloc fail!\n");
			exit(-1);
		}
		else
		{
			ps->a = tmp;
			ps->capacity *= 2;
		}
	}

	ps->a[ps->top] = x;
	ps->top++;
}

// 出栈
void StackPop(ST* ps)
{
	assert(ps);
	// 栈空了,再调用Pop,就会直接中止程序报错
	assert(ps->top > 0);

	//ps->a[ps->top - 1] = 0; //置为0只考虑了int型等,若为char、double等就不适用了。
	ps->top--;
}
//取栈顶数据
STDataType StackTop(ST* ps)
{
	assert(ps);
	// 栈空了,再调用Top,就会直接中止程序报错
	assert(ps->top > 0);

	return ps->a[ps->top - 1];
}
//求栈大小
int StackSize(ST* ps)
{
	assert(ps);

	return ps->top;
}
//判空
bool StackEmpty(ST* ps)
{
	assert(ps);
	return ps->top == 0;
}

//maze实现
ST path;  //定义全局变量
//打印
void PrintMaze(int** maze, int N, int M)
{
	for (int i = 0; i < N; ++i)
	{
		for (int j = 0; j < M; ++j)
		{
			printf("%d ", maze[i][j]);
		}
		printf("\n");
	}
	printf("\n");
}

// 输出栈里面的坐标路径
//要求从最开始的坐标先输出,而栈是后进先出,需要调整输出
void PirntPath(ST* ps)
{
	// path数据倒到rPath
	ST rPath;
	StackInit(&rPath);
	while (!StackEmpty(&path))
	{
		StackPush(&rPath, StackTop(&path));
		StackPop(&path);
	}

	while (!StackEmpty(&rPath))
	{
		PT top = StackTop(&rPath);
		printf("(%d,%d)\n", top.row, top.col);
		StackPop(&rPath);
	}

	StackDestory(&rPath);
}
//判断该坐标是否为有效可遍历的坐标
bool IsPass(int** maze, int N, int M, PT pos)
{
	if (pos.row >= 0 && pos.row < N
		&& pos.col >= 0 && pos.col < M
		&& maze[pos.row][pos.col] == 0)
	{
		return true;
	}
	else
	{
		return false;
	}
}
//判断路径坐标进行可以进行遍历
bool GetMazePath(int** maze, int N, int M, PT cur)
{
	//直接入栈
	StackPush(&path, cur);

	// 如果走到出口
	if (cur.row == N - 1 && cur.col == M - 1)
		return true;

	// 探测cur位置得上、下、左、右四个方向
	PT next;
	maze[cur.row][cur.col] = 2;  //已经遍历过的(包括当前)位置,标记为2

	//向上遍历
	next = cur;
	next.row -= 1;
	if (IsPass(maze, N, M, next))
	{
		if (GetMazePath(maze, N, M, next))
			return true;
	}

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

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

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

	StackPop(&path);  //不通,这个坐标不能使用,就出栈

	return false; //四个方向都不通,就返回false,递归会回溯,探索其他通路
}

int main()
{
	int N = 0, M = 0;
	while (scanf("%d%d", &N, &M) != EOF)
	{
		// int a[n][m]; // vs2013 不支持
		// 动态开辟二维数组
		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);
		// PrintMaze(maze, N, M);
		PT entry = { 0, 0 };       //入口
		//迷宫走的思路
		if (GetMazePath(maze, N, M, entry))
		{
			//printf("找到通路\n");
			//
			PirntPath(&path);
		}
		else
		{
			printf("没有找到通路\n");
		}

		StackDestory(&path);

		for (int i = 0; i < N; ++i)
		{
			free(maze[i]);
		}
		free(maze);
		maze = NULL;
	}

	return 0;
}

总结

初阶迷宫问题的思路想通了就很好写了,我觉得最难的开辟二维数组和搭建那个栈,进阶的迷宫问题,他会设置一个最短路径的选择,这个下次在讲。

  • 75
    点赞
  • 54
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 85
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 85
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

LaNzikinh篮子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值