基于C语言实现迷宫

说到迷宫,首先一定要有一个迷宫的地图,让后还要规定寻找迷宫出口的一些规则
关于迷宫的地图,在这里我创建了一个二维数组来表示迷宫地图,并且约定
(1)位置0为墙(即不能走的地方),位置1为路
(2)走到边界点的位置,就为出路
(3)按照上、右、下、左(顺时针)的顺序来探测下一步出路,若下一步路,就往下走

(4)若当前点可以走,就将当前点标记为2,为了区分位置1,防止不知道自己走了重复的路

首先先来创建迷宫地图并打印迷宫地图
在这里通过使用二维数组的方法来表示迷宫的地图,设计出的地图如图所示


  4 #define Max_ROW 6
  5 #define Max_COL 6
  6 typedef struct Maze
  7 {
  8     int map[Max_ROW][Max_COL];
  9 }Maze;
 10 
 11 //初始化
 12 void MazeInit(Maze* maze)
 13 {
 14     int map[Max_ROW][Max_COL]={{0,1,0,0,0,0},
 15                                 {0,1,1,1,0,0},
 16                                 {0,1,0,1,0,0},
 17                                 {0,1,0,1,0,0},                                                
 18                                 {0,1,1,0,0,0},
 19                                 {0,0,1,0,0,0}};
 20     size_t i=0;
 21     for(;i<Max_ROW;++i)
 22     {
 23         size_t j=0;
 24         for(;j<Max_COL;++j)
 25         {
 26             maze->map[i][j]=map[i][j];
 27         }
 28     }

 32 //打印迷宫
 33 void MazePrint(Maze* maze)
 34 {
 35     size_t i=0;
 36     for(;i<Max_ROW;++i)
 37     {
 38         size_t j=0;
 39         for(;j<Max_COL;++j)
 40         {
 41             printf("%2d ",maze->map[i][j]);
 42         }
 43         printf("\n");
 44     }
 45 }
 46 


方法一:使用递归的方法来解决迷宫问题,为迷宫找出路
使用递归的方法来解决迷宫问题本质上还是使用栈,只不过这个栈是由操作系统提供的,即函数调用栈

创建函数GetPath,传入地图,和入口点,然后通过_GetPath函数来辅助完成递归的操作

134 void GetPath(Maze* maze,Point entry)
135 {
136     if(maze == NULL)
137     {
138         //非法操作
139         return;
140     }
141     //用_GetPath来辅助完成递归
142     _GetPath(maze,entry,entry);//第一个entry为当前走到的点,第二个entry为真正的入口点
143 }
144 

在使用递归的方法来实现迷宫问题时,首先要清楚,当每个坐标入栈的时候,都会调用一次函数,当出栈的时候,函数调用结束,而每当走到下一个点的时候又会递归地调用下一个函数

下面是实现 _GetPath函数,需要传入地图和当前到的点和迷宫入口点
1.首先要判断当前点是否能落脚,即当前点是否为1(在刚开始的时候,就已经约定0为墙,1为路)
    需要引用一个函数CanStay来判断是否能落脚
2.若能落脚,就将当前的位置标记为2,表示当前点已走
    需要引用一个函数Mark来将走过的路来标记成2
3.若当前点为出口(即地图边缘的点),则说明寻找路径结束,则此时打印一条语句“找到出路”结束探测
    需要引用一个函数IsExit来判断当前点是否为出口点
4.若不是出口位置,则需要按照上、右、下、走(顺时针)的顺序来探测当前点的四个方向的点是否能够落脚,此时就是要递归地调用函数 _GetPath函数自身,每次递归的时候递归的点都是下次要走的点,而此点是否能走,就要交给函数来判断
注:递归的调用函数就是将下一个点入栈的操作, 当每个坐标入栈的时候,都会调用一次函数,当出栈的时候,函数调用结束,而每当走到下一个点的时候又会递归地调用下一个函数

详细过程见程序

 
 
 95 void _GetPath(Maze* maze,Point cur,Point entry)
 96 {
 97 
 98     printf("cur:%d,%d\n",cur.row,cur.col);
 99     //1.判定当前点是否能落脚
100     if(!CanStay(maze,cur))
101     {
102         //不能落脚
103         return;
104     }
105     //2.若能落脚,就给当前位置坐标2,2表示已经走过的路
106     Mark(maze,cur);
107     //3.若当前点是出口,则说明找到了一条出路,探测结束
108     if(IsExit(maze,cur,entry))
109     {
110         //找到了出路
111         printf("找到了一条路径\n");
112         return;
113     }
114     //4.若当前点不是出口,则按顺时针探测四个相邻的点,递归地调用函数自身,递归时更新cur
115     //(每次递归的时候,点都是下一次要走的点,这个点究竟能不能走,交给递归函数来判断)
116     Point up=cur;                                                                            
117     up.row-=1;//上方的点
118     _GetPath(maze,up,entry);
119 
120     Point right=cur;
121     right.col+=1;//右
122     _GetPath(maze,right,entry);
123 
124     Point down=cur;
125     down.row+=1;//下
126     _GetPath(maze,down,entry);
127 
128     Point left=cur;
129     left.col-=1;//左
130     _GetPath(maze,left,entry);
131     
132     return;
133 }
134 

下面是实现 CanStay函数,来判断当前点是否能落脚的函数

函数需要引入地图和要判断的点pt,有两种情况,一种为在迷宫外部,一种为在迷宫内部
(1)若pt点在迷宫地图的外部,则一定不能落脚
(2)若pt点在迷宫内部,若pt点的值为1,则说明能落脚,否则(pt的值为0或者为2)就不能落脚
 54 //判断点是否能落脚
 55 int CanStay(Maze* maze,Point pt)
 56 {
 57     //1.若pt点在迷宫圈外,则不能落脚
 58     if(pt.row < 0 || pt.row >= Max_ROW || pt.col < 0 || pt.col >= Max_COL)
 59     {
 60         return 0;
 61     }
 62     //2.若pt地图内部,若位置为1,则能落脚,若位置为0或者为2,则不能落脚
 63     int value=maze->map[pt.row][pt.col];
 64     if(value == 1)
 65     {
 66         return 1;
 67     }
 68     return 0;
 69 }
 70 

下面是实现 Mark函数,来将走过的点标记为2
 71 //将走过的点做标记2
 72 void Mark(Maze* maze,Point cur)
 73 {
 74     maze->map[cur.row][cur.col]=2;
 75 }

下面是实现 IsExit函数,来判断当前点是否为出口点
在刚开始的时候,约定在地图边缘的点都可以为出口点,但是迷宫的入口点同样也是在边缘上,所以,要先来判断当前点是否为入口点,若为入口点,则一定不是出口,然后再判断当前点是否为地图的边缘点
 77 //判断是否为出口
 78 int IsExit(Maze* maze,Point cur,Point entry)
 79 {
 80     (void)maze;
 81     //1.看当前点是否为入口,若为入口,则一定不是出口
 82     if(cur.row == entry.row && cur.col == entry.col)
 83     {
 84         //为入口点
 85         return 0;
 86     }
 87     //2.若点在地图边上,则说明是出口
 88     if(cur.row == 0 || cur.row == Max_ROW-1 || cur.col == 0 || cur.col == Max_COL-1)
 89     {
 90         return 1;
 91     }
 92     return 0;
 93 }

方法二:使用非递归的方式来解决迷宫问题

使用非递归的方式来解决迷宫问题就是使用循环来解决
创建GetPathByLoop函数,传入地图和入口点,由于这个方法需要引用之前栈的一些操作
1.首先要创建,并初始化一个栈,这个栈保存着我们走过的路径
2.要判断当前点是否能落脚,调用方法一中写过的CanStay函数
3.若能落脚,就将入口点标记为2,并将入口点入栈
4.然后进入循环,获取当前的栈顶元素,因为栈顶元素一定能落脚
5.判断这个栈顶元素是否为迷宫的出口,若是出口,则直接返回“找到了一条路径”
6.若不是出口则需要按照上、右、下、走(顺时针)的顺序来探测当前点的四个方向的点是否能够落脚,若能落脚,就将点标记为2,然后入栈,并进入到下一轮循环
7.若四个相邻的点都不能落脚,就出栈(相当于是回溯)
153 void GetPathByLoop(Maze* maze,Point entry)
154 {
155 
156     //1.创建一个栈,并初始化,这个栈保存着我们走过的路径                                     
157    SeqStact stack;
158    SeqStactInit(&stack);
159     //2.判定入口是否合法,若不能则表示参数非法
160     if(!CanStay(maze,entry))
161     {
162         //入口非法
163         return ;
164     }
165     //3.标记入口点,将入口点入栈
166     Mark(maze,entry);
167     SeqStactPush(&stack,entry);
168     while(1)
169     {
170         //4.进入循环,获取当前栈的栈顶元素,栈顶元素一定能落脚
171         Point cur;
172         int ret = SeqStactTop(&stack,&cur);
173         if(ret == 0)
174         {
175             //栈为空
176             return ;
177         }
178         //5.判定这个点是否为出口,若是出口,函数返回
179         if(IsExit(maze,cur,entry))
180         {
181             printf("找到了一条路径\n");
182             return;
183         }
184     
185         //6.按照顺时针方向取相邻点,判断相邻点是否能落脚,若能就标记,并入栈,立刻进入到下一轮循环
186         Point up=cur;
187         up.row-=1;
188         if(CanStay(maze,up))
189         {
190             Mark(maze,up);
191             SeqStactPush(&stack,up);
192             continue;
193         }
194 
195         Point right=cur;
196         right.col+=1;
197         if(CanStay(maze,right))
198         {
199             Mark(maze,right);
200             SeqStactPush(&stack,right);
201             continue;
202         }
203 
204         Point down=cur;                                                                      
205         down.row+=1;
206         if(CanStay(maze,down))
207         {
208             Mark(maze,down);
209             SeqStactPush(&stack,down);
210             continue;
211         }
212 
213         Point left=cur;
214         left.col-=1;
215         if(CanStay(maze,left))
216         {
217             Mark(maze,left);
218             SeqStactPush(&stack,left);
219             continue;
220         }
221         //7.若四个相邻点都不能落脚,就出栈当前点,相当于回溯
222         SeqStactPop(&stack);
223     }
224     return;                                                                                  
225 }

方法三:迷宫有多条出路,寻找迷宫的最短路径
迷宫地图为

先将迷宫的地图初始化
233 void MaseInitShortPath(Maze* maze)
234 {
235     int map[Max_ROW][Max_COL]={{0,1,0,0,0,0},
236                                 {0,1,1,1,0,0},
237                                 {0,1,0,1,1,1},
238                                 {1,1,1,0,0,0},
239                                 {0,0,1,0,0,0},
240                                 {0,0,1,0,0,0}
241                                 };
242     size_t i=0;
243     for(;i<Max_ROW;++i)
244     {
245         size_t j=0;
246         for(;j<Max_COL;++j)                                                                     
247         {
248             maze->map[i][j]=map[i][j];
249         }
250     }
251     return ;
252 }
253 
寻找最短路径的方法,是基于之前写过的栈的一些操作,需要创建两个栈分别为cur_path和shaor_path栈,来存放当前路径和存放当前走过的这些路径的较短的路径
创建GetShortPath,传入地图,和入口点,然后引用函数_GetShortPath来辅助完成寻找寻找最短路径的操作
309 void GetShortPath(Maze* maze,Point entry)
310 {
311     SeqStact cur_path;//保存当前找到对路经
312     SeqStact short_path;//当前最短路径
313     SeqStactInit(&cur_path);
314     SeqStactInit(&short_path);
315     _GetShortPath(maze,entry,entry,&cur_path,&short_path);//第一个entry为当前走到的点,第二个entry为真正的入口点
316     //打印棧中对内容
317     SeqStactDebugPrint(&short_path,"最短路径为:\n");
318 }                                                                                               
319 
下面是实现_GetShortPath函数,需要引入当前位置的点和入口点和当前路径和当前的较短路径
1.首先要使用之前的函数CanStay,来判断当前点是否能够落脚
2.若能落脚,就使用Mark函数将该点标记为2,表示该点不能再走了,然后将该点入栈到当前路径cur_path的栈中
3.标记之后,要判断当前点是否为出口
    1)若为出口,就说明找到了一条出路,并将当前路径cur_path与当前的最短路径short_path进行比较,若cur_path的长度要小于当前最小路径short_path的长度,或者当前最小路径short_path为空,则将用SeqStackAssgin函数将cur_path栈中的内容赋给short_path栈,同时还要回溯,来寻找下一条路径
        2)若 cur_path的长度要大于等于当前最小路径short_path的长度,就进行回溯,来寻找其他路径,在回溯之前要将当前栈cur_path栈的栈顶元素,出栈,因为此时栈中存放的路径不是最小路径,就没有用处了,表示这条路就没有用处了
4.若当前点不是出口,就需要按照上、右、下、走(顺时针)的顺序来探测当前点的四个方向的点是否能够落脚,此时就要递归地调用_GetShortPath函数自身,每次递归的时候 递归的点都是下次要走的点,而此点是否能走,就要交给函数来判断
5.若四个方向都探测过了,并且都不能落脚,就进行出栈

详细见程序

254 void _GetShortPath(Maze* maze,Point cur,Point entry,SeqStact* cur_path,SeqStact* short_path)
255 {
256 
257     printf("cur:%d,%d\n",cur.row,cur.col);
258     //1.判断当前点是否能落脚
259     if(!CanStay(maze,cur))
260     {
261         //在地图之外,不能落脚
262         return ;
263     }
264     //2.若能落脚,将当前点进行标记,并插入到当前找到对路径cur_path中
265     Mark(maze,cur);
266     SeqStactPush(cur_path,cur);
267     //3.若标记完,判断当前点是否为出口
268     if(IsExit(maze,cur,entry))
269     {
270 
271     //a)若为出口,则找到一条路经,将当前路经cur_path与short_path路径进行比较,
272     //若当前路径cur_path比最短路径short_path短或者当前路径short_path为空,则就用当前路径cur_path来代替最短路径short_path
273     //  同时还要回溯,来继续找下一条路径
274         printf("找到了一条出路\n");
275         if(cur_path->size < short_path->size || short_path->size == 0)
276         {                                                                                       
277             printf("找到了一条较短的出路\n");
278             //将棧对号赋值
279             SeqStactAssgin(cur_path,short_path);
280         }
281 
282     //b)若当前路径cur_path对长度大于等于最短路径short_path的长度,就尝试去找其他路径,进行回溯,
283     //  在回溯前要将cur_path对棧顶元素出棧,因为cur_path保存着当前路路径,若不是这条路经,则表示这条路已经失败
284         SeqStactPop(cur_path);
285         return ;
286     }
287     //4.若当前点不是出口点,则要继续探测其余的四个方向
288     Point up = cur;
289     up.row-=1;
290     _GetShortPath(maze,up,entry,cur_path,short_path);
291     
292     Point right = cur;
293     right.col+=1;
294     _GetShortPath(maze,right,entry,cur_path,short_path);
295 
296     Point down = cur;
297     down.row+=1;
298     _GetShortPath(maze,down,entry,cur_path,short_path);
299 
300     Point left = cur;
301     left.col-=1;
302     _GetShortPath(maze,left,entry,cur_path,short_path);
303     //为了找到其他的路而递归                                                                    
304     //5.若四个方向都递归地探测过了,就出棧,回溯到上一个点
305     SeqStactPop(cur_path);
306     return ;
307 }
下面是实现SeqStackAssgin函数,来将一个栈中的内容赋值到另一个栈中,将from栈中的内容赋值到to栈中
1.首先先要比较两个栈的长度,若from栈中的元素个数小于等于to栈中元素的个数,则直接循环将from中的元素赋值到to栈中,不会出现内存越界的情况
2.若 from栈中的元素个数大于to栈中元素的个数,就要释放to中原有的内容,然后根据from中元素的个数来确定要申请内存空间的大小,再给to栈申请一个足够的内存空间,然后进行赋值操作

