简单迷宫问题

article/2025/9/14 5:23:08

简单迷宫问题

    • 一、问题描述
    • 二、数据组织
    • 三、算法说明
    • 附录:完整代码

#简单迷宫问题

一、问题描述

给定一个M×N得迷宫,求一条从指定入口到出口得迷宫路径。假设一个迷宫如图1所示,(这里M=N=8),其中每个方块用空白表示通道,用阴影表示障碍物。
在这里插入图片描述

规定和要求:

  • 所求得迷宫路径是简单路径,即在求得得迷宫路径上不重复出现同一方块。
  • 迷宫可能有多个路径,这里只考虑求得其中一条。

二、数据组织

为了表示迷宫,一般会很自然得考虑使用一个二维数组表示,如使用 m g [ M ] [ N ] mg[M][N] mg[M][N],其中每个元素使用0表示可通行单元,使用1表示障碍物。

但是在本解法中,采用栈数数据结构存储所有的障碍物得坐标,使用一个整形数字(4个字节)表示。具体来说就是使用一个整形的高位2个字节表示坐标的X,使用整形的低2个字节表示坐标Y。因此使用一个整形数字就能完成上图中障碍物的坐标表示。由于考虑的问题规模较小,所以2个字节能表示的迷宫大小足够我们使用了。

对坐标的存储及其操作

/* 分别取出2个整形的高2位和低2位,合并为1个整形,这里假设hi,low小于65535 */
int _maze_element_maker(int hi,int low){return (hi << (sizeof(int)*8/2)) | low ;
}

在迷宫中,位置的移动是体现在坐标的变化,为了描述方便,我们使用地图方向描述,比如向坐标轴X正方向移动,就是向东EAST移动,因此对坐标四个方向的移动,可以使用以下的函数实现。


int _maze_step_north(int e){return e - (1 << sizeof(int) * 4);
}int _maze_step_south(int e){return e + (1 << sizeof(int) * 4);
}int _maze_step_west(int e){return e - 1;
}int _maze_step_east(int e){return e + 1;
}

数据栈的实现

本案例取自《数据结构》教材第3章栈,因此这里使用的栈,是使用C语言实现了的栈的数据结构。因此完整的代码中包含顺序栈的实现。为了方便介绍,这先给出栈的数据结构,以及主要函数的声明。

  1. 顺序栈的定义
typedef int ElemType,*ElemTypePtr;
typedef struct{int stack_size;ElemType *top,*bottom;
} Stack;

定义了栈中存储的数据类型是ElemType,对应的指针类型是ElemTypePtr(等效 ElemType *)。从以上代码的第1行定义可以知道,实际代码ElemType类型就是int。所以后面所有的代码中,ElemType就是int类型。

  1. 基本栈的操作函数

/* 定义栈中元素遍历时候的处理函数指针类型 */
typedef int (*TranversAtom)(ElemType);/* 遍历栈元素操作*/
int Traverse(Stack* stack,TranversAtom transAtom);/* 初始化栈 */
int InitStack(Stack* stack);/* 获得栈中元素个数 */
long StackLength(Stack* stack);/* 释放栈 */
void DestroyStack(Stack* stack);/* 获得栈顶元素 */
ElemType GetTop(Stack *stack);/* POP操作 */
ElemType Pop(Stack *stack);/* PUSH操作*/
void Push(Stack* stack, ElemType elem);

以上的是使用C语言实现的顺序栈的函数声明,具体代码实现可以参考附录中完整代码,这里不再叙述,这里特别注意Traverse函数是用于遍历栈中的所有元素的函数,在后面的算法中,可以实现检查将要入栈的元素是否已经在栈中操作,而不需要将整个栈中元素都POP出来。

三、算法说明

1.算法

本算法的采用双栈进行求解,一个工作栈和一个路径栈配合工作。工作栈用于记录下一步可能走的所有位置,用于不断的试探,而路径栈就存放如果试探到下一步可以走,那么将其位置放入路径栈中。而如果试探向后没有任何路可走,那么弹出工作栈顶的试探元素,实现后退到上一步的分析,然后再从工作栈顶找到其他的可能的点进行分析。直到工作栈顶分析的元素和终点相等,表明已经走到了终点,此时算法结束。

注意:

  1. 工作栈存放的是下一步可能走的位置点,所以栈顶总是准备尝试的位置。
  2. 路径栈是已经走过的点,栈顶是最近走到的位置。
  3. 当遇无路可走的时候,说明工作栈顶这个点是死路,所以出栈,准备尝试其他方向。
  4. 当连续退回多个步的时候,不但工作栈中尝试的点要出栈,同时还要在退栈的过程中检测已经走过的路径栈顶是不是同一个点,如果是,那么也需要同步退栈。简单说,就是同时工作栈和路径栈相同的点都要出栈,以保证回退过程数据的一致。
  5. 算法假设迷宫总是存在到终点的路线。

2.代码实现

主要算法代码:

Push(&stack_path, 0);  /* 压入0值,用于分割迷宫原始障碍物坐标和求解的路径坐标。 */
Push(&stack_work, start);do{e = GetTop(&stack_work);if (e==GetTop(&stack_path)) { /* 检查将要分析的位置e是否在路径栈中 */Pop(&stack_path);         /* 如果存在,说明这条路前面已经走过,*/Pop(&stack_work);         /* 现在再次出现在工作栈顶端,说明这条*/continue;                 /* 路走不通,所以全部退栈,放弃!!  */}count = 0;next = _maze_step_east(e);if(_maze_position_valid(&stack_path, next)){Push(&stack_work, next);count++;}next = _maze_step_north(e);if(_maze_position_valid(&stack_path, next)){Push(&stack_work, next);count++;}next = _maze_step_west(e);if(_maze_position_valid(&stack_path, next)){Push(&stack_work, next);count++;}next = _maze_step_south(e);if(_maze_position_valid(&stack_path, next)){Push(&stack_work, next);count++;}if(count>0){                  /* count不为零,表明在当前的位置e是有下一步    */Push(&stack_path, e);     /* 可走的,所以将e位置压入路径栈中,假设走一步 */}else if(Pop(&stack_work)==GetTop(&stack_path)) /* e向后无路可走的情况下,工作栈退栈    */Pop(&stack_path);                          /* 同时,如果路线最近走过这个点,也放弃。*/}while(e!=end);

以上代码整体比较简单,比较难以理解的地方已经添加了注释,其思路就是每次从工作栈中出栈的点,如果是回溯情况下,那么必须将路径栈中的点也弹出。

程序运行效果如图2。
在这里插入图片描述

3.算法小结

本算法使用了2个栈保存数据,使用了循环代替了通常使用的DFS算法。因为通常使用DFS算法实现都是使用的函数递归,因此对于数据量很大的情况下,有可能会出现程序栈溢出的情况。但是本算法重新实现了顺序栈,并且顺序栈是可以不断按需要延展的。所以理论上程序规模只会收到主机内存大小的限制。

附录:完整代码

#include <stdio.h>
#include <stdlib.h>#define STACK_INIT_SIZE 100
#define STACK_RELLOC_SIZE 10/* 顺序栈定义声明 */
typedef int ElemType,*ElemTypePtr;
typedef struct{int stack_size;ElemType *top,*bottom;
} Stack;
typedef int (*TranversAtom)(ElemType);
int InitStack(Stack* stack);
long StackLength(Stack* stack);
void DestroyStack(Stack* stack);
ElemType GetTop(Stack *stack);
ElemType Pop(Stack *stack);
void Push(Stack* stack, ElemType elem);
int Traverse(Stack* stack,TranversAtom transAtom);/* 迷宫辅助函数声明 */
int Travers_Find(ElemType tofind);
ElemType _maze_element_maker(int hi,int low);
ElemType _maze_HiDWord(ElemType e);
ElemType _maze_LoDWord(ElemType e);
void _maze_print(ElemType e);
void _maze_init(Stack* stack);
void _maze_print_map(Stack * stack);
int _maze_position_valid(Stack * stack,ElemType e);
ElemType _maze_step_north(ElemType e);
ElemType _maze_step_south(ElemType e);
ElemType _maze_step_west(ElemType e);
ElemType _maze_step_east(ElemType e);ElemType global_elmt;int main(int argc, char* argv[])
{int count;Stack stack_work, stack_path;ElemType start=_maze_element_maker(8, 8), end=_maze_element_maker(1, 1), e, next;InitStack(&stack_work);/* 工作栈,用于存放试探前进过程得位置,方便回溯。*/InitStack(&stack_path);/* 路径栈,记录已经走过得路径。*/_maze_init(&stack_path);Push(&stack_path, 0);  /* 压入0值,用于分割迷宫原始障碍物坐标和求解的路径坐标。 */Push(&stack_work, start);do{e = GetTop(&stack_work);if (e==GetTop(&stack_path)) { /* 检查将要分析的位置e是否在路径栈中 */Pop(&stack_path);         /* 如果存在,说明这条路前面已经走过,*/Pop(&stack_work);         /* 现在再次出现在工作栈顶端,说明这条*/continue;                 /* 路走不通,所以全部退栈,放弃!!  */}count = 0;next = _maze_step_east(e);if(_maze_position_valid(&stack_path, next)){Push(&stack_work, next);count++;}next = _maze_step_north(e);if(_maze_position_valid(&stack_path, next)){Push(&stack_work, next);count++;}next = _maze_step_west(e);if(_maze_position_valid(&stack_path, next)){Push(&stack_work, next);count++;}next = _maze_step_south(e);if(_maze_position_valid(&stack_path, next)){Push(&stack_work, next);count++;}if(count>0){                  /* count不为零,表明在当前的位置e是有下一步    */Push(&stack_path, e);     /* 可走的,所以将e位置压入路径栈中,假设走一步 */}else if(Pop(&stack_work)==GetTop(&stack_path)) /* e向后无路可走的情况下,工作栈退栈    */Pop(&stack_path);                          /* 同时,如果路线最近走过这个点,也放弃。*/}while(e!=end);_maze_print_map(&stack_path);DestroyStack(&stack_work);DestroyStack(&stack_path);return 0;
}int InitStack(Stack* stack){stack->stack_size = STACK_INIT_SIZE;stack->bottom = (ElemTypePtr)malloc(stack->stack_size*sizeof(ElemType));if (!stack->bottom) {printf(">> 内存分配失败 !\n");return -1;}stack->top = stack->bottom;return 1;
}long StackLength(Stack* stack){return stack->top - stack->bottom;
}void DestroyStack(Stack* stack){if (stack->bottom) {free(stack->bottom);}
}ElemType GetTop(Stack *stack){if (StackLength(stack)) {return *(stack->top-1);}printf(">> 空栈无数据!\n");return 0;
}ElemType Pop(Stack *stack){ElemType el = GetTop(stack);if(StackLength(stack)){stack->top--;return el;}return 0;
}void Push(Stack* stack, ElemType elem){long len =StackLength(stack);if ( len >= stack->stack_size ) {ElemTypePtr tmp = (ElemTypePtr)realloc(stack->bottom, (stack->stack_size+=STACK_RELLOC_SIZE)*sizeof(ElemType));if(tmp){stack->bottom = tmp;stack->top = stack->bottom + len;}else{printf(">> 内存分配失败\n");return;}}*(stack->top) = elem;stack->top++;
}int Traverse(Stack* stack,TranversAtom transAtom){long i,len = StackLength(stack);int ret=0;for (i=0; i<len; i++) {if ((ret = transAtom(*(stack->bottom+i)))!=0) {return ret;}}return ret;
}int Travers_Find(ElemType tofind){return tofind==global_elmt ? 1 : 0;
}ElemType _maze_element_maker(int hi,int low){return (hi << (sizeof(ElemType)*4)) | low ;
}ElemType _maze_step_north(ElemType e){return e - (1 << sizeof(ElemType) * 4);
}ElemType _maze_step_south(ElemType e){return e + (1 << sizeof(ElemType) * 4);
}ElemType _maze_step_west(ElemType e){return e - 1;
}ElemType _maze_step_east(ElemType e){return e + 1;
}ElemType _maze_HiDWord(ElemType e){return e >> (sizeof(ElemType)*4);
}ElemType _maze_LoDWord(ElemType e){return e - (_maze_HiDWord(e) << (sizeof(ElemType)*4));
}void _maze_print(ElemType e){ElemType hi,lo;hi = _maze_HiDWord(e);lo = _maze_LoDWord(e);printf("(%d,%d)",hi,lo);
}void _maze_init(Stack* stack){int i;ElemType x[]={1,2,2,2,3,3,3,3,4,4,4,5,6,6,6,7,7,7/*,5*/};ElemType y[]={8,4,6,7,1,2,4,7,4,5,7,3,3,6,7,1,2,7/*,7*/};    for (i=0; i<sizeof(x)/sizeof(int); i++)Push(stack, _maze_element_maker(y[i], x[i]));for (i=0; i<10; i++) {Push(stack, _maze_element_maker(0,i));Push(stack, _maze_element_maker(9,i));Push(stack, _maze_element_maker(i,0));Push(stack, _maze_element_maker(i,9));}}int _maze_position_valid(Stack * stack,ElemType e){global_elmt = e;    return !Traverse(stack, Travers_Find);
}void _maze_print_map(Stack * stack){ElemType e;int map[10][10]={0},i=1,j;int k=0; int z=0;for(k=0; k<10; k++) {for(z=0; z<10; z++) {map[k][z]=' '-'0';}}puts("$ 迷宫的解:");printf("起点=>");while (GetTop(stack)!=0) {e = Pop(stack);_maze_print(e);printf("=>");map[_maze_HiDWord(e)][_maze_LoDWord(e)] = '@' - '0';}puts("终点.\n");while (StackLength(stack)) {e = Pop(stack);map[_maze_HiDWord(e)][_maze_LoDWord(e)] = '#' - '0';}printf("  ");for (i=0; i<10; i++)printf("%2d",i);printf("\n");for (i=0; i<10; i++) {printf("%2d",i);for (j=0; j<10; j++) {printf("%2c",map[i][j]+'0');}printf("\n");}printf("\n图例:\n# —— 迷宫障碍\n@ —— 线路点\n");
}

http://chatgpt.dhexx.cn/article/hdQ7VXtt.shtml

相关文章

maven生命周期lifecycle和plugins介绍

一、Maven的生命周期 生命周期的定义&#xff1a;Maven的生命周期就是为了对所有的构建过程进行抽象和统一。在大量项目的构建过程中&#xff0c;Maven总结出了一套高度完善的&#xff0c;易于扩展的生命周期&#xff0c;包括项目的清理&#xff0c;初始化&#xff0c;编译&am…

【Maven】IDEA中Maven生命周期

Maven生命周期&#xff08;lifecycle&#xff09;由各个阶段组成&#xff0c;每个阶段由Maven的插件plugin来执行完成。 生命周期&#xff08;lifecycle&#xff09;主要包括clean、resources、complie、install、pacakge、testResources、testCompile、deploy等&#xff0c;其…

Maven生命周期与插件

竟无语凝噎 文章目录 前言一、生命周期二、插件总结 前言 maven原来有这么多道道 一、生命周期 Maven对项目构建的生命周期划分为三套 clean&#xff1a;清理工作 default&#xff1a;核心工作&#xff0c;例如编译&#xff0c;测试&#xff0c;打包&#xff0c;部署等 si…

Maven 生命周期详解

思考&#xff1a;我们常使用的maven命令&#xff0c;比如 mvn clean install&#xff0c;mvn clean package 这些命令到底最后是如何工作的&#xff1f; 在这里我们还是先一步步来&#xff0c;其实它们运行的是生命周期中对应的phase阶段。 Maven 拥有三套独立的生命周期&…

关于maven生命周期的理解

晚上有点无聊&#xff0c;看到了一些东西引发了自己的思路&#xff0c;就想将maven的一些东西总结总结&#xff0c;有从网上抄的&#xff0c;也有自己的思路。 一、生命周期是指什么&#xff08;lifecycle&#xff09; Maven的生命周期就是对所有的构建过程进行抽象和统一。包…

Maven的生命周期

一、生命周期简介&#xff1a; Maven强大的一个重要的原因是它有一个十分完善的生命周期模型&#xff0c;这个生命周期可以从两方面来理解&#xff1a; 运行Maven的每个步骤都由它来定义的&#xff0c;这种预定义的默认行为使得我们使用Maven变得简单。 这个模型是一种标准&am…

Maven 生命周期

1. Maven 构建生命周期 Maven 构建生命周期就是 Maven 将一个整体任务划分为一个个的阶段&#xff0c;类似于流程图&#xff0c;按顺序依次执行。也可以指定该任务执行到中间的某个阶段结束。Maven 的内部有三个构建生命周期&#xff0c;分别是 clean, default, site。其中 def…

