以二维数组模拟一个矩形迷宫,利用随机广度优先生成不含有回路的迷宫中任意两点的正确路径。
与随机深度优先算法类似,随机广度优先算法也是基于图论的算法,可以用来生成迷宫。该算法与广度优先搜索算法类似,不同之处在于在遍历相邻节点时是随机选择一个节点进行遍历,而不是按照固定顺序进行遍历。同样由于随机选择的特性,该算法生成的迷宫具有随机性和不可预测性,同时保证没有循环路径。
代码:
#include <iostream>
#include <queue>
#include <vector>
using namespace std;
/*定义方向,这段代码定义了几个常量,分别表示四个方向和方向的数量。具体来说,RIGHT等于0,代表向右;DOWN等于1,
代表向下;LEFT等于2,代表向左;UP等于3,代表向上。而WAY_NUM等于4,表示总共有四个方向。这些常量可以在程序中方便地使用,避免出现数字硬编码,增加代码的可读性和可维护性。 */
const int RIGHT = 0;
const int DOWN = 1;
const int LEFT = 2;
const int UP = 3;
const int WAY_NUM = 4;
/*定义行走状态,这段代码定义了两个常量,分别表示“是”和“否”的状态。具体来说,YES等于4,代表“是”;NO等于5,
代表"否"。这些常量可以在程序中方便地使用,避免出现数字硬编码,增加代码的可读性和可维护性。*/
const int YES = 4;
const int NO = 5;
/*迷宫
这段代码定义了一个迷宫类Maze,并实现了该类的两个成员函数——initNode和setNodeState。
在Maze的构造函数中,首先根据传入的行和列数开辟_pMaze的空间。其中_pMaze是一个二维数组,存储了迷宫中每个节点的信息,包括节点的横坐标、纵坐标、节点的类型(墙或路)以及节点到周围四个方向的通行状态等。
initNode函数用于初始化迷宫中某一节点的信息。其中x和y表示该节点的横坐标和纵坐标,val表示该节点是墙还是路。根据节点的坐标和类型,初始化该节点到周围四个方向的通行状态。
setNodeState函数用于根据迷宫中每个节点的信息,设置该节点到周围四个方向的通行状态。当该方向可以通行时,将状态设置为YES;否则,将状态设置为NO。通行状态的设置可以根据迷宫中每个节点的类型(1表示墙,0表示路)进行判断。*/
class Maze{
public:
Maze(int row, int col):_row(row), _col(col)
{
_pMaze = new Node*[_row];
for (int i = 0; i < _row; ++i)
{
_pMaze[i] = new Node[_col];
}
_pPath.resize(_row * _col); //开辟空间
}
void initNode(int x, int y, int val)
{
_pMaze[x][y]._x = x;
_pMaze[x][y]._y = y;
_pMaze[x][y]._val = val;
for (int i = 0; i < WAY_NUM; ++i)
{
_pMaze[x][y]._state[i] = NO;
}
}
void setNodeState()
{
for (int i = 0; i < _row; ++i)
{
for (int j = 0; j < _col; ++j)
{
if (_pMaze[i][j]._val == 1)
{
continue;
}
if (j < _col - 1 && _pMaze[i][j + 1]._val == 0)
{
_pMaze[i][j]._state[RIGHT] = YES;
}
if (i < _row - 1 && _pMaze[i + 1][j]._val == 0)
{
_pMaze[i][j]._state[DOWN] = YES;
}
if (j > 0 && _pMaze[i][j - 1]._val == 0)
{
_pMaze[i][j]._state[LEFT] = YES;
}
if (i > 0 && _pMaze[i - 1][j]._val == 0)
{
_pMaze[i][j]._state[UP] = YES;
}
}
}
}
//广度优先遍历搜索路径
/*这段代码实现了搜索迷宫路径的功能,其中用到了广度优先搜索算法。
首先判断起点(0, 0)是否是墙,如果是墙则无法走通,直接返回。
使用队列(_queue)存储处理过程中的每一个节点,初始时将起点(0, 0)压入队列。当队列不为空时,循环对队列中的每一个节点进行处理。
对于每一个节点,分别向其四个方向进行搜索。找到一个可以通行的方向时,将该方向的通行状态设为NO,并将下一个节点加入队列。
同时,记录路径信息。对于正在处理的节点,如果它的右侧节点可以通过,则记录右侧节点从当前节点移动而来,并将右侧节点加入队列。路径信息保存在_pPath数组中,每个节点对应数组中的一个位置,位置计算方法为:x * 列数 + y。
在每次加入队列的过程中,都进行判断,如果已经找到目标节点,则返回。
对于队列中的每个节点,处理完成后需要将其出队列(_queue.pop())。
最终,如果找到通往终点的路径,则路径信息存储在_pPath中,否则_pPath数组为空。*/
void searchMazePath()
{
if (_pMaze[0][0]._val == 1)
{
return;
}
_queue.push(_pMaze[0][0]);
while (!_queue.empty()){
Node front = _queue.front();
int x = front._x;
int y = front._y;
// 右方向
if (_pMaze[x][y]._state[RIGHT] == YES){
_pMaze[x][y]._state[RIGHT] = NO;
_pMaze[x][y + 1]._state[LEFT] = NO;
// 记录下一个结点是从哪个节点走过来的
_pPath[x*_col + y + 1] = _pMaze[x][y];
_queue.push(_pMaze[x][y + 1]);
if (check(_pMaze[x][y + 1]))
return;
}
// 下方向
if (_pMaze[x][y]._state[DOWN] == YES){
_pMaze[x][y]._state[DOWN] = NO;
_pMaze[x + 1][y]._state[UP] = NO;
_pPath[(x + 1)*_col + y] = _pMaze[x][y];
_queue.push(_pMaze[x + 1][y]);
if (check(_pMaze[x + 1][y]))
return;
}
// 左方向
if (_pMaze[x][y]._state[LEFT] == YES){
_pMaze[x][y]._state[LEFT] = NO;
_pMaze[x][y - 1]._state[RIGHT] = NO;
_pPath[x*_col + y - 1] = _pMaze[x][y];
_queue.push(_pMaze[x][y - 1]);
if (check(_pMaze[x][y - 1]))
return;
}
// 上方向
if (_pMaze[x][y]._state[UP] == YES){
_pMaze[x][y]._state[UP] = NO;
_pMaze[x - 1][y]._state[DOWN] = NO;
_pPath[(x - 1)*_col + y] = _pMaze[x][y];
_queue.push(_pMaze[x - 1][y]);
if (check(_pMaze[x - 1][y]))
return;
}
// 出队列
_queue.pop();
}
}
/*这段代码实现了展示迷宫路径的功能,主要是根据搜索得到的路径信息(_pPath)回溯查找路径上的节点,并将其标记为*。最后输出标记过的迷宫以展示路径。
如果队列为空,说明不存在一条通往终点的路径,此时输出相应的提示信息。
否则,从右下角的节点开始回溯寻找路径节点。在回溯过程中,先将(x, y)节点标记为*,然后根据_pPath存储的路径信息找到路径上的上一个节点。重复执行该过程,一直回溯到起点(0, 0),标记完整条路径上的节点,最终输出标记过的迷宫。
在输出过程中,如果某个节点被标记,输出一个*;否则,输出该节点的类型值(val)。*/
void showMazePath(){
if (_queue.empty()){
cout << "不存在一条迷宫路径!" << endl;
}
else{
// 回溯寻找迷宫路径节点
int x = _row - 1;
int y = _col - 1; //x,y现在处于右下角出口
for (;;){
_pMaze[x][y]._val = '*';
if (x == 0 && y == 0)
break;
Node node = _pPath[x*_col + y]; //取出_pPath存的路径的上一结点
x = node._x;
y = node._y;
}
for (int i = 0; i < _row; ++i){
for (int j = 0; j < _col; ++j){
if (_pMaze[i][j]._val == '*'){
cout << "* ";
}
else{
cout << _pMaze[i][j]._val << " ";
}
}
cout << endl;
}
}
}
/*这段代码给出了Maze类的完整定义,包括私有成员变量_pMaze、_row、_col、_queue、_pPath以及私有嵌套结构体Node。
_pMaze是一个指向Node类型的指针,其大小为_row * _col。每个Node表示迷宫中的一个节点,保存该节点的横纵坐标、节点的类型(墙或路)以及节点到周围四个方向的通行状态。_row和_col分别表示迷宫的行数和列数。
_queue是一个STL队列,存储遍历过程中的每一个节点。_pPath是一个STL向量,用于存储广度优先遍历时,节点的行走信息。
私有嵌套结构体Node有4个成员变量:_x、_y、_val和_state。_x和_y表示该节点的横纵坐标,_val表示该节点的类型(墙或路),_state是一个长度为4的整型数组,用于记录该节点到周围四个方向的通行状态。
check函数用于判断给定节点是否为迷宫的右下角出口节点,如果是,返回true,否则返回false。*/
private:
// 定义迷宫节点路径信息
struct Node
{
int _x;
int _y;
int _val; // 节点的值
int _state[WAY_NUM]; // 记录节点四个方向的状态
};
// 检查是否是右下角的迷宫出口节点
bool check(Node &node)
{
return node._x == _row - 1 && node._y == _col - 1;
}
Node **_pMaze;
int _row;
int _col;
queue<Node> _queue; // 广度遍历依赖的队列结构
vector<Node> _pPath; // 记录广度优先遍历时,节点的行走信息
};
/*
这是应用上述迷宫类Maze的主函数。
首先提示用户输入迷宫的行列数,并创建迷宫对象maze。然后提示用户输入迷宫的路径信息,通过调用Maze类的initNode函数对每个节点进行初始化。接着调用Maze类的setNodeState函数,设置所有节点到周围四个方向的通行状态。
使用Maze类的searchMazePath函数搜索迷宫中是否存在通往终点的路径。最后使用Maze类的showMazePath函数输出标记过的迷宫路径。
完整代码如下:*/
int main(){
cout << "请输入迷宫的行列数(例如:10 10):";
int row, col, data;
cin >> row >> col;
Maze maze(row, col); // 创建迷宫对象
cout << "请输入迷宫的路径信息(0表示可以走,1表示不能走):" << endl;
for (int i = 0; i < row; ++i)
{
for (int j = 0; j < col; ++j)
{
cin >> data;
// 可以初始化迷宫节点的基本信息
maze.initNode(i, j, data);
}
}
// 开始设置所有节点的四个方向的状态
maze.setNodeState();
// 开始从左上角搜索迷宫的路径信息了
maze.searchMazePath();
// 打印迷宫路径搜索的结果
cout << "路径如下:" << endl;
maze.showMazePath();
return 0;
}
运行结果: