广度优先搜索算法 - 迷宫找路

news2024/9/23 19:17:24

广度优先搜索算法

  • 1 思考问题
    • 1.1 这个迷宫需不需要指定入口和出口?
  • 2 先粗略实现
    • 2.1 源码
    • 2.2 源码解释
  • 3 优化代码
    • 3.1 优化读取文件部分
    • 3.2 增加错误处理
  • 4 再优化-让程序变得更加灵活
    • 4.1 用户外部可以循环输入入口和出口
  • 5 完整代码

这是一个提问者的提出的问题: 连接

数据结构算法,用C语言完成,
迷宫寻路:以一个的长方阵表示迷宫,用0和1分别表示迷宫中的通路和障碍,将迷宫的长方阵存储在相关数据文件中,迷宫数据从该文件中读取。找到一条从入口到出口的通路,或得到没有通路的结论。将找到的通路以三元组的形式输出,表示经过节点的坐标,表示从入口出发达到该节点的距离,每走一步距离加1。最终输出全部通路,并统计路径距离。

经过我们的讨论,我决定重新实现我之前的算法!以下是完整内容

1 思考问题

1.1 这个迷宫需不需要指定入口和出口?

我之前提供的算法是默认起点为左上角,终点为右下角。如果你的迷宫入口或者出口为“1”,这将导致无法找到路径;这也就解释了,为什么你的迷宫会出现这样的结果!

根据的你的提供迷宫,我猜测,你的想法可能是:这个迷宫不需要指定出口和入口,而是让程序自己找。
这样的情况算法实现起来会更复杂,更困难,需要加很多个约束条(比如,入口不能是出口等)!
根据之前的沟通,我感觉您像是初学者(如果我猜错了,冒犯到您,向您道歉),应该不会一上来就挑战那么难的算法!所以这个算法应该是指定入口和出口的,这样就会让程序变得简单很多!

2 先粗略实现

2.1 源码

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

#define MAX_SIZE 100

typedef struct {
	int x;
	int y;
	int distance;
} Node;

void BFS(int maze[][MAX_SIZE], int n, int m, int startX, int startY, int endX, int endY) {
	// 定义方向数组,表示上下左右四个方向
	int dx[4] = { -1, 1, 0, 0 };
	int dy[4] = { 0, 0, -1, 1 };

	// 定义队列和标记数组
	Node queue[MAX_SIZE * MAX_SIZE];
	int front = 0, rear = 0;
	bool visited[MAX_SIZE][MAX_SIZE] = { false };
	Node parentMap[MAX_SIZE][MAX_SIZE];

	// 将起点加入队列中
	Node start = { startX, startY, 0 };
	queue[rear++] = start;
	visited[startX][startY] = true;

	// 开始广度优先搜索
	while (front < rear) {
		// 取出队列中的节点
		Node curr = queue[front++];

		// 检查是否到达终点
		if (curr.x == endX && curr.y == endY) {
			printf("Find a path: ");
			printf("(%d,%d,%d)", curr.x, curr.y, curr.distance);
			Node parent = parentMap[curr.x][curr.y];
			while (parent.x != startX || parent.y != startY) {
				printf(" <- ");
				printf("(%d,%d,%d)", parent.x, parent.y, parent.distance);
				parent = parentMap[parent.x][parent.y];
			}
			printf(" <- ");
			printf("(%d,%d,0)\n", startX, startY);
			continue;
		}

		// 访问相邻节点
		for (int i = 0; i < 4; i++) {
			int nx = curr.x + dx[i];
			int ny = curr.y + dy[i];
			if (nx >= 0 && nx < n && ny >= 0 && ny < m && maze[nx][ny] == 0 && !visited[nx][ny]) {
				Node next = { nx, ny, curr.distance + 1 };
				queue[rear++] = next;
				visited[nx][ny] = true;
				parentMap[nx][ny] = curr;
			}
		}
	}
}

int main() {
	// 从文件中读取迷宫数据
	int maze[MAX_SIZE][MAX_SIZE];
	int n, m;
	FILE *fp = fopen("maze.txt", "r");
	fscanf(fp, "%d%d", &n, &m);
	for (int i = 0; i < n; i++) {
		for (int j = 0; j < m; j++) {
			fscanf(fp, "%d", &maze[i][j]);
		}
	}
	fclose(fp);

	// 确定起点和终点
	int startX = 0,startY = 0, endX = n - 1, endY = m - 1;

	// 使用广度优先搜索算法搜索最短路径
	BFS(maze, n, m, startX, startY, endX, endY);

	return 0;
}

2.2 源码解释

#define _CRT_SECURE_NO_WARNINGS

我的编译器使用的是vs2017,而我在代码中使用了fopen等函数,编译器认为:使用的是不安全的C标准库函数fopen(),建议使用更安全的fopen_s()函数来代替。但是没有必要,所以我加上了这行代码,让编译器忽略这个警告。如果你的编译器运行我之前的代码,编译没有报错,你也可以将这一行去掉。

	int startX = 0,startY = 0, endX = n - 1, endY = m - 1;

这一行是在main函数中的,他的目的就是确定迷宫的入口和出口,默认是左上到右下,你可以通过更改数值实现,自定义入口和出口。
例如:起点(0,1);终点(6,8)

int startX = 0,startY = 1, endX = 6, endY = 7;

这里我提供一个7X9迷宫:

7 9
1 0 0 0 1 0 1 1 0
0 1 0 1 1 0 0 0 0
0 0 0 0 0 0 1 1 1
1 1 0 1 0 1 0 0 0
0 0 0 1 0 0 0 1 1
1 1 0 0 0 1 0 0 0
0 0 0 1 0 1 0 0 0

你可能注意到了,我的迷宫结构和之前提供给你的迷宫结构发生了变化,在开始处,增加了迷宫的大小“7 9”,这是告诉程序我迷宫的大小,也就是说,现在的迷宫大小我们也可以是自定义的了
运行结果:

在这里插入图片描述

代码已经可以实现了!

3 优化代码

3.1 优化读取文件部分

以下是将读取迷宫数据部分的代码封装成一个函数 readMaze,并添加了判断文件是否成功打开的代码:

bool readMaze(const char *filename, int maze[][MAX_SIZE], int *n, int *m) {
    FILE *fp = fopen(filename, "r");
    if (!fp) {
        printf("Error: Failed to open file %s.\n", filename);
        return false;
    }
    fscanf(fp, "%d%d", n, m);
    for (int i = 0; i < *n; i++) {
        for (int j = 0; j < *m; j++) {
            fscanf(fp, "%d", &maze[i][j]);
        }
    }
    fclose(fp);
    return true;
}

然后,在 main 函数中调用该函数读取迷宫数据

int main() {
    int maze[MAX_SIZE][MAX_SIZE];
    int n, m;
    if (!readMaze("maze.txt", maze, &n, &m)) {
        return 1;
    }
    
    // 确定起点和终点
    int startX = 0, startY = 0, endX = n-1, endY = m-1;//注意:可以自定义
    
    // 使用广度优先搜索算法搜索最短路径
    BFS(maze, n, m, startX, startY, endX, endY);
    
    return 0;
}

这样做的好处是,将读取迷宫数据的代码封装成一个函数,可以使 main 函数更加清晰简洁,也方便在其他函数中重复使用该代码。另外,添加判断文件是否成功打开的代码,可以在打开文件失败时及时提示用户,并退出程序。

3.2 增加错误处理

  1. 判断自定义的迷宫入口和出口是否合法,比如说,刚才迷宫大小为7*9,你却定义一个出口为(10,11),那么这个出口肯定是越界了
  2. 以及判断你自定义迷宫入口和出口是否合法,也就是说,如果你设计迷宫入口或者出口为“1”,也是不合理的。
    一个函数实现这两个功能