118 //将一个棧对值赋值到另一个棧中
119     //由于棧支持动态内存对增长,因此                                                            
120     //1.from中的元素个数<=to中的元素个数,则进行循环赋值
121     //2.若from中的元素个数>to中的元素个数,则必须要保证to中对内存足够赋值,防止内存越界
122 void SeqStactAssgin(SeqStact* from,SeqStact* to)
123 {
124     //为了保证to中的内存能够容纳from中的元素
125     //1.释放to中原有对内容
126     SeqStactDestroy(to);
127     //2.1根据from中对元素个数确定内存申请大小,
128     to->size=from->size;
129     to->capacity=from->capacity;
130     //2.2给to申请一个足够的内存空间
131     to->data=(SeqStactType*)malloc(to->capacity*sizeof(SeqStactType));
132     //3.赋值
133     size_t i=0;
134     for(;i<from->size;++i)
135     {
136         to->data[i]=from->data[i];
137     }
138     return ;
139 }
方法四:针对一个复杂迷宫(可能带环、有多个出口)寻找最短路径
迷宫地图如图所示,其中有环和多个出口

针对带环的迷宫问题,就不能再之前那样,将走过的点标记为2,来记录点了,在处理带环问题的迷宫时,就采用一种将将路线标号的方式,在一条路线上的每个点都标上依次增大的标号,若另一条路走到该点的序号要小于该点原本的序号,就将原本的点的序号覆盖,换上新的序号,继续往下标号,若另一条路要标记 的序号,大于或等于原本的序号,则就说明新的这条路就不是最短路径,此路作废,在寻找其他出路,详细见图和程序

