OJ题——迷宫问题

news2024/11/18 17:35:47

🍬个人主页:Yanni.—

🌈数据结构:Data Structure.​​​​​​

🎂C语言笔记:C Language Notes

🏀OJ题分享: Topic Sharing

前言:

在笔试或者竞赛的过程中,经常会遇到迷宫问题,今天用c语言给大家带来初入迷宫的思路,以及一些复杂条件增加的迷宫问题,相信大家看完之后会收获满满!

基础迷宫

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

这道OJ题曾经是百度某一年的其中一个笔试题,迷宫问题的本质就是一个图遍历问题,从起点开始不断四个方向探索,直到走到出口,走的过程我们可以借助数据结构-栈来记录走过路径的坐标。

栈记录坐标有两方面的作用:

1.记录走过的路径

2.便于走到死路时进行回溯找其他的通路

创建迷宫

我们把迷宫想象成xy坐标轴上的坐标,创造出一个二维数组,考虑到空间需要自己输入,所以我们这里开辟动态二维数组,要开辟动态二维数组,有两个操作。

1.创造指针数组

2.在指针数组的基础上开辟动态数组空间

typedef struct postion
{
	int row;
	int col;
}PT;
int main()
{
	int N = 0, M = 0;
	while (scanf("%d%d", &N, &M) != EOF)
	{
		//要开辟二维数组,首先开辟指针数组
		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]);
			}
		}
		PtintMaze(maze, N, M);
		StackInit(&path);
		PT entry = { 0,0 };
		if (GetMazePath(maze, N, M, entry))
		{
			PrintPath(&path);
		}
		else {
			printf("没有找到通路");
		}
		StackDestory(&path);

		//二维数组的销毁
		for (int i = 0; i < N; i++)
		{
			free(maze[i]);
		}
		free(maze);
		maze = NULL;
	}
	return 0;
}
//打印迷宫
void PtintMaze(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");
}

上面结构体定义了xy坐标,然后再开辟动态二维数组。

回溯算法

接下来解题。要从起始位置到达终点,不免会遇到死路,遇到就必须往回走,看回去的路有没有可以走的方向,所以大致思路为:

1.将每个坐标都需要进行四个方向的判断,可以走,那就走到下一个,将原来的0变成2,代表已经走过

2.遇到思路,往回走,再去判断之前的路的方向,可以走,就继续。

3.重复上面的操作,直到走到终点。

bool GetMazePath(int** maze, int N, int M, PT cur)
{
	//压栈
	StackPush(&path, cur);
	//判断是否到达终点
	if (cur.row == N - 1 && cur.col == M - 1)
	{
		return true;
	}

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

用bool类型来接收返回值,进行回溯。

下面是判断是否可以走的代码段

bool IsPass(int** maze, int N, int M, PT pos)
{
	if (pos.col >= 0 && pos.col < N
		&& pos.row >= 0 && pos.row < M
		&& maze[pos.row][pos.col] == 0)
	{
		return true;
	}
	else {
		return false;
	}
}

打印路径坐标 

路径坐标的打印就需要用到数据结构-栈(后进先出)了,将走过的路径储存再栈中,再通过倒栈的方法拿出来,这样迷宫的通路就打印出来了。(栈的实现在前面的博客已经讲过了,不懂的可以取看噢)

//打印路径坐标
void PrintPath(Stack* ps)
{
	//将path的数据倒到rpath
	Stack 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);
}

创造一个新的栈,将储存路径的栈的数据给进去,这样原本倒着的顺序变成顺序了。

复杂迷宫 

这里并不是迷宫的复杂,而是条件的复杂,新增加了两个条件。

1.要算出迷宫的最短路径。

2.往下走会消耗三个体力值,其他方向只消耗一个体力值,判断在规定的体力值的情况下能否走出迷宫。

思路分析

这是两种走出迷宫的通路,按照基础迷宫的方法,会出现第一种图的情况。接下来我们要在基础迷宫的基础上改动功能,使得出现第二种图的情况。

1.我们要创造两个栈,一个栈minpath用来存放最小的路径,另一个栈path用来进行实验 。

2.这里的栈拷贝涉及到浅拷贝和深拷贝的问题。

3.体力值可以用一个变量来表示。

回溯算法的改进

