我们把迷宫先初始化为这样一个矩阵:每一个格子互不相连,如果使用区域的定义的话,每个格子就是一个区域。如果迷宫矩阵大小是m*n,那它在最开始拥有m*n个区域。
(1)随机选择两个相邻的格子,把它们合成一个区域。
如下图所示,当我们把5和6合并时,2、4、5、6、9将变成同一个区域。
(2)在每一次合并后,我们都检查起点S和终点T是否在同一个区域里。如果是,程序结束;如果不是,重复第一步。
使用并查集实现迷宫的原理就是这么简单!
并查集的原理本文并不打算详细讲解,读者可搜索相关文章阅读。
网上的并查集实现一般都是使用一维数组的,本程序为了方便,改成了二维数组,其实质是差不多的。
并查集核心代码如下:
private Position Find(Position x)
{
Position r = x;
while (rooms[r.row, r.col] != r)
r = rooms[r.row, r.col];//找到他的前导结点
Position i = x, j;
while (i != r)//路径压缩算法
{
j = rooms[i.row, i.col];//记录x的前导结点
rooms[i.row, i.col] = r;//将i的前导结点设置为r根节点
i = j;
}
return r;
}
private void Join(Position x, Position y)
{
Position a = Find(x);//x的根节点为a
Position b = Find(y);//y的根节点为b
if (a != b)//如果a,b不是相同的根节点,则说明ab不是连通的
{
rooms[a.row, a.col] = b;//我们将ab相连,将a的前导结点设置为b
}
}
然后就是怎么使用并查集生成迷宫了,代码如下:
private Position[,] rooms;//空白
private List remain_wall1;//竖墙
private List remain_wall2;//横墙
public override void Build()
{
rooms = new Position[room_row, room_col];
remain_wall1 = new List();
remain_wall2 = new List();
for (int i = 0; i < ROW; i++)
{
for (int j = 0; j < COL; j++)
{
if (i == 0 || i == ROW - 1 || j == 0 || j == COL - 1)
{
maze[i, j] = 1;
}
else
{
if (i % 2 == 0)
{
maze[i, j] = 1;
if (j % 2 == 1)
{
remain_wall2.Add(GetPosition(i / 2 - 1, (j - 1) / 2));
}
}
else
{
if (j % 2 == 0)
{
maze[i, j] = 1;
remain_wall1.Add(GetPosition((i - 1) / 2, j / 2 - 1));
}
else
{
maze[i, j] = 0;
int r = (i - 1) / 2;
int c = (j - 1) / 2;
rooms[r, c] = GetPosition(r, c);
}
}
}
}
}
Random rand = new Random();
while (Find(GetPosition(0, 0)) != Find(GetPosition(room_row - 1, room_col - 1)))
{
int index = rand.Next(remain_wall1.Count + remain_wall2.Count);
Position wall = null;
int wall_type = 0;
if (index >= remain_wall1.Count)
{
wall = remain_wall2[index - remain_wall1.Count];
wall_type = 2;
}
else
{
wall = remain_wall1[index];
wall_type = 1;
}
Position room1 = null;
Position room2 = null;
if (wall_type == 1)
{
room1 = GetPosition(wall.row, wall.col);
room2 = GetPosition(wall.row, wall.col + 1);
}
else
{
room1 = GetPosition(wall.row, wall.col);
room2 = GetPosition(wall.row + 1, wall.col);
}
if (Find(room1) == Find(room2))
{
continue;
}
if (wall_type == 1)
{
remain_wall1.RemoveAt(index);
maze[wall.row * 2 + 1, (wall.col + 1) * 2] = 0;
}
else
{
remain_wall2.RemoveAt(index - remain_wall1.Count);
maze[(wall.row + 1) * 2, wall.col * 2 + 1] = 0;
}
Join(room1, room2);
}
}
程序是很好理解的。就是把所有墙都放到一个列表里,每次循环拆掉一堵墙,从列表去掉这堵墙,然后检查起点S和终点T是不是在同一个区域。
使用并查集生成的迷宫如下图所示:
其特点是:不是所有点都在同一个区域,也就是有些格子是怎么走都走不到的。