一个创建迷宫并寻找通道的小程序

在学习递归时,在迷宫中寻找路径的问题经常会被当成例子,类似的还有汉诺塔。所有的递归程序都可以改用栈的方式求解,事实上系统执行递归函数也不过就是频繁的入栈和出栈,当然,也可能导致栈溢出的问题。直接借助“栈”这种数据结构来实现程序其实会更好点,可以动态分配内存,只是递归程序看上去更简洁精致,不太费脑,所以在初步构想解决方法时,还是更容易用递归的思维求解。

递归有两个重要步骤: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所得到的。


### 回答1: 自动寻找迷宫最短路径的程序可以用广度优先搜索 (BFS) 或者 Dijkstra 算法来实现。 广度优先搜索 (BFS) 是一种图论算法,它先搜索距离起点最近的点,再搜索距离起点次近的点,直到找到终点。 而 Dijkstra 算法是最短路径算法,它对每个点维护一个最短距离,然后从起点开始不断更新每个点的最短距离,直到找到终点。 两者都需要一个队列来存储当前搜索的点,并需要一个二维数组来存储迷宫地图。 ### 回答2: 自动寻找迷宫最短路径的程序可以通过使用广度优先搜索(BFS)算法来实现。下面我就以 Python 语言为例,给出一个简单的实现步骤。 首先,我们需要创建一个迷宫的矩阵,用0表示可通行的路,用1表示墙壁或者障碍物。例如,一个迷宫可以表示为: maze = [ [0, 1, 0, 0, 0], [0, 1, 0, 1, 0], [0, 0, 0, 1, 0], [0, 1, 0, 0, 0], [0, 0, 0, 1, 0] ] 接下来,我们需要定义一个函数来寻找最短路径。在这个函数中,我们首先需要定义一个队列,用来存储待搜索的节点。然后,我们需要定义一个二维数组来存储每个节点到起点的距离,初始值为无穷大。最后,我们定义一个二维数组来存储每个节点的前驱节点,表示最短路径是从哪个节点到达的。 然后,我们将起点加入队列,并将起点到起点的距离设为0。接着,我们开始一个循环,直到队列为空为止。在循环中,我们从队列中取出一个节点,并对它的上、下、左、右四个方向进行尝试。如果某个方向上的节点是可通行的,并且它还没有被访问过,我们将它加入队列,并更新它到起点的距离和前驱节点。 当队列为空时,我们就可以通过前驱节点数组来生成最短路径了。我们可以从终点开始,通过不断寻找前驱节点,一直追溯到起点,便得到了最短路径。 总结起来,这个自动寻找迷宫最短路径的程序主要包括创建迷宫矩阵、定义寻找最短路径的函数、使用广度优先搜索算法进行搜索、更新节点距离和前驱节点、以及生成最短路径等步骤。通过以上方法,我们就能够自动寻找迷宫最短路径了。 ### 回答3: 迷宫最短路径问题是一个经典的问题,可以用深度优先搜索(DFS)或广度优先搜索(BFS)解决。下面我将用BFS算法编写一个自动寻找迷宫最短路径的程序。 首先,我们将迷宫模拟成一个二维数组,其中0表示可以通过的通道,1表示墙壁或障碍物。我们还需要定义一个二维数组visited,用于记录访问过的位置。 接下来,我们从起点开始进行BFS算法。我们创建一个队列,并将起点加入队列中。然后,从队列中弹出一个位置,并检查它的邻居位置,如果邻居位置是合法的且未访问过,我们将其加入队列,并标记为已访问。我们还需要一个数组prev,用于记录访问路径。 直到遍历到终点位置或队列为空,我们都继续以上步骤。遍历结束时,我们可以根据prev数组回溯出最短路径。从终点开始,我们将prev中的位置添加到路径中,直到回溯到起点位置。 以下是一个简单的伪代码实现: ``` function findShortestPath(maze, start, end): rows := maze.rows cols := maze.columns visited := create a 2D array with size rows * cols and initialize with False prev := create a 2D array with size rows * cols and initialize with None queue := empty queue directions := [(0, 1), (0, -1), (1, 0), (-1, 0)] enqueue(queue, start) visited[start[0]][start[1]] = True while queue is not empty: current := dequeue(queue) if current == end: break for direction in directions: next := current + direction if next is a valid position in the maze and maze[next[0]][next[1]] == 0 and not visited[next[0]][next[1]]: enqueue(queue, next) visited[next[0]][next[1]] = True prev[next[0]][next[1]] = current path := empty list current := end while current is not None: path.append(current) current := prev[current[0]][current[1]] return path ``` 通过以上算法,我们可以找到起点到终点的最短路径。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值