1.这里我们需要找到所有的通路,再将这些通路中最小的路径选出来,可以先将第一个通路放进minpath中,之后的通路存放在path中,两者进行比较,path>minpath,则minpath不变,反之,将path的路径拷贝到minpath当中。

2.要实现找到所有的通路,则不需要接收返回值,函数类型为void。

3.在找路的过程中,往回走时,要将走过的路变成障碍值,这样就可以寻找其他的路。

//回溯算法进行找路
void GetMazePath(int** maze, int N, int M, PT cur,int p)
{
	//压栈
	StackPush(&path, cur);
	//判断是否到达终点
	if (cur.row == 0 && cur.col == M - 1)
	{
		//找到了更短的路径,更新minpath
		if (p>0 && StackEmpty(&minpath) || StackSize(&minpath) > StackSize(&path))
		{
			StackDestory(&minpath);
			StackCopy(&path, &minpath);
		}
	}

	PT next;
	maze[cur.row][cur.col] = 2;
	//上
	next = cur;
	next.row -= 1;
	if (IsPass(maze, N, M, next))
	{
		GetMazePath(maze, N, M, next,p-3);
	}
	//下
	next = cur;
	next.row += 1;
	if (IsPass(maze, N, M, next))
	{
		GetMazePath(maze, N, M, next,p-1);
	}
	//左
	next = cur;
	next.col -= 1;
	if (IsPass(maze, N, M, next))
	{
		GetMazePath(maze, N, M, next,p-1);
	}
	//右
	next = cur;
	next.col += 1;
	if (IsPass(maze, N, M, next))
	{
		GetMazePath(maze, N, M, next,p-1);
	}
	maze[cur.row][cur.col] = 0;
	StackPop(&path);
}

深拷贝与浅拷贝 

在比较的过程中,会发生拷贝的算法,这里必须使用深拷贝,否则会发生越界访问的情况。

浅拷贝:仅仅将栈path里的坐标赋值到栈minpath当中,空间大小并没有改变。

深拷贝:需要将minpath里的空间释放,再创造出和栈path空间大小一样的空间,再将栈path里的值拷贝到栈minpath里面。

//深度拷贝 如果是进行浅度拷贝会发生越界访问
void StackCopy(Stack* ppath, Stack* pcopy)
{
	pcopy->a = (STDataType*)malloc(sizeof(STDataType*) * ppath->capacity);
	if (pcopy->a == NULL)
	{
		perror("malloc fail");
	}
	memcpy(pcopy->a, ppath->a, sizeof(STDataType*) * ppath->top);
	pcopy->top = ppath->top;
	pcopy->capacity = ppath->capacity;
}

总代码实现

Text.c

#include<stdio.h>
#include<stdbool.h>
#include<stdlib.h>
#include"Stack.h"
#include<string.h>

Stack path;
Stack minpath;
//打印迷宫
void PtintMaze(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");
}
//判断是否可以走
bool IsPass(int** maze, int N, int M, PT pos)
{
	if (pos.col >= 0 && pos.col < N
		&& pos.row >= 0 && pos.row < M
		&& maze[pos.row][pos.col] == 0)
	{
		return true;
	}
	else {
		return false;
	}
}

//打印路径坐标
void PrintPath(Stack* ps)
{
	//将path的数据倒到rpath
	Stack rpath;
	StackInit(&rpath);
	while (!StackEmpty(ps))
	{
		StackPush(&rpath, StackTop(ps));
		StackPop(ps);
	}
	while (StackSize(&rpath)>1)
	{
		PT top = StackTop(&rpath);
		printf("(%d,%d)\n", top.row, top.col);
		StackPop(&rpath);
	}
	PT top = StackTop(&rpath);
	printf("(%d,%d)\n", top.row, top.col);
	StackPop(&rpath);

	StackDestory(&rpath);
}
//深度拷贝 如果是进行浅度拷贝会发生越界访问
void StackCopy(Stack* ppath, Stack* pcopy)
{
	pcopy->a = (STDataType*)malloc(sizeof(STDataType*) * ppath->capacity);
	if (pcopy->a == NULL)
	{
		perror("malloc fail");
	}
	memcpy(pcopy->a, ppath->a, sizeof(STDataType*) * ppath->top);
	pcopy->top = ppath->top;
	pcopy->capacity = ppath->capacity;
}

//回溯算法进行找路
void GetMazePath(int** maze, int N, int M, PT cur,int p)
{
	//压栈
	StackPush(&path, cur);
	//判断是否到达终点
	if (cur.row == 0 && cur.col == M - 1)
	{
		//找到了更短的路径,更新minpath
		if (p>0 && StackEmpty(&minpath) || StackSize(&minpath) > StackSize(&path))
		{
			StackDestory(&minpath);
			StackCopy(&path, &minpath);
		}
	}

	PT next;
	maze[cur.row][cur.col] = 2;
	//上
	next = cur;
	next.row -= 1;
	if (IsPass(maze, N, M, next))
	{
		GetMazePath(maze, N, M, next,p-3);
	}
	//下
	next = cur;
	next.row += 1;
	if (IsPass(maze, N, M, next))
	{
		GetMazePath(maze, N, M, next,p-1);
	}
	//左
	next = cur;
	next.col -= 1;
	if (IsPass(maze, N, M, next))
	{
		GetMazePath(maze, N, M, next,p-1);
	}
	//右
	next = cur;
	next.col += 1;
	if (IsPass(maze, N, M, next))
	{
		GetMazePath(maze, N, M, next,p-1);
	}
	maze[cur.row][cur.col] = 0;
	StackPop(&path);
}


int main()
{
	int N = 0, M = 0, P = 0;
	while (scanf("%d%d%d", &N, &M,&P) != EOF)
	{
		//要开辟二维数组,首先开辟指针数组
		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]);
			}
		}

		PtintMaze(maze, N, M);
		StackInit(&path);
		StackInit(&minpath);

		PT entry = { 0,0 };
		GetMazePath(maze, N, M, entry,P);
		if (!StackEmpty(&minpath))
		{
			PrintPath(&minpath);
		}
		else {
			printf("没有找到通路");
		}
		StackDestory(&path);
		StackDestory(&minpath);

		//二维数组的销毁
		for (int i = 0; i < N; i++)
		{
			free(maze[i]);
		}
		free(maze);
		maze = NULL;
	}
	return 0;
}

Stack.h

#include<stdbool.h>
#include<stdlib.h>
#include<assert.h>
//创造坐标
typedef struct postion
{
	int row;
	int col;
}PT;
typedef PT STDataType;

typedef struct Stack
{ 
	STDataType* a;
	int top;     //栈顶
	int capacity;//栈容量
}Stack;
//初始化栈
void StackInit(Stack* ps);
//栈的销毁
void StackDestory(Stack* ps);
//压栈
void StackPush(Stack* ps,STDataType x);
//出栈
void StackPop(Stack* ps);
//获取栈顶元素
STDataType StackTop(Stack* ps);
//获取栈中有效元素的个数
int StackSize(Stack* ps);
//检查栈是否为空,如果不为空则返回0,如果为空则返回非零结果
bool StackEmpty(Stack* ps);//bool也可以使用 表示真假

Stack.c

#include"Stack.h"


void StackInit(Stack* ps)
{
	assert(ps);
	ps->a = (STDataType*)malloc(sizeof(STDataType) * 4);
	ps->top = 0;
	ps->capacity = 4;
}
void StackDestory(Stack* ps)
{
	assert(ps);
	free(ps->a);
	ps->a = NULL;
	ps->top = ps->capacity = 0;

}
//入栈
void StackPush(Stack* ps,STDataType x)
{
	assert(ps);
	if (ps->top == ps->capacity)//考虑到空间大小不够,使用realloc进行增容操作
	{
		STDataType* tmp = (STDataType*)realloc(ps->a, ps->capacity * 2 * sizeof(STDataType));
		if (tmp == NULL)//要注意tmp可能为空的情况。
		{
			printf("realloc fail!!!");
			exit(-1);
		}
		ps->a = tmp;//别忘了把指针a指向新开辟的空间
		ps->capacity *= 2;
	}
	ps->a[ps->top] = x;
	ps->top++;//注意个数的增加
}
void StackPop(Stack* ps)
{
	assert(ps);
	assert(ps->top > 0);//top不能小于或等于0,因为top是指向栈顶元素的下一个位置
	ps->top--;
}
STDataType StackTop(Stack* ps)
{
	assert(ps);
	assert(ps->top > 0);
	return ps->a[ps->top - 1];//因为top是指向栈顶元素的下一个位置,所以要-1

}
int StackSize(Stack* ps)
{
	assert(ps);
	return ps->top;

}
bool StackEmpty(Stack* ps)//bool也可以使用 表示真假
{
	assert(ps);
	return ps->top == 0;
}