Maven生命周期

Maven生命周期 个人网站 https://blog.deschen.cn/ 文章目录 Maven生命周期一、Maven生命周期的定义二、Maven三套独立的生命周期三、Maven的插件 一、Maven生命周期的定义 Maven的生命周期就是为了对所有的构建过程进行抽象和统一。包括项目的清理&#xff0c;初始化&#x…

Maven —— 生命周期

每个生命周期的各个环节都是由各种插件完成&#xff01;&#xff01;&#xff01;Maven有三个相互独立的生命周期&#xff08;Maven的这三个生命周期不能看成一个整体&#xff09;&#xff01;&#xff01;&#xff01; mvn clean&#xff1a;清理编译的项目mvn compile&#x…

代码统计利器--CLOC

MAC下安装命令:$ brew install cloc其他的linux安装 $ aptitude install cloc使用方法.到目录下运行: $ cloc .The default output will show you a breakdown by language. Here’s an example of what it’ll look like: php much? You can, of course, customize the poop …

代码统计工具cloc使用

简介 CLOC(Count Lines of Code)&#xff0c;是一个可以统计多种编程语言中空行、评论行和物理行的工具。这个工具还是蛮实用的&#xff0c;可以帮我们快速了解一个项目中代码的信息。 安装使用 windows 10 win10下可以去github上下载其最新版&#xff0c;截止本文时&#…

windows代码量计算开源工具cloc安装和使用教程

windows代码量计算开源工具cloc 下载cloc使用cloc 下载cloc cloc下载地址: https://github.com/AlDanial/cloc/releases. 选择exe版本的&#xff0c;也可以下载我上传的1.90版本链接: 点击跳转下载地址 使用cloc 下载好cloc-1.90.exe之后&#xff0c;重命名未cloc.exe&…

Linux统计代码量命令cloc

记录一下Linux中一个非常好用的代码量统计命令&#xff1a;cloc 安装步骤&#xff1a; sudo apt-get install cloc使用方法&#xff1a; 进入到要统计的工程根目录&#xff1a; cloc .运行结果:

Windows环境下用cloc统计代码量

cloc一款开源代码统计工具&#xff0c;支持windows和Linux环境。能统计指定文件夹或文件夹中文件数files、空白行数blank、注释行数comment和代码行数code。今天介绍windows环境下的使用方法。 使用简单&#xff1a; 下载&#xff1a; Releases AlDanial/cloc (github.com)…

代码统计工具CLOC的使用

简介 CLOC(Count Lines of Code)&#xff0c;是一个可以统计多种编程语言中空行、评论行和物理行的工具。这个工具还是蛮实用的&#xff0c;可以帮我们快速了解一个项目中代码的信息。 安装使用 windows 10 win10下可以去github上下载其最新版&#xff0c;截止本文时&#…

cloc 代码统计工具

安装 yum -y install cloc使用 [rootnode1 new-website]# cloc .135 text files.134 unique files.20 files ignored.github.com/AlDanial/cloc v 1.70 T3.63 s (32.0 files/s, 26416.0 lines/s) ------------------------------------------------------------------------…

10分钟掌握高效代码行统计工具——cloc

cloc 一款高效的代码行统计工具&#xff0c;且跨多平台&#xff1a; WindowLinuxMac… … 高效是其优点&#xff0c;且稳定性比较好。 Linux版的可以处理超大工程的文件&#xff0c;不会出现其它同类工具在处理超大文件时崩溃的问题。 用法简单&#xff0c;学习成本低&…

前端代码统计工具cloc的安装与使用

怎么来衡量一个web端项目的大小&#xff0c;一是看页面多少&#xff0c;二是看源代码行数。页面多少比较好统计&#xff0c;通过 Router 的配置大概就能知道。但是源代码行数&#xff0c;如果要一个文件一个文件去计算&#xff0c;那就费了劲了。有问题有需求&#xff0c;就会有…

“无法启动mysql服务,错误1053”解决办法

启动MySQL服务时&#xff0c;报错如下&#xff1a; 1.“CTRLR”打开运行窗口&#xff0c;输入regedit点击确定打开注册表编辑器 2.找到HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\MySQL目录 3.修改ImagePath路径为mysqlld.exe路径&#xff0c;重启服务即可