一个创建迷宫并寻找通道的小程序
2017-07-08 18:40
169 查看
在学习递归时,在迷宫中寻找路径的问题经常会被当成例子,类似的还有汉诺塔。所有的递归程序都可以改用栈的方式求解,事实上系统执行递归函数也不过就是频繁的入栈和出栈,当然,也可能导致栈溢出的问题。直接借助“栈”这种数据结构来实现程序其实会更好点,可以动态分配内存,只是递归程序看上去更简洁精致,不太费脑,所以在初步构想解决方法时,还是更容易用递归的思维求解。
递归有两个重要步骤:1.找出递推关系式,2.找到递归终止条件。譬如对于迷宫寻路径而言,终止条件显然就是到达出口(返回true),递推关系则是分别尝试四个方向,若不是障碍物且未曾踏足的,就前进一步,并标记为已经过,然后将新的坐标作为参数重复调用自身并检查返回值,为true时则标记当前坐标属于有效路径,并继续返回true。可以说定义递归函数的另一件重要事情就是确定需要哪些参数。下面是一个迷宫的定义:
用一个长度为HEIGHT宽度为WIDTH的矩阵代表迷宫,矩阵的四个边(x==0 || x==WIDTH || y==0 || y==HEIGHT)均为BLOCKED,在其上两个开口代表入口和出口,设置为SELECTED,那么就可以开始寻找路径了。同时定义了上下左右四个方向,写出这个递归函数并不难:
现在的问题是如何创建一个迷宫,听起来似乎要麻烦许多,然后一旦抓住关键点,就显得不是很困难了。创建迷宫,可以想象成在一个除了入口和出口以外满是障碍物的矩阵中挖出一条路径,并随机制造许多不会融合到主路径的岔路。假设迷宫里路径宽度只能为1(不好意思没有大广场),那么路与路之间必然隔着墙,为了让路径更加眼花缭乱,每一寸土地都要利用起来,所以墙的厚度也只能是1。这下看到要点了吧,在挖路的时候每次向前挖开两格(寻路仅仅是前进一格),当前方两格处到达边墙或者已经被挖通过,就换个方向挖,这下顺便还避免了环路。在前面迷宫矩阵的定义中,规定了宽度和长度都必须是奇数,为什么呢?首先矩阵的边长至少也得是3,毕竟四面的墙就需要两格厚度,还可以规定迷宫的入口和出口不相连,且四周的墙上只允许有入口和出口,那么(0,
0)这种坐标点就不能是入口或出口了,不信你自己画画看。此时可以假设入口坐标是(0, 1),出口坐标是(HEIGHT - 1, WIDTH - 2),当然选取(0, 1)和(HEIGHT-2, WIDTH-1)也是可以的,两种情况是对应的,这里以前者为例,一个最小的迷宫看上去像下面这样:
█ █
█ █
█ █
如果矩阵的边长为4,结合上面定义的出入口坐标点和挖掘方式,必然是挖不通的(可以画个图试试),当边长为奇数就没问题了。起始点可以选在和入口或出口相邻的位置,如果直接定在出入口,按照挖路一次前进两格的方式,通路必定无法和边墙相邻,也就是说会出现两格厚度的边墙,有点浪费空间了。下面是创建迷宫的递归函数,里面用到了随机函数,在上述寻路的过程中,是按照右下左上的方向依次尝试,而在创建路径时,可以增加一点随机性,否则容易挖出一条长长的直道。这个函数并没有定义明确的终止条件,不同于寻找路径,当到达出口就不再做无用功,创建迷宫的函数会一直运行到没有地方可以挖掘了为止。当然,如果你不想挖太多的通道,让这个函数早点儿结束,也有很多办法,比如在下面for循环的终止条件可以将i<4改成i<3。
接下来是输出函数,用来打印我们创建的迷宫和寻找到的连通路径,必须注意这里用了非ASCII的文本符号,在英文操作系统上可能会出现一大堆问号。
按照上面的假设,迷宫边长为奇数,入口坐标是(0, 1)且出口坐标是(HEIGHT - 1, WIDTH - 2),或者分别是(0, 1)和(HEIGHT-2, WIDTH-1),其实也等于说是第二步坐标必为(1, 1),倒数第二步必为(HEIGHT - 2, WIDTH - 2),这两个点都可以作为创建迷宫的起始点,如果想随机选另外一个起始点,那么该点按照一次两格朝各个方向前进,必须能到达(1, 1)和(HEIGHT - 2, WIDTH - 2),结合随机函数可以得到这个起始点为(rand() % ((HEIGHT
- 1) / 2)) * 2 + 1, (rand() % ((WIDTH - 1) / 2)) * 2 + 1)。接下来是主函数:
运行整个C程序可以得到完美的迷宫,下面的运行结果是设置了宽度为33、高度为23所得到的。
递归有两个重要步骤:1.找出递推关系式,2.找到递归终止条件。譬如对于迷宫寻路径而言,终止条件显然就是到达出口(返回true),递推关系则是分别尝试四个方向,若不是障碍物且未曾踏足的,就前进一步,并标记为已经过,然后将新的坐标作为参数重复调用自身并检查返回值,为true时则标记当前坐标属于有效路径,并继续返回true。可以说定义递归函数的另一件重要事情就是确定需要哪些参数。下面是一个迷宫的定义:
#include <stdio.h> #include <stdlib.h> #include <time.h> // height & width must be both odd numbers and not less than 3 #define HEIGHT 23 #define WIDTH 33 // 2-dimensional array that represents the maze int matrix[HEIGHT][WIDTH]; // 4 directions: {right, down, left, up} const int DIRECTIONS[4][2] = {{0,1}, {1,0}, {0,-1}, {-1,0}}; // status of a cell in the matrix enum status { BLOCKED, UNBLOCKED, SELECTED, VISITED };
用一个长度为HEIGHT宽度为WIDTH的矩阵代表迷宫,矩阵的四个边(x==0 || x==WIDTH || y==0 || y==HEIGHT)均为BLOCKED,在其上两个开口代表入口和出口,设置为SELECTED,那么就可以开始寻找路径了。同时定义了上下左右四个方向,写出这个递归函数并不难:
bool searchPath(int start_row, int start_col, int end_row, int end_col) { for (int d = 0; d != 4; ++d) { int next_row = start_row + DIRECTIONS[d][0], next_col = start_col + DIRECTIONS[d][1]; if ((next_row == end_row) && (next_col == end_col)) { return true; } if (matrix[next_row][next_col] == UNBLOCKED) { matrix[next_row][next_col] = VISITED; if (searchPath(next_row, next_col, end_row, end_col)) { matrix[next_row][next_col] = SELECTED; return true; } } } return false; }
现在的问题是如何创建一个迷宫,听起来似乎要麻烦许多,然后一旦抓住关键点,就显得不是很困难了。创建迷宫,可以想象成在一个除了入口和出口以外满是障碍物的矩阵中挖出一条路径,并随机制造许多不会融合到主路径的岔路。假设迷宫里路径宽度只能为1(不好意思没有大广场),那么路与路之间必然隔着墙,为了让路径更加眼花缭乱,每一寸土地都要利用起来,所以墙的厚度也只能是1。这下看到要点了吧,在挖路的时候每次向前挖开两格(寻路仅仅是前进一格),当前方两格处到达边墙或者已经被挖通过,就换个方向挖,这下顺便还避免了环路。在前面迷宫矩阵的定义中,规定了宽度和长度都必须是奇数,为什么呢?首先矩阵的边长至少也得是3,毕竟四面的墙就需要两格厚度,还可以规定迷宫的入口和出口不相连,且四周的墙上只允许有入口和出口,那么(0,
0)这种坐标点就不能是入口或出口了,不信你自己画画看。此时可以假设入口坐标是(0, 1),出口坐标是(HEIGHT - 1, WIDTH - 2),当然选取(0, 1)和(HEIGHT-2, WIDTH-1)也是可以的,两种情况是对应的,这里以前者为例,一个最小的迷宫看上去像下面这样:
█ █
█ █
█ █
如果矩阵的边长为4,结合上面定义的出入口坐标点和挖掘方式,必然是挖不通的(可以画个图试试),当边长为奇数就没问题了。起始点可以选在和入口或出口相邻的位置,如果直接定在出入口,按照挖路一次前进两格的方式,通路必定无法和边墙相邻,也就是说会出现两格厚度的边墙,有点浪费空间了。下面是创建迷宫的递归函数,里面用到了随机函数,在上述寻路的过程中,是按照右下左上的方向依次尝试,而在创建路径时,可以增加一点随机性,否则容易挖出一条长长的直道。这个函数并没有定义明确的终止条件,不同于寻找路径,当到达出口就不再做无用功,创建迷宫的函数会一直运行到没有地方可以挖掘了为止。当然,如果你不想挖太多的通道,让这个函数早点儿结束,也有很多办法,比如在下面for循环的终止条件可以将i<4改成i<3。
void createMaze(int row, int col) { matrix[row][col] = UNBLOCKED; int direction = rand() % 4; // randomly choose a direction to try int clockwise = rand() % 2 ? 1 : 3; // clockwise or anti-clockwise for (int i = 0, d = direction; i < 4; ++i, d = (d + clockwise) % 4) { int next_row = row + DIRECTIONS[d][0] * 2, next_col = col + DIRECTIONS[d][1] * 2; if ((next_row > 0) && (next_row < HEIGHT - 1) && (next_col > 0) && (next_col < WIDTH - 1) && (matrix[next_row][next_col] == BLOCKED)) { matrix[row + DIRECTIONS[d][0]][col + DIRECTIONS[d][1]] = UNBLOCKED; createMaze(next_row, next_col); } } }
接下来是输出函数,用来打印我们创建的迷宫和寻找到的连通路径,必须注意这里用了非ASCII的文本符号,在英文操作系统上可能会出现一大堆问号。
void printMatrix(void) { for (int row = 0; row < HEIGHT; ++row) { for (int col = 0; col < WIDTH; ++col) { if (matrix[row][col] == BLOCKED) printf("█"); else if (matrix[row][col] == UNBLOCKED) printf(" "); else if (matrix[row][col] == SELECTED) printf("·"); else printf(" "); } printf("\n"); } }
按照上面的假设,迷宫边长为奇数,入口坐标是(0, 1)且出口坐标是(HEIGHT - 1, WIDTH - 2),或者分别是(0, 1)和(HEIGHT-2, WIDTH-1),其实也等于说是第二步坐标必为(1, 1),倒数第二步必为(HEIGHT - 2, WIDTH - 2),这两个点都可以作为创建迷宫的起始点,如果想随机选另外一个起始点,那么该点按照一次两格朝各个方向前进,必须能到达(1, 1)和(HEIGHT - 2, WIDTH - 2),结合随机函数可以得到这个起始点为(rand() % ((HEIGHT
- 1) / 2)) * 2 + 1, (rand() % ((WIDTH - 1) / 2)) * 2 + 1)。接下来是主函数:
int main(void) { int start_row = 0, start_col = 1; int end_row = HEIGHT - 1, end_col = WIDTH - 2; srand((unsigned)time(0)); createMaze((rand() % ((HEIGHT - 1) / 2)) * 2 + 1, (rand() % ((WIDTH - 1) / 2)) * 2 + 1); matrix[start_row][start_col] = matrix[end_row][end_col] = SELECTED; searchPath(start_row, start_col, end_row, end_col); printMatrix(); return 0; }
运行整个C程序可以得到完美的迷宫,下面的运行结果是设置了宽度为33、高度为23所得到的。
相关文章推荐
- 用EclipseME0.5.5创建一个简单的J2ME程序
- 用EclipseME0.5.5创建一个简单的J2ME程序
- [原创]我的WCF之旅(1):创建一个简单的WCF程序
- 创建一个完整Win32程序
- 创建一个完整Win32程序
- 使用timer控件创建一个简单的报警程序
- 实战 HTTP 处理程序(HTTP Handler) (1) -- 创建一个最简单的 HTTP Handler
- 求助:安装程序无法创建一个DCOM用户帐号来注册.....\valec.exe
- 本文利用C#和.NET提供的类来轻松创建一个抓取网页内容源代码的程序
- 一个简单的迷宫访问程序。
- 如何用Delphi创建一个能随意拨号,并连接到对方的“猫”上的程序,谢谢(小弟才浅,请指教)
- [导入]创建一个没有窗口的程序
- [导入]创建一个没有窗口的程序
- 我的WCF之旅(1):创建一个简单的WCF程序
- 创建ASP.NET程序时候发生错误"HTTP/1.0 500 Server Error"的一个解决办法
- 创建并控制一个asp.net程序!(for delphi.net )
- 创建一个没有窗口的程序
- [导入]创建一个没有窗口的程序
- 不使用ATL向导,创建一个简单的ATL对话框程序.
- 实战 HTTP 处理程序(HTTP Handler) (1) -- 创建一个最简单的 HTTP Handler