好啦,这就是今天学习的分享啦!看到希望大家的三连呀!

如果有不当之处,欢迎大佬指正!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2132964.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

剖析 MySQL 数据库连接池(C++版)

目录 ☀️0. 前言 &#x1f324;️1. 数据库连接池概述 ⛅1.1 服务器与数据库交互 ⛅1.2 MySQL 数据库网络模型 ⛅1.3 MySQL 连接驱动安装 ⛅1.4 同步&#xff08;synchronous&#xff09;连接池与异步&#xff08;asynchronous&#xff09;连接池 ⛅1.5 同步连接池和异…

Spring 循环依赖原理及解决方案

一、什么是循环依赖 循环依赖指的是一个实例或多个实例存在相互依赖的关系&#xff08;类之间循环嵌套引用&#xff09;。 举例&#xff1a; Component public class AService {// A中注入了BAutowiredprivate BService bService; }Component public class BService {// B中也…

通信工程学习:什么是PC永久连接、SPC软永久连接

一、PC永久连接 PC&#xff08;Permanent Connection&#xff09;永久连接是一种由网管系统通过网管协议建立的长期稳定的连接方式。在ASON&#xff08;自动交换光网络&#xff09;中&#xff0c;PC永久连接沿袭了传统光网络的连接建立形式&#xff0c;其特点主要包括&#xff…

【Canvas与密铺】正六边形、正方形和正三角形的密铺

【成图】 【代码】 <!DOCTYPE html> <html lang"utf-8"> <meta http-equiv"Content-Type" content"text/html; charsetutf-8"/> <head><title>正六边形正方形和正三角形的密铺1920x1080</title><style t…

SpringSecurity原理解析(四):SpringSecurity初始化过程

1、对 SpringSecurity初始化时的几个疑问 通过对前边一个请求流转的分析&#xff0c;我们知道一个请求要想到达服务端Servlet需要经过n多个 拦截器处理&#xff0c;请求处理流程如下所示&#xff1a; 对于一个请求到来后会通过FilterChainProxy来匹配一个对应的过滤器链来处理该…

PEST分析法包括哪些内容?用在线白板工具轻松绘制,简单好用!

在当今瞬息万变的商业环境中&#xff0c;企业需要一个全面而系统的方法来分析外部环境对其经营的影响。PEST分析模型恰好提供了这样一个强大的工具&#xff0c;帮助企业洞察政治、经济、社会和技术因素对其发展的潜在影响。 然而&#xff0c;如何高效地创建PEST分析模型一直是…

Unity 第一人称游戏的武器被其他物体覆盖解决方案

在第一人称游戏的时候&#xff0c;会出现渲染过程中&#xff0c;主角的手持武器可能会被其他物体挡住。 解决方法 在主摄像机下再创建一个摄像机&#xff0c;负责渲染不同图层 Main Camera的参数&#xff1a;我们这个摄像机不渲染equipable层&#xff08;自定义武器为equipab…

前后端分离项目实现SSE

SSE介绍 在日常web开发中经常会遇到查看数据最新状态的业务场景&#xff0c;例如查看任务状态与日志内容等。比较场景的解决方案是轮循和SSE。 Server-Sent Events (SSE) 是一种允许服务器通过单向通道向客户端推送更新的技术。它基于HTTP协议&#xff0c;客户端使用一个标准…

2024CCPC网络预选赛

vp链接&#xff1a;Dashboard - The 2024 CCPC Online Contest - Codeforces B. 军训 II 序列 a 从小到大排列或者从大到小排列时&#xff0c;不整齐度是最小的。方案数是所有相同数字的个数的排列数的乘积。如果首尾的数字不同的话&#xff0c;还要再乘个 2。 #include <…

Running setup.py install for wxPython did not run successfully.

Running setup.py install for wxPython did not run successfully. 欢迎来到英杰社区https://bbs.csdn.net/topics/617804998 欢迎来到我的主页&#xff0c;我是博主英杰&#xff0c;211科班出身&#xff0c;就职于医疗科技公司&#xff0c;热衷分享知识&#xff0c;武汉城市开…

