尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

C语言与数据结构课设:老鼠走迷宫升级版源码与算法解析

发布时间:2026/9/25 1:56:00

资讯中心
01
ARTICLE

C语言与数据结构课设:老鼠走迷宫升级版源码与算法解析

C语言与数据结构课设:老鼠走迷宫升级版源码与算法解析
简介面向C语言与数据结构课程设计老鼠走迷宫游戏升级版是一套完整可运行的课程设计资源。程序启动后展示迷宫地图玩家通过方向键控制老鼠在限时内到达粮仓具备结果判定、迷宫编辑墙与路互换、查找所有路径及最短路径等能力覆盖课设常见考点与难点。压缩包共5个文件包括C源代码、编译好的exe可执行程序及3个txt迷宫数据文件整体仅50KB轻量且便于直接运行和学习。已有3889人学习下载适合初次接触迷宫类项目的读者参考通过exe可快速验证游戏玩法通过cpp源文件可学习迷宫数据结构建模、路径遍历算法及键盘交互实现。迷宫文件支持自行修改便于扩展不同地图测试整体麻雀虽小但功能完备能帮助理解数据结构在游戏开发中的实际应用。1. 老鼠走迷宫游戏升级版课程设计为什么说它是 C 语言与数据结构课设里的“全能选手”很多同学第一次看到“老鼠走迷宫游戏升级版课程设计c语言数据结构源代码迷宫文件”这个标题时第一反应是这不就是个深搜题吗二维数组、四个方向、走不通就回头半小时写完了。但真正动手做才发现这份作业把指针、二维数组动态分配、栈与队列、文件读取、路径回放全部串了一遍比单纯写个链表反转或冒泡排序更能检验你有没有把 C 语言和数据结构的知识连成一条线。它解决的是“学完了却不知道如何组织一个完整程序”的尴尬。适合正在赶课程设计的人、准备考研上机题的人以及期末复习想找一份能动手复现的实战代码的人。2. 迷宫求解核心算法DFS 与 BFS、栈与队列的选型逻辑2.1 深度优先搜索与递归回溯一条路走到黑靠的是系统栈老鼠走迷宫最简单的求解思路是深度优先搜索DFS每走到一个没去过的格子就随便挑一个方向继续走如果四个方向都走不通就退回上一个格子再试另一个方向。这种“退回”动作在计算机里就是弹栈。C 语言里最省事的写法是用递归递归调用天然就有一层系统调用栈在帮你做回溯。下面这段是递归版的核心函数很多课设代码都是从它改出来的#define MAX_ROWS 100 #define MAX_COLS 100 typedef struct { int rows, cols; int grid[MAX_ROWS][MAX_COLS]; // 1 表示墙0 表示路 int visited[MAX_ROWS][MAX_COLS]; // 访问标记避免回头路 } Maze; // 递归求解从 (r, c) 出发是否能走到 (er, ec) int solve(Maze *m, int r, int c, int er, int ec) { if (r er c ec) return 1; // 到达终点 if (m-visited[r][c]) return 0; // 走过的格子不再走 m-visited[r][c] 1; int dr[4] {0, 1, 0, -1}; // 右、下、左、上 int dc[4] {1, 0, -1, 0}; for (int k 0; k 4; k) { int nr r dr[k]; int nc c dc[k]; if (nr 0 nr m-rows nc 0 nc m-cols m-grid[nr][nc] 0 !m-visited[nr][nc]) { if (solve(m, nr, nc, er, ec)) return 1; } } return 0; // 四个方向都走不通回溯 }这段代码里有两个参数值得细看。第一个是方向数组dr和dc的顺序右、下、左、上会让搜索优先向右下角走如果迷宫出口默认在右下角这种策略通常更快找到一条路如果把方向顺序改成上、右、下、左路径形态和搜索耗时都会变。第二个是visited标记没有它老鼠会在死胡同里反复横跳递归永远不结束这是新手最常翻的车。递归的问题也很明显递归深度等于当前探索路径的长度。像 50×50 这种深度两三千的迷宫Windows 默认的 1MB 栈空间很容易被打穿程序直接崩溃。所以实战里我一般把递归改成手动栈这个会在第 4 章给出完整代码。2.2 广度优先搜索与队列第一次碰到终点时路径一定最短深度优先能找到一条路但不保证最短。如果课程设计题面里写了“求最短路径”或者“打印最短步数”那就必须换广度优先搜索BFS。BFS 的思路是逐层扩散从起点出发先走一步能到的所有格子再走两步能到的所有格子以此类推。因为每一层都按步数推进所以第一次到达终点时扩展的层数就是最短步数。BFS 需要一个队列来保存“待扩展的格子”每次从队头取一个格子扩展新格子放进队尾。标准《数据结构》教材以严蔚敏的 C 语言版为代表会把迷宫求解放在栈与队列那一章就是这个原因——DFS 对应栈BFS 对应队列一个项目同时考两个数据结构。typedef struct { int row, col; } Point; // 队列数组容量等于格子总数每个格子最多入队一次 Point queue[MAX_ROWS * MAX_COLS]; int head 0, tail 0; // BFS 求解path 数组用于回放路径pathLen 返回路径长度 int solveByBFS(Maze *m, Point start, Point end, Point *path, int *pathLen) { memset(m-visited, 0, sizeof(m-visited)); head tail 0; queue[tail] start; m-visited[start.row][start.col] 1; int dr[4] {-1, 1, 0, 0}; int dc[4] {0, 0, -1, 1}; while (head tail) { Point cur queue[head]; if (cur.row end.row cur.col end.col) { int len 0; Point p cur; // 从终点倒着找回起点这里省略了 prev 数组 // 完整版会在扩展时记录每个点的前驱见第 4 章 *pathLen len; return 1; } for (int i 0; i 4; i) { int nr cur.row dr[i]; int nc cur.col dc[i]; if (nr 0 nr m-rows nc 0 nc m-cols m-grid[nr][nc] 0 !m-visited[nr][nc]) { m-visited[nr][nc] 1; queue[tail] (Point){nr, nc}; } } } return 0; }队列容量设为MAX_ROWS * MAX_COLS是够用的因为每个格子最多被入队一次不会发生“队列满了但还没结束”的情况。方向顺序对 BFS 的路径形态影响不像 DFS 那么大但会影响“多条最短路径里选哪一条”的表现。2.3 选型对照递归、手动栈、队列升级版应该怎么选如果你的课设只是“找到一条路径”递归 DFS 加上路径打印就能交差。但如果标题叫“升级版”一般意味着三个硬指标地图不能写死在代码里、要有最短路径结果、要有可视化的输出。我的建议是递归版本用来理解原理、写实验报告里的算法分析手动栈 DFS 作为兜底方案应对深迷宫BFS 作为主功能输出最短路径。三个函数都写上答辩时老师问“为什么这里不用递归”你就能直接解释栈溢出和系统栈开销的问题这是加分项。3. 迷宫文件设计把地图从代码里解放出来升级版的第一步3.1 数字迷宫格式行列、起点、终点、地图分段约定“源代码迷宫文件”这个交付结构说明地图和程序是分离的。最稳妥的格式是纯数字文本第一行写行数和列数第二行写起点坐标第三行写终点坐标从第四行开始是地图矩阵0 表示路1 表示墙。下面是一个 5×5 的示例maze1.txt5 5 0 0 4 4 0 1 0 0 0 0 1 0 1 0 0 0 0 1 0 1 1 0 1 0 0 0 0 0 0为什么要把起点终点单独写成两行而不是默认左上角进、右下角出因为“升级版”往往要求出入口可以配置比如从 (0,1) 进、从 (4,3) 出。写死起点终点会让换地图时被迫改代码答辩时一句“地图和逻辑分离”比写一百行功能代码都有说服力。3.2 动态读图代码malloc 二维数组与文件 IO 的参数细节typedef struct { int rows, cols; int startRow, startCol; int endRow, endCol; int **grid; // 动态二维数组 } Maze; // 从文件读取数字迷宫 int loadMaze(const char *filename, Maze *maze) { FILE *fp fopen(filename, r); if (fp NULL) { printf(无法打开迷宫文件: %s\n, filename); return -1; } // 前三个 fscanf 依次读行列、起点、终点 fscanf(fp, %d %d, maze-rows, maze-cols); fscanf(fp, %d %d, maze-startRow, maze-startCol); fscanf(fp, %d %d, maze-endRow, maze-endCol); // 先分配行指针数组再逐行分配列空间 maze-grid (int **)malloc(maze-rows * sizeof(int *)); for (int i 0; i maze-rows; i) { maze-grid[i] (int *)malloc(maze-cols * sizeof(int)); for (int j 0; j maze-cols; j) { fscanf(fp, %d, maze-grid[i][j]); } } fclose(fp); return 0; }两个参数细节要说清楚。第一fscanf的%d会自动跳过空格、换行和 Windows 的\r所以数字迷宫用记事本编辑也不会读错位这是数字格式比字符格式省心的地方。第二释放内存时要先free每一行的grid[i]最后free整个grid顺序反了会泄漏或报堆损坏。这个读图函数的返回值设计成-1失败、0成功比直接exit(1)优雅调用方可以根据返回值决定是换文件还是提示用户。3.3 字符迷宫格式用 S 和 E 标记出入口附过滤 \r 的解析代码另一种常见的地图文件是字符画风格#表示墙、空格表示路、S表示起点、E表示终点。这种格式人眼看着直观但解析比数字格式麻烦最大的坑是 Windows 下的文本文件行尾带有\r\n用fgetc逐字符读时会把\r当成一个迷宫字符读进来导致地图串行。// 逐行读取字符迷宫过滤行尾的 \r 和 \n int loadMazeChar(const char *filename, Maze *maze) { FILE *fp fopen(filename, r); if (fp NULL) return -1; char line[256]; fgets(line, sizeof(line), fp); // 第一行: 行列数 sscanf(line, %d %d, maze-rows, maze-cols); maze-grid (int **)malloc(maze-rows * sizeof(int *)); for (int i 0; i maze-rows; i) { if (fgets(line, sizeof(line), fp) NULL) return -1; // 去掉行尾的 \n 和 \r这是字符格式的关键处理 size_t len strlen(line); while (len 0 (line[len - 1] \n || line[len - 1] \r)) { line[--len] \0; } maze-grid[i] (int *)malloc(maze-cols * sizeof(int)); for (int j 0; j maze-cols line[j] ! \0; j) { if (line[j] #) { maze-grid[i][j] 1; } else if (line[j] S) { maze-grid[i][j] 0; maze-startRow i; maze-startCol j; } else if (line[j] E) { maze-grid[i][j] 0; maze-endRow i; maze-endCol j; } else { maze-grid[i][j] 0; // 空格和其他字符都当路 } } } fclose(fp); return 0; }这段代码里的while循环就是在处理\r\n的历史包袱。我在实际课设里更推荐数字迷宫因为字符格式容易出现“地图里有个看不见的字符”这种玄学故障数字格式的调试成本低很多。字符格式的优点是方便人读适合放在实验报告里展示地图长什么样。4. 核心源代码落地MazeSolver 结构体、手动栈 DFS、队列 BFS4.1 用一个结构体管住所有状态网格、访问标记、前驱表第 2 章的递归版是理解用的真正写成课程设计主程序时要把所有迷宫状态收进一个结构体避免grid、visited、path散落在 main 函数里被改乱。我一般这样组织#define MAX_ROWS 100 #define MAX_COLS 100 typedef struct { int row, col; } Point; typedef struct { int rows, cols; int startRow, startCol, endRow, endCol; int grid[MAX_ROWS][MAX_COLS]; // 原始地图 int visited[MAX_ROWS][MAX_COLS]; // 访问标记 Point prev[MAX_ROWS][MAX_COLS]; // 前驱表记录每个点从哪个点来 } MazeSolver;这里用了定长二维数组而不是动态分配是因为课设地图通常在 100×100 以内定长数组让memset的初始化更简单也省去释放内存的烦恼。prev数组是整个“升级版”的灵魂DFS 和 BFS 在扩展格子时只记录“从哪个格子来”等走到终点再顺着prev一步步回退到起点这样就能拼接出完整的路径而不是只知道“能走通”或“最短步数是 12”。4.2 手动栈 DFS不用递归也能回溯附路径回放Point dfsStack[MAX_ROWS * MAX_COLS]; int dfsTop -1; // 显式栈 DFS返回路径到 path 数组 int solveByDFS(MazeSolver *maze, Point start, Point end, Point *path, int *pathLen) { memset(maze-visited, 0, sizeof(maze-visited)); dfsTop -1; dfsStack[dfsTop] start; maze-visited[start.row][start.col] 1; maze-prev[start.row][start.col] start; int dr[4] {0, 1, 0, -1}; // 右、下、左、上 int dc[4] {1, 0, -1, 0}; while (dfsTop 0) { Point cur dfsStack[dfsTop--]; if (cur.row end.row cur.col end.col) { // 顺着 prev 回放路径再逆序成从头到尾 int len 0; Point p cur; while (p.row ! start.row || p.col ! start.col) { path[len] p; p maze-prev[p.row][p.col]; } path[len] start; for (int i 0; i len / 2; i) { Point t path[i]; path[i] path[len - 1 - i]; path[len - 1 - i] t; } *pathLen len; return 1; } for (int k 0; k 4; k) { int nr cur.row dr[k]; int nc cur.col dc[k]; if (nr 0 nr maze-rows nc 0 nc maze-cols maze-grid[nr][nc] 0 !maze-visited[nr][nc]) { maze-visited[nr][nc] 1; maze-prev[nr][nc] cur; dfsStack[dfsTop] (Point){nr, nc}; } } } *pathLen 0; return 0; }参数调整的空间有两个。第一个是方向数组顺序右、下、左、上会让 DFS 优先贴近右下角的终点适合入口左上、出口右下的经典迷宫如果你的迷宫出口在别处把方向改成对应优先级即可。第二个是栈容量MAX_ROWS * MAX_COLS已经覆盖了所有格子都入栈的极端情况再小就可能越界。这个函数和递归版的结果不一定相同——显式栈的搜索顺序受入栈顺序影响但都能找到一条通路。4.3 队列 BFS最短路径核心函数Point bfsQueue[MAX_ROWS * MAX_COLS]; int qHead 0, qTail 0; // 队列 BFS返回最短路径到 path 数组 int solveByBFS(MazeSolver *maze, Point start, Point end, Point *path, int *pathLen) { memset(maze-visited, 0, sizeof(maze-visited)); qHead qTail 0; bfsQueue[qTail] start; maze-visited[start.row][start.col] 1; maze-prev[start.row][start.col] start; int dr[4] {-1, 1, 0, 0}; int dc[4] {0, 0, -1, 1}; while (qHead qTail) { Point cur bfsQueue[qHead]; if (cur.row end.row cur.col end.col) { int len 0; Point p cur; while (p.row ! start.row || p.col ! start.col) { path[len] p; p maze-prev[p.row][p.col]; } path[len] start; for (int i 0; i len / 2; i) { Point t path[i]; path[i] path[len - 1 - i]; path[len - 1 - i] t; } *pathLen len; return 1; } for (int k 0; k 4; k) { int nr cur.row dr[k]; int nc cur.col dc[k]; if (nr 0 nr maze-rows nc 0 nc maze-cols maze-grid[nr][nc] 0 !maze-visited[nr][nc]) { maze-visited[nr][nc] 1; maze-prev[nr][nc] cur; bfsQueue[qTail] (Point){nr, nc}; } } } *pathLen 0; return 0; }BFS 和 DFS 的代码结构几乎一样差别只在“扩展顺序的数据结构”DFS 用栈后进先出BFS 用队列先进先出。prev数组在 BFS 里还有一个额外价值由于 BFS 逐层扩展每个格子的prev记录的必然是“从起点到该格的最短路径上的前一个点”所以最终回放出来的路径一定是最短的。这是“升级版”里最有技术含量的一段答辩时老师大概率会盯着它问。4.4 控制台画图和 main 组织把成果变成可演示的程序路径算出来了还得让老师一眼看懂。我习惯的做法是复制一张地图到字符二维数组把路径格子标成*起点标S终点标E再逐行打印void printSolution(MazeSolver *maze, Point *path, int pathLen) { char display[MAX_ROWS][MAX_COLS]; for (int i 0; i maze-rows; i) { for (int j 0; j maze-cols; j) { display[i][j] maze-grid[i][j] 1 ? # : ; } } for (int k 0; k pathLen; k) { display[path[k].row][path[k].col] *; } display[maze-startRow][maze-startCol] S; display[maze-endRow][maze-endCol] E; for (int i 0; i maze-rows; i) { for (int j 0; j maze-cols; j) { printf(%c , display[i][j]); } printf(\n); } printf(路径长度(包含起点和终点): %d\n, pathLen); }main 函数按“读图 → 选算法 → 打印”的流程组织支持命令行传入迷宫文件名int main(int argc, char *argv[]) { MazeSolver maze; const char *filename (argc 1) ? argv[1] : maze1.txt; if (loadMaze(filename, maze) ! 0) return 1; Point start {maze.startRow, maze.startCol}; Point end {maze.endRow, maze.endCol}; Point path[MAX_ROWS * MAX_COLS]; int pathLen 0; printf( DFS 求解 \n); if (solveByDFS(maze, start, end, path, pathLen)) { printSolution(maze, path, pathLen); } printf(\n BFS 最短路径 \n); if (solveByBFS(maze, start, end, path, pathLen)) { printSolution(maze, path, pathLen); } return 0; }用命令行参数指定文件名的好处是换地图不用重新编译直接./maze maze2.txt就能跑。这个习惯在课设答辩时很受用老师想验证你的程序能不能处理新地图只需要自己写一个迷宫文件塞给你。5. 踩坑与排查迷宫课设最容易翻车的五个细节5.1 路径穿墙而过坐标的行列写反了现象程序输出的路径直接穿过#墙或者走出地图边界。原因二维数组的习惯是grid[行][列]但有人会顺手把横坐标当第一维导致访问的是grid[列][行]。方向扩展时nr算的是行、nc算的是列如果判断越界时写成nr maze-cols恰好行列数相同的方阵迷宫看不出来一旦换成矩形迷宫必炸。解决全部统一用row、col命名判断越界写四条件nr 0 nr maze-rows nc 0 nc maze-cols。我习惯在一开始就把rows和cols打印出来核对矩形地图最容易暴露这个问题。5.2 地图读进来整体错位Windows 换行符和不可见字符的锅现象数字迷宫用fscanf读得好好的但字符迷宫读进来第一行正常、后面全部串位或者地图最后多了几个奇怪的字符。原因Windows 记事本写的文本文件行尾是\r\n用fgets按行读时line字符串末尾带着\r。如果不处理这个\r会被当成地图里的一个格子塞进数组。解决每次读完整行后先把行尾的\n和\r去掉再解析。第 3 章代码里那个while循环就是标准处理。另一个后悔药是直接用数字格式配fscanf它能跳过所有空白字符让 Windows 和 Linux 的地图文件完全通用。5.3 栈数组越界程序跑到一半崩了现象小地图一切正常换一张 50×50 的大迷宫程序走几步就报错退出或者输出一堆乱码后崩溃。原因手动栈数组大小设成MAX_STACK 200这类固定值而迷宫路径长度可能超过它。极端情况下深度优先会把大量候选格子同时压在栈里。解决栈容量直接设为MAX_ROWS * MAX_COLS这是理论上限因为每个格子最多入栈一次不会超出。同理BFS 队列容量也要用这个上限别用小了。5.4 明明有路程序却返回“走不通”现象把一份网上找来的迷宫文件导入后solveByDFS返回 0但肉眼明明能看到通路。原因常见的有三个。一是起点或终点在文件里被标成墙值写成 1程序第一个格子就撞墙二是visited没有memset清零上一次运行留下的标记导致新搜索跳过正确路径三是迷宫文件行列数与实际地图不匹配有的是“行 列”顺序反了。解决读图之后立刻做合法性校验检查起点和终点坐标是否越界、grid[startRow][startCol]和grid[endRow][endCol]是否等于 0不合法就直接报错而不是进入搜索。visited每次求解前必须清零这个成本很低但能省掉大量排查时间。5.5 控制台输出乱码源文件编码与控制台代码页不一致现象程序在 Windows 命令行里运行地图和路径符号正常但中文提示变成????或者一团乱码。原因源码文件保存成 UTF-8而 Windows 命令行默认代码页可能是 GBK936编码不一致导致中文字符显示错乱。课程设计程序里如果到处是中文printf这个问题几乎避不开。解决最简单的方案是程序里的提示语全用英文路径和地图只用#、*、S、E这些 ASCII 字符。非要中文提示就在main开头加system(chcp 65001 nul);配合源文件存成 UTF-8。我通常选第一种省事且跨平台干净。6. 升级方向与验证技巧让这份课设从“能运行”到“能拿高分”如果时间还有富余这五个升级方向按性价比排序前两个容易加而且答辩效果好。第一路径动画演示。在打印路径时用system(cls)清屏配合Sleep(100)让路径一步步长出来效果非常直观。注意Sleep在 Windows 头文件是windows.hLinux 下要用usleep。第二比较 DFS 和 BFS 的耗时与路径长度。clock()函数包住两个求解函数打印“DFS 路径长度 25耗时 0.5msBFS 路径长度 15耗时 0.3ms”。这条数据放在实验报告里直接证明你理解两种算法的区别。第三统计走过的格子数。在visited赋 1 时加一个计数器能展示 DFS 可能探索了几乎整个迷宫才找到一条长路而 BFS 只扩展了少量格子就锁定了最短路径。第四把路径存成单链表而非数组每走到一个新格子就插入链表节点最终遍历链表打印路径。这一步能顺带展示链表操作课设评分点会多说一项。第五加一个“无解判断”。如果两个求解函数都返回 0打印No path found并退出。这个分支看起来简单但能防止答辩时老师故意喂一张无解地图让你现场翻车。做完这些用一个小得不能再小的地图验证正确性3×3 地图0 0 0 / 0 1 0 / 0 0 0起点左上角终点右下角预期路径只有一条长度为 5。如果这段能通过再换一张 5×5 手算过的地图最后用全通路地图验证 BFS 的最短路径长度必须等于(rows-1) (cols-1)这个值是固定公式算不对就是 BFS 有问题。我现在写这类程序一定会先做一张 3×3 的手工地图跑通再换大图这个习惯帮我省掉了大量“拿大图调试到深夜”的冤枉时间。希望帮到你。本文还有配套的精品资源点击获取
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。