bool isValidPoint(int maze[][MAX_SIZE], int n, int m, int x, int y) {
    if (x < 0 || x >= n || y < 0 || y >= m) {
        return false;
    }
    if (maze[x][y] != 0) {
        return false;
    }
    return true;
}

然后,我们可以在 BFS 函数和 main 函数中使用该函数来检查起点和终点的合法性。例如,在 BFS 函数中,可以将下面这行代码:

if (nx >= 0 && nx < n && ny >= 0 && ny < m && maze[nx][ny] == 0 && !visited[nx][ny]) {

修改为:

if (isValidPoint(maze, n, m, nx, ny) && !visited[nx][ny]) {

在 main 函数中,可以添加以下代码来检查起点和终点的合法性:

if (!isValidPoint(maze, n, m, startX, startY)) {
    printf("Error: Invalid start point (%d,%d).\n", startX, startY);
    return 1;
}
if (!isValidPoint(maze, n, m, endX, endY)) {
    printf("Error: Invalid end point (%d,%d).\n", endX, endY);
    return 1;
}

这样,当输入的起点或终点不合法时,程序会输出错误信息并退出。
我们使用刚才的迷宫进行测试,起点设置为(0,0)
运行如下:
在这里插入图片描述
因为刚才的迷宫入口为1,所以给出了(0,0)位置无效的提示,其他的可以自行测试!

4 再优化-让程序变得更加灵活

4.1 用户外部可以循环输入入口和出口

输入格式为0,1英文逗号隔开

int main() {
	int maze[MAX_SIZE][MAX_SIZE];
	int n, m;
	if (!readMaze("maze.txt", maze, &n, &m)) {
		return 1;
	}
	// 从键盘输入起点和终点的坐标
	int startX, startY, endX, endY;
	while (1) {
		printf("Please enter the start point (x,y), or enter -1 to quit: ");
		int len = scanf("%d,%d", &startX, &startY);
		if (len == 1 && startX == -1)
			break;
		if ( len != 2) {
			printf("Error: Invalid input for start point.\n");
			continue;
		}
		
	
		if (!isValidPoint(maze, n, m, startX, startY)) {
			printf("Error: Invalid start point (%d,%d).\n", startX, startY);
			continue;
		}
		printf("Please enter the end point (x,y): ");
		if (scanf("%d,%d", &endX, &endY) != 2) {
			printf("Error: Invalid input for end point.\n");
			continue;
		}
		if (!isValidPoint(maze, n, m, endX, endY)) {
			printf("Error: Invalid end point (%d,%d).\n", endX, endY);
			continue;
		}

		// 使用广度优先搜索算法搜索最短路径
		BFS(maze, n, m, startX, startY, endX, endY);
	}

	return 0;
}

运行结果:
在这里插入图片描述

5 完整代码

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

#define MAX_SIZE 100

typedef struct {
	int x;
	int y;
	int distance;
} Node;

bool isValidPoint(int maze[][MAX_SIZE], int n, int m, int x, int y) {
	if (x < 0 || x >= n || y < 0 || y >= m) {
		return false;
	}
	if (maze[x][y] != 0) {
		return false;
	}
	return true;
}


bool readMaze(const char *filename, int maze[][MAX_SIZE], int *n, int *m) {
	FILE *fp = fopen(filename, "r");
	if (!fp) {
		printf("Error: Failed to open file %s.\n", filename);
		return false;
	}
	fscanf(fp, "%d%d", n, m);
	for (int i = 0; i < *n; i++) {
		for (int j = 0; j < *m; j++) {
			fscanf(fp, "%d", &maze[i][j]);
		}
	}
	fclose(fp);
	return true;
}


void BFS(int maze[][MAX_SIZE], int n, int m, int startX, int startY, int endX, int endY) {
	// 定义方向数组,表示上下左右四个方向
	int dx[4] = { -1, 1, 0, 0 };
	int dy[4] = { 0, 0, -1, 1 };

	// 定义队列和标记数组
	Node queue[MAX_SIZE * MAX_SIZE];
	int front = 0, rear = 0;
	bool visited[MAX_SIZE][MAX_SIZE] = { false };
	Node parentMap[MAX_SIZE][MAX_SIZE];

	// 将起点加入队列中
	Node start = { startX, startY, 0 };
	queue[rear++] = start;
	visited[startX][startY] = true;

	// 开始广度优先搜索
	while (front < rear) {
		// 取出队列中的节点
		Node curr = queue[front++];

		// 检查是否到达终点
		if (curr.x == endX && curr.y == endY) {
			printf("Find a path: ");
			printf("(%d,%d,%d)", curr.x, curr.y, curr.distance);
			Node parent = parentMap[curr.x][curr.y];
			while (parent.x != startX || parent.y != startY) {
				printf(" <- ");
				printf("(%d,%d,%d)", parent.x, parent.y, parent.distance);
				parent = parentMap[parent.x][parent.y];
			}
			printf(" <- ");
			printf("(%d,%d,0)\n", startX, startY);
			continue;
		}

		// 访问相邻节点
		for (int i = 0; i < 4; i++) {
			int nx = curr.x + dx[i];
			int ny = curr.y + dy[i];
			if (isValidPoint(maze, n, m, nx, ny) && !visited[nx][ny]) {
				Node next = { nx, ny, curr.distance + 1 };
				queue[rear++] = next;
				visited[nx][ny] = true;
				parentMap[nx][ny] = curr;
			}
		}
	}
}

int main() {
	int maze[MAX_SIZE][MAX_SIZE];
	int n, m;
	if (!readMaze("maze.txt", maze, &n, &m)) {
		return 1;
	}
	// 从键盘输入起点和终点的坐标
	int startX, startY, endX, endY;
	while (1) {
		printf("Please enter the start point (x,y), or enter -1 to quit: ");
		int len = scanf("%d,%d", &startX, &startY);
		if (len == 1 && startX == -1)
			break;
		if ( len != 2) {
			printf("Error: Invalid input for start point.\n");
			continue;
		}
		
		if (!isValidPoint(maze, n, m, startX, startY)) {
			printf("Error: Invalid start point (%d,%d).\n", startX, startY);
			continue;
		}
		printf("Please enter the end point (x,y): ");
		if (scanf("%d,%d", &endX, &endY) != 2) {
			printf("Error: Invalid input for end point.\n");
			continue;
		}
		if (!isValidPoint(maze, n, m, endX, endY)) {
			printf("Error: Invalid end point (%d,%d).\n", endX, endY);
			continue;
		}

		// 使用广度优先搜索算法搜索最短路径
		BFS(maze, n, m, startX, startY, endX, endY);
	}

	return 0;
}

maze.txt内容

7 9
1 0 0 0 1 0 1 1 0
0 1 0 1 1 0 0 0 0
0 0 0 0 0 0 1 1 1
1 1 0 1 0 1 0 0 0
0 0 0 1 0 0 0 1 1
1 1 0 0 0 1 0 0 0
0 0 0 1 0 1 0 0 0

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

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

相关文章

制造业的云ERP在外网怎么访问?内网服务器一步映射到公网

随着企业信息化、智能化时代的到来&#xff0c;很多制造业企业都在用云ERP。用友U 9cloud通过双版本公有云专属、私有云订阅、传统软件购买三种模式满足众多制造业企业的需求&#xff0c;成为一款适配中型及中大型制造业的云ERP&#xff0c;是企业数智制造的创新平台。 用友U 9…

python 面向对象利用selenium【获取某东商品信息】

用python程序和谷歌selenium插件获取某东商品详细信息【商品名称、商品简介&#xff0c;超链接】利用selenium自动化程序 中的css页面结构索取来获取详细数据关于谷歌selenium的安装方法和使用方法第一步检查自己谷歌浏览器的版本1.1 找到设置&#xff1a;并鼠标点击进入1.2进入…

排序评估指标——NDCG和MAP

在搜索和推荐任务中&#xff0c;系统常返回一个item列表。如何衡量这个返回的列表是否优秀呢&#xff1f; 例如&#xff0c;当我们检索【推荐排序】&#xff0c;网页返回了与推荐排序相关的链接列表。列表可能会是[A,B,C,G,D,E,F],也可能是[C,F,A,E,D]&#xff0c;现在问题来了…

使用canvas写一个flappy bird小游戏

简介 canvas 是HTML5 提供的一种新标签&#xff0c;它可以支持 JavaScript 在上面绘画&#xff0c;控制每一个像素&#xff0c;它经常被用来制作小游戏&#xff0c;接下来我将用它来模仿制作一款叫flappy bird的小游戏。flappy bird&#xff08;中文名&#xff1a;笨鸟先飞&am…

XSS注入进阶练习篇(一)XSS-LABS通关教程

XSS注入进阶练习篇1.常用标签整理2. XSS-LABS 练习2.1 level 1 无限制2.2 level 2 双引号闭合2.3 level 3 源码函数书写不全&#xff0c;单引号绕过2.4 level 4 无尖括号绕过2.5 level 5 a标签使用2.6 level 6 大小写绕过2.7 level 7 置空替换绕过2.8 level 8 URL编码绕过 - 重…

安全—07day

Tomcat AJP 文件包含漏洞&#xff08;CVE-2020- 1938&#xff09; 漏洞概述 Ghostcat(幽灵猫&#xff09;是由长亭科技安全研究员发现的存在于Tomcat 中的安全漏洞&#xff0c;由于Tomcat AJP 协议设计上存在缺陷,攻击者通过Tomcat AJP Connector可以读取或包含 Tomcat上所有…

Java岗面试题--Java并发(日积月累,每日三题)

目录面试题一&#xff1a;并行和并发有什么区别&#xff1f;面试题二&#xff1a;线程和进程的区别&#xff1f;追问&#xff1a;守护线程是什么&#xff1f;面试题三&#xff1a;创建线程的几种方式&#xff1f;1. 继承 Thread 类创建线程&#xff0c;重写 run() 方法2. 实现 …

详解垃圾回收算法,优缺点是什么?|金三银四系列

本文详细介绍了在 JVM 中如何判断哪些对象是需要回收的&#xff0c;以及不同的垃圾回收算法以及优缺点。点击上方“后端开发技术”&#xff0c;选择“设为星标” &#xff0c;优质资源及时送达上篇文章详细介绍了 JVM 的结构以及其内存结构&#xff0c;需要阅读请移步。本文主要…

Android 9.0系统源码_通知服务(二)应用发送状态栏通知的流程

前言 应用发送一个显示在状态栏上的通知&#xff0c;对于移动设备来说是很常见的一种功能需求&#xff0c;本篇文章我们将会结合Android9.0系统源码具体来分析一下&#xff0c;应用调用notificationManager触发通知栏通知功能的源码流程。 一、应用触发状态栏通知 应用可以通…

关于HDFS

目录 一、HDFS概述 二、HDFS架构与工作机制 三、HDFS的Shell操作 四、Hdfs的API操作 一、HDFS概述 HDFS&#xff1a;Hadoop Distributed File System&#xff1b;一种分布式文件管理系统&#xff0c;通过目录树定位文件。使用场景&#xff1a;一次写入&#xff0c;多次读出…

java 自定义注解

文章目录前言Annotation包自定义注解自定义注解示例参考文章&#xff1a;java 自定义注解 用处_java注解和自定义注解的简单使用参考文章&#xff1a;java中自定义注解的作用和写法前言 在使用Spring Boot的时候&#xff0c;大量使用注解的语法去替代XML配置文件&#xff0c;十…

SpringAMQP消息队列(SpringBoot集成RabbitMQ)

一、初始配置1、导入maven坐标<!--rabbitmq--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId></dependency>2、yml配置spring:rabbitmq:host: 你的rabbitmq的ipport: …

4G模块DTU网关远程抄表方案(三):水表188协议

4G模块DTU网关远程抄表方案&#xff08;三&#xff09;&#xff1a;水气电表188协议 1 CTJ 188协议简介 CJ/T188协议规定了户用计量仪表(以下简称仪表)&#xff0c;包括水表、燃气表、热量表等仪表数据传输的基本原则&#xff0c;接口形式及物理性能、数据链路、数据标识及数…

目标检测回归损失函数简介:SmoothL1/IoU/GIoU/DIoU/CIoU Loss

目标检测 回归损失函数1、Smooth L1 Loss2、 IoU Loss3、 GIoU Loss &#xff08;Generalized-IoU Loss&#xff09;4、 DIoU Loss &#xff08;Distance-IoU Loss&#xff09;5、 CIoU Loss &#xff08;Complete-IoU Loss&#xff09;总结&#xff1a;目标检测任务的损失函数…

【计算机网络】数据链路层(下)

文章目录媒体接入控制媒体接入控制-静态划分信道随机接入 CSMACD协议随机接入 CSMACA协议MAC地址MAC地址作用MAC地址格式MAC地址种类MAC地址的发送顺序单播MAC地址广播MAC地址多播MAC地址随机MAC地址IP地址区分网络编号IP地址与MAC地址的封装位置转发过程中IP地址与MAC地址的变…

1.1 硬件与micropython固件烧录及自编译固件

1.ESP32硬件和固件 淘宝搜ESP32模块,20-50元都有,自带usb口,即插即用. 固件下载地址:MicroPython - Python for microcontrollers 2.烧录方法 为简化入门难度,建议此处先使用带GUI的开发工具THonny,记得不是给你理发的tony老师. 烧录的入口是: 后期通过脚本一次型生成和烧…

[软件工程导论(第六版)]第3章 需求分析(课后习题详解)

文章目录1. 为什么要进行需求分析&#xff1f;通常对软件系统有哪些需求&#xff1f;2. 怎样与用户有效地沟通以获取用户的真实需求&#xff1f;3. 银行计算机储蓄系统的工作过程大致如下&#xff1a;储户填写的存款单或取款单由业务员输入系统&#xff0c;如果是存款则系统记录…

C语言经典编程题100例(81~100)

目录81、习题7-7 字符串替换82、习题8-10 输出学生成绩83、习题8-2 在数组中查找指定元素84、习题8-3 数组循环右移85、题8-9 分类统计各类字符个数86、习题9-2 计算两个复数之积87、习题9-6 按等级统计学生成绩88、习题11-1 输出月份英文名89、习题11-2 查找星期90、练习10-1 …

分享113个HTML娱乐休闲模板,总有一款适合您

分享113个HTML娱乐休闲模板&#xff0c;总有一款适合您 113个HTML娱乐休闲模板下载链接&#xff1a;https://pan.baidu.com/s/1aWYO2j2pSTjyqlQPHa0-Jw?pwdbium 提取码&#xff1a;bium Python采集代码下载链接&#xff1a;采集代码.zip - 蓝奏云 海上的沤鸟HTML网页模板…

(三十六)Vue解决Ajax跨域问题

文章目录环境准备vue的跨域问题vue跨域问题解决方案方式一方式二上一篇&#xff1a;&#xff08;三十五&#xff09;Vue之过渡与动画 环境准备 首先我们要借助axios发送Ajax&#xff0c;axios安装命令&#xff1a;npm i axios 其次准备两台服务器&#xff0c;这里使用node.j…