axure循环介绍

一直在犹豫要不要写关于axure循环方面的介绍&#xff0c;因为循环的场景用其它方法都是可以实现的&#xff0c;今天还是用上次手机号码判断的案例来写一下循坏吧。 1、页面新建元件&#xff0c;手机号码输入框重命名为【手机号码输入框】按钮重命名为【按钮】再在页面拖动上来一…

python学习第八节:爬虫的初级理解

python学习第八节&#xff1a;爬虫的初级理解 爬虫说明&#xff1a;爬虫准备工作&#xff1a;分析网站url分析网页内容 爬虫获取数据&#xff1a;1.使用urllib库发起一个get请求2.使用urllib库发起一个post请求3.网页超时处理4.简单反爬虫绕过5.获取响应参数6.完整请求代码 解析…

【Python机器学习】长短期记忆网络(LSTM)

目录 随时间反向传播 实践 模型的使用 脏数据 “未知”词条的处理 字符级建模&#xff08;英文&#xff09; 生成聊天文章 进一步生成文本 文本生成的问题&#xff1a;内容不受控 其他记忆机制 更深的网络 尽管在序列数据中&#xff0c;循环神经网络为对各种语言关系…

Java项目: 基于SpringBoot+mybatis+maven医院管理系统(含源码+数据库+任务书+开题报告+毕业论文)

一、项目简介 本项目是一套基于SpringBootmybatismaven医院管理系统 包含&#xff1a;项目源码、数据库脚本等&#xff0c;该项目附带全部源码可作为毕设使用。 项目都经过严格调试&#xff0c;eclipse或者idea 确保可以运行&#xff01; 该系统功能完善、界面美观、操作简单、…

强化网络安全:通过802.1X协议保障远程接入设备安全认证

随着远程办公和移动设备的普及&#xff0c;企业网络面临着前所未有的安全挑战。为了确保网络的安全性&#xff0c;同时提供无缝的用户体验&#xff0c;我们的 ASP 身份认证平台引入了先进的 802.1X 认证协议&#xff0c;确保只有经过认证的设备才能接入您的网络。本文档将详细介…

【我的 PWN 学习手札】Fastbin Attack

关于fastbin&#xff0c;有很多攻击利用手法&#xff0c;本篇只是讲述了修改fd指针&#xff0c;分配到fake_chunk&#xff0c;更多利用手法拆分到后面的博客中 目录 前言 一、Fastbin保护检查机制 二、利用手法 &#xff08;1&#xff09;分配到任意地址&#xff08;__mall…

VScode相关问题与解决

1.写c文件时找不到头文件stdio.h 在linux下我们gcc命令来编译c文件时&#xff0c;会遇到找不到头文件的问题 解决方法&#xff1a;我们每写完一个文件记得保存一下文件即可&#xff0c;这样就解决了找不到头文件的问题&#xff01; 参考链接&#xff1a; /usr/bin/ld: /us…

Java实现生成验证码实战

文章目录 需求描述思想思路实现代码实现效果 在实际项目中&#xff0c;管理端的登录&#xff0c;会涉及验证码的校验&#xff0c;简单的数字与字母组合形式&#xff0c;在Java中要如何生成与实现&#xff0c;记录下来&#xff0c;方便备查。 需求描述 生成8位的由数字、大写字…

总结拓展九:SAP数据迁移(2)

第三节 数据迁移工具LTMC实操 1、供应商&#xff08;BP&#xff09;主数据导入 1.1 首先在SAP S 4系统&#xff0c;通过事务代码“LTMC”跳转进入数据迁移控制台&#xff08;网页版&#xff09;&#xff1b; 1.2 点击“创建”按钮&#xff0c;创建迁移项目“NJDHMM-01”; 传…

AI问答-Vue实例属性/实例方法:$refs、$emit、$attrs、$props、$data...

一、本文简介 在Vue.js中&#xff0c;$ 符号通常用于表示Vue实例或组件上的内置属性和方法&#xff0c;这些被称为“实例属性”或“实例方法”。以下是一些常见的以$开头的Vue实例属性和方法 1.1、实例属性 序号实例属性解释1$dataVue实例的数据对象&#xff0c;用于存储组件…