先将迷宫地图初始化
328 void MaseInitShortPathWithCycle(Maze* maze)
329 {
330     int map[Max_ROW][Max_COL]={{0,1,0,0,0,0},
331                                 {0,1,1,1,0,0},
332                                 {0,1,0,1,1,1},
333                                 {1,1,1,1,0,0},
334                                 {0,0,1,0,0,0},
335                                 {0,0,1,0,0,0}
336                                 };
337     size_t i=0;
338     for(;i<Max_ROW;++i)
339     {
340         size_t j=0;
341         for(;j<Max_COL;++j)
342         {
343             maze->map[i][j]=map[i][j];
344         }
345     }
346     return ;
347 }
创建函数GetShortPathWithCycle,传入地图和入口点,找最短路径的方法与方法三着最短路径的方法相同,还是使用两个栈,分别存放着当前路径和最短路径,来实现,该问题与之前方法的区别在于,该问题存在了一个环,而若还按照之前的方法来实现,就将走过的路标记为2,那么走过的路就不能再判断了,那么就会错过最短路径的寻找,
使用_GetShortPathWithCycle函数来辅助完成找最短路径的操作
442 void GetShortPathWithCycle(Maze* maze,Point entry)
443 {
444     SeqStact cur_path;
445     SeqStact short_path;
446     SeqStactInit(&cur_path);
447     SeqStactInit(&short_path);
448     Point pre={(-1,-1)};
449     _GetShortPathWithCycle(maze,entry,pre,entry,&cur_path,&short_path);
450     SeqStactDebugPrint(&short_path,"找到最短的路经\n");                                         
451 
452 }
下面是实现 _GetShortPathWithCycle函数,需要传入地图,当前点的位置,入口点的位置,和存放当前路径的栈和当前最短路径的栈
1.首先要用CanStayWithCycle函数来判断当前点是否能落脚,在这里判断是否能落脚的判断条件与之前的判断方法不同,
之前是只要是走过的路都标记为2,若下一条路走到2号标记点的时候,就走不了了
现在新的判断路是否能走的函数为CanStayWithCycle,具体判断方式见下面的CanStayWithCycle函数的描述即程序
2.若能落脚,就将当前点进行标记(标记方法也与之前不同),然后将当前点入栈到cur_path栈中
3.然后判断当前点是否为出口(出口的判断方法不变),若是出口,则返回一句话“找到一条出口”,然后判断当前路径的长度与当前最短路径的长度谁的路径更短就将较短的路径赋值给shor_path(方法与之前方法三相同),然后要进行回溯,这是为了找下一条路径
4.若当前点不是出口,就要 按照上、右、下、走(顺时针)的顺序来探测当前点的四个方向的点是否能够落脚,此时就要递归地调用_GetShortPathWithCycle函数自身,每次递归的时候 递归的点都是下次要走的点,而此点是否能走,就要交给函数来判断
5.若四个方向都探测过了,就进行出栈操作,回溯
详细见程序
394 void _GetShortPathWithCycle(Maze* maze,Point cur,Point pre,Point entry,SeqStact* cur_path,SeqStact* short_path)
395 {
396 
397     printf("cur:%d,%d\n",cur.row,cur.col);
398     //1.判断当前点是否能落脚(判定规则变了)
399     if(!CanStayWithCycle(maze,cur,pre))
400     {
401         return ;
402     }
403     //2.若能落脚,标记当前点且当前点插入到cur_path中(标记规则也变了)
404     MarkWithCycle(maze,cur,pre);
405     if(IsExit(maze,cur,entry))
406     {
407         printf("找到了一条路径\n");
408         SeqStactPush(cur_path,cur);
409         //3.判定当前点是否为出口(判定规则没变)
410         //若是出口,找到路径,另cur_path与short_path比较
411         if(cur_path->size < short_path->size || short_path->size == 0)
412         {
413             printf("找到了一条较短的出路\n");
414             SeqStactAssgin(cur_path,short_path);
415         }                                                                                       
416         //进行回溯,不管当前找到对路径是否为最短路径,都要进行回溯
417         SeqStactPop(cur_path);
418         return ;
419     }
420     //若当前点不是出口,以当前点为基准点按照上、右、下、左(顺时针)的顺序判定四个方向
421     //若四个方向都递归地探测过了,就出棧,进行回溯
422     Point up = cur;
423     up.row-=1;
424     _GetShortPathWithCycle(maze,up,cur,entry,cur_path,short_path);
425     
426     Point right = cur;
427     right.col+=1;
428     _GetShortPathWithCycle(maze,right,cur,entry,cur_path,short_path);
429 
430     Point down = cur;
431     down.row+=1;
432     _GetShortPathWithCycle(maze,down,cur,entry,cur_path,short_path);
433 
434     Point left = cur;
435     left.col-=1;
436     _GetShortPathWithCycle(maze,left,cur,entry,cur_path,short_path);
437     
438     SeqStactPop(cur_path);
439     return;                                                                                     
440 }
441 
下面是实现CanStayWithCycle函数,来判断该点是否能够落脚,
1.首先要判断该点是否在地图上,和是否为墙(0)
2.若该点为1,则可以直接落脚
3.若当前点标记的值大于要标记的点,则也可以直接落脚
349 int CanStayWithCycle(Maze* maze,Point cur,Point pre)
350 {
351     //1.判断当前点是否在地图上                                                                  
352     if(cur.row<0 || cur.row>=Max_ROW || cur.col<0 || cur.col>=Max_COL)
353     {
354         return 0;
355     }
356     //当前点是不是墙
357     int cur_value=maze->map[cur.row][cur.col];
358     if(cur_value == 0)
359     {
360         return 0;
361     }
362     //3.若当前点为1,表示可以直接落脚
363     if(cur_value == 1)
364     {
365         return 1;
366     }
367     //4.在取出prev_value之前要判断prev点是否为非法点
368     //if(pre.row == -1 && pre.col == -1)
369     //{
370     //若pre为非法点就只有这一种情况
371     //但这种情况又不用考虑,因为若pre为非法点,则cur一定为入口点,
372     //判定入口点是否能落脚与pre无关
373     //}
374     int pre_value=maze->map[pre.row][pre.col];
375     if(cur_value>pre_value+1)
376     {
377         return 1;
378     }
379     return 0;
380 }
下面实现实现MarkWithCycle函数,来将路径上的点进行标记

382 void MarkWithCycle(Maze* maze,Point cur,Point pre)
383 {                                                                                               
384     if(pre.row == -1 && pre.col == -1)
385     {
386         maze->map[cur.row][cur.col]=2;
387         //针对入口点与已被标记的点,此时的pre为非法点,不能用Maze方法进行标记
388         return;
389     }
390     int pre_value=maze->map[pre.row][pre.col];
391     maze->map[cur.row][cur.col]=pre_value+1;
392 }
393 

为了将迷宫的路径从入口进行打印出来,需要对栈进行遍历操作
  6 //次函数用于迷宫测试所用
  7 //通常棧是不允许进行遍历的,单若进行测试或者调试除外
  8 //此时的遍历仅用于测试所用
  9 //之所以写这个函数是为了能够从函数对入口开始打印路线
 10 void SeqStactDebugPrint(SeqStact* stack,const char* msg)
 11 {
 12     printf("[%s]\n",msg);
 13     size_t i=0;
 14     for(;i<stack->size;i++)
 15     {
 16         printf("(%d,%d)\n",stack->data[i].row,stack->data[i].col);
 17     }
 18     printf("\n");
 19 }
 20 


猜你喜欢

转载自blog.csdn.net/l_x_y_hh/article/details/80020972