本文利用matlab,实现了常见的三种迷宫算法:深度优先算法、Prim算法、递归分割算法,和Wilson算法(Loop-erased random walk)。并通过求解路径,对比三种迷宫不同的特点。
迷宫均为标准网格化的2维迷宫,规定迷宫内只能进行上下左右4个方向进行移动。入口和出口均只有一个。
对于一般迷宫而言,我们要找的是一种连接所有通路,且不存在回路和死路的方法。入口和出口的位置反而是次要的,而且可以在迷宫生成之后再定义。
基于马尔科夫链思想的MarkovJunior方法生成迷宫可见:
利用MarkovJunior方法生成迷宫和图形的MATLAB演示[迷宫生成、贪吃蛇、地图生成、图案生成]
深度优先(递归回溯)算法可以表示为:
1.设置一个起点。将起点作为当前迷宫单元,并标记为已访问
2.当还存在未标记的迷宫单元,进行循环
1.如果当前迷宫单元有未被访问过的的相邻的迷宫单元
1.随机选择一个未访问的相邻迷宫单元
2.将当前迷宫单元入栈
3.移除当前迷宫单元与相邻迷宫单元的墙
4.标记相邻迷宫单元已访问,并用它作为当前迷宫单元
2.如果当前迷宫单元不存在未访问的相邻迷宫单元,并且栈不空
1.栈顶的迷宫单元出栈
2.令其成为当前迷宫单元
大概思路是先随机走,如果走不通了,则保存路径。之后逐步后退,直到能继续随机走。最终走完所有格点,则停止程序。
具体过程可以参考下面动图:
matlab的实现的代码为:
% 深度优先算法
clear
clc
%初始迷宫
X=8;
Y=8;
Maze=zeros(2*Y+1,2*X+1);
Maze(2:2:end-1,2:2:end-1)=1;%1代表不是墙wall,可以行走的区域room
%未访问
R_n=1:X*Y;
%已访问
R_y=[];
%临时栈
R_t=[];
%定义起点
R_Start=1;
R_Now=R_Start;%将起点作为当前迷宫单元
R_y=[R_y,R_Start];%标记已访问
R_n(R_n==R_Start)=[];%删除未访问
while ~isempty(R_n)
%当还存在未标记的迷宫单元,进行循环
R_N_Beside=R_Not_Beside(X,Y,R_Now,R_n);
if ~isempty(R_N_Beside)
%如果当前迷宫单元有未被访问过的的相邻的迷宫单元
%随机选择一个未访问的相邻迷宫单元
R_N_Beside_Ri=R_N_Beside(randi(length(R_N_Beside)));
%将当前迷宫单元入栈
if ismember(R_Now,R_t)
Ri=find(R_t==R_Now);
R_t([Ri,length(R_t)])=R_t([length(R_t),Ri]);
else
R_t=[R_t;R_Now];
end
%移除当前迷宫单元与相邻迷宫单元的墙
Maze_Sub_New=Move_R1_R2_Wall(X,Y,R_N_Beside_Ri,R_Now);
Maze(Maze_Sub_New(1),Maze_Sub_New(2))=1;
%标记相邻迷宫单元并用它作为当前迷宫单元
R_y=[R_y,R_N_Beside_Ri];%加入已访问
R_n(R_n==R_N_Beside_Ri)=[];%删除未访问
R_Now=R_N_Beside_Ri;
elseif ~isempty(R_t)
%如果当前迷宫单元不存在未访问的相邻迷宫单元,并且栈不空
%栈顶的迷宫单元出栈,令其成为当前迷宫单元
R_Now=R_t(end);
R_t(end)=[];
end
end
%定义终点
Maze(end,end-1)=1;
Maze(1,2)=1;%把起点的墙挖开
figure(1)
imagesc(Maze)
figure(2)
DrawMaze(Maze,1,3)
%记录当前迷宫单元有未被访问过的的相邻的迷宫单元
function R_N_Beside=R_Not_Beside(X,Y,R_Now,R_n)
[I,J] = ind2sub([Y,X],R_Now);
P4=zeros(4,2);
P4(1,:)=[I-1,J];%上
P4(2,:)=[I+1,J];%下
P4(3,:)=[I,J-1];%左
P4(4,:)=[I,J+1];%右
R_N_Beside=[];
for k=1:4
%对相邻的边依次循环
if (~all(P4(k,:))) || (P4(k,1)==Y+1) || (P4(k,2)==X+1)
%如果超出边界,跳过
continue
else
R_temp=sub2ind([Y,X],P4(k,1),P4(k,2));
if ismember(R_temp,R_n)
%如果该单元属于未被访问过
R_N_Beside=[R_N_Beside;R_temp];
end
end
end
end
function Maze_Sub_New=Move_R1_R2_Wall(X,Y,R1,R2)
[I1,J1]=ind2sub([Y,X],R1);
[I2,J2]=ind2sub([Y,X],R2);
Maze_Sub_New=[0,0];
if I1==I2
Maze_Sub_New=[2*I1,2*min(J1,J2)+1];
elseif J1==J2
Maze_Sub_New=[2*min(I1,I2)+1,2*J1];
end
end
function DrawMaze(Maze,Wall_Width,Room_Width)
[I,J]=size(Maze);
Y=(I-1)/2;
X=(J-1)/2;
%先,列方向
Maze_New1=zeros(Y*Room_Width+(Y+1)*Wall_Width,J);
%最开始
t=1;
Maze_New1(t:t+Wall_Width-1,:)=repmat(Maze(1,:),Wall_Width,1);
t=t+Wall_Width;
for k=1:Y
%先Room行
Maze_New1(t:t+Room_Width-1,:)=repmat(Maze(2*k,:),Room_Width,1);
t=t+Room_Width;
%后Wall行
Maze_New1(t:t+Wall_Width-1,:)=repmat(Maze(2*k+1,:),Wall_Width,1);
t=t+Wall_Width;
end
%后,行方向
Maze_New2=zeros(Y*Room_Width+(Y+1)*Wall_Width,X*Room_Width+(X+1)*Wall_Width);
%最开始
t=1;
Maze_New2(:,t:t+Wall_Width-1)=repmat(Maze_New1(:,1),1,Wall_Width);
t=t+Wall_Width;
for k=1:X
%先Room行
Maze_New2(:,t:t+Room_Width-1)=repmat(Maze_New1(:,2*k),1,Room_Width);
t=t+Room_Width;
%后Wall行
Maze_New2(:,t:t+Wall_Width-1)=repmat(Maze_New1(:,2*k+1),1,Wall_Width);
t=t+Wall_Width;
end
colormap(gray)
imagesc(Maze_New2)
axis equal
axis off
end
迷宫求解算法采用最普通的搜索路径方法,遇到死胡同则后退继续前进。
%求解迷宫
M_In=[2,2];%迷宫入口
M_Out=[2*Y,2*X];%迷宫出口
k=0;
M_Now=M_In;%起始点
Maze_S=Maze;%定义求解迷宫
Maze_S(1,:)=0;Maze_S(:,1)=0;Maze_S(:,end)=0;Maze_S(end,:)=0;%把求解迷宫的入口出口封闭
S_Walk=[];%定义路径
Maze_S(M_In(1),M_In(2))=2;%走过的路记录为2
S_Walk=[S_Walk;M_Now];%把起点添加到路径
while k==0
%如果该格周围有1,则随机前进一格
M_Now_B=[Maze_S(M_Now(1)-1,M_Now(2)),Maze_S(M_Now(1)+1,M_Now(2)),...
Maze_S(M_Now(1),M_Now(2)-1),Maze_S(M_Now(1),M_Now(2)+1)];%当前点上下左右的值
M_Now_B_Ind=find(M_Now_B==1);%等于1的索引
if ~isempty(M_Now_B_Ind)
Walk_Direction=M_Now_B_Ind(randi(length(M_Now_B_Ind)));
if Walk_Direction==1
M_Now=[M_Now(1)-1,M_Now(2)];
elseif Walk_Direction==2
M_Now=[M_Now(1)+1,M_Now(2)];
elseif Walk_Direction==3
M_Now=[M_Now(1),M_Now(2)-1];
elseif Walk_Direction==4
M_Now=[M_Now(1),M_Now(2)+1];
end
S_Walk=[S_Walk;M_Now];%添加到路径
Maze_S(M_Now(1),M_Now(2))=2;%定义为2
else
%如果该格周围没有1,则后退1个路径
M_Now_B_Ind=find(M_Now_B==2);%等于2的索引
Maze_S(M_Now(1),M_Now(2))=3;%定义为3错误路线
M_Now=S_Walk(end-1,:);
S_Walk(end,:)=[];
end
if M_Now(1)==M_Out(1) && M_Now(2)==M_Out(2)
break
end
end
figure(3)
%Maze_S(Maze_S==3)=1;
imagesc(Maze_S)
colormap([0,0,0;1,1,1;0,1,0;1,0,0])
以32×32的迷宫生成为例:
第一行为生成的迷宫,第二行为迷宫对应的解法(绿色为正确解法,红色为计算机求解过程中的错误路径)。
可以看到深度优先算法生成的迷宫,正确路径往往非常曲折,长度较长。而且错误路径同样很曲折,一旦走错,需要很长的时间才能发现。
参考:
1 三大迷宫生成算法 (Maze generation algorithm) – 深度优先,随机Prim,递归分割:
https://blog.csdn.net/juzihongle1/article/details/73135920/
2 Python 四大迷宫生成算法实现(1): 递归回溯算法:
https://blog.csdn.net/marble_xu/article/details/88201319
Prim算法可以表示为:
1.让迷宫全是墙.
2.选一个单元格作为迷宫的通路,然后把它的邻墙放入墙列表
3.当列表里还有墙时
1.从墙列表里随机选一个墙,如果这面墙分隔的两个单元格只有一个单元格被访问过
1.那就从列表里移除这面墙,即把墙打通。
2.让未访问的单元格成为迷宫的通路,标记为访问
3.把这个格子周围的邻墙加入墙列表
2.如果墙两面的单元格都已经被访问过,那就从列表里移除这面墙
和上一个深度优先利用格子作为循环判断不同,Prim算法以墙作为判断循环体进行循环。根据墙两边空间的状态决定打通或者保持封闭。
clear
clc
close all
%Prim算法
%初始迷宫
X=40;
Y=40;
Maze=zeros(2*Y+1,2*X+1);
Maze(2:2:end-1,2:2:end-1)=1;%1代表不是墙wall,可以行走的区域room
%生成墙
Wall=zeros((X-1)*Y+(Y-1)*X,4);%墙的储存格式为(R1_i,R1_j,R2_i,R2_j)
l=1;
for j=1:Y
for k=1:(X-1)
Wall(l,:)=[j,k,j,k+1];
l=l+1;
end
end
for k=1:X
for j=1:(Y-1)
Wall(l,:)=[j,k,j+1,k];
l=l+1;
end
end
%定义起点
R_Start=[1,1];
%确定相邻的边
Wall_Beside_1 = ismember(Wall(:,1:2),R_Start,'rows');
Wall_Beside_2 = ismember(Wall(:,3:4),R_Start,'rows');
Wall_Beside=or(Wall_Beside_1,Wall_Beside_2);
%搭建临时墙列表,把临边放入列表
Wall_B_Temp=Wall(Wall_Beside,:);
%被访问单元列表
Room_Y=R_Start;
%当列表里还有墙时
while ~isempty(Wall_B_Temp)
%从列表里随机选一个墙
Wall_Beside_R_Id=randi( size(Wall_B_Temp,1) );
Wall_Beside_R=Wall_B_Temp(Wall_Beside_R_Id,:);
%墙第一面的单元是否被访问过
B1=any(ismember(Room_Y,Wall_Beside_R(1:2),'rows'));
%墙第一面的单元是否被访问过
B2=any(ismember(Room_Y,Wall_Beside_R(3:4),'rows'));
%如果这面墙分隔的两个单元格只有一个单元格被访问过
if xor(B1,B2)
%那就从列表里移除这面墙
Wall_B_Temp(Wall_Beside_R_Id,:)=[];
Maze_Sub_New=Move_R1_R2_Wall(Wall_Beside_R(1:2),Wall_Beside_R(3:4));
Maze(Maze_Sub_New(1),Maze_Sub_New(2))=1;
%让未访问的单元格成为迷宫的通路
if B1
%如果第一面被访问过,则加入第二面
R_New=Wall_Beside_R(3:4);
Room_Y=[Room_Y;R_New];
elseif B2
%反之,如果第二面被访问过,则加入第一面
R_New=Wall_Beside_R(1:2);
Room_Y=[Room_Y;R_New];
end
%把这个格子的临墙加入列表
Wall_Beside_1 = ismember(Wall(:,1:2),R_New,'rows');
Wall_Beside_2 = ismember(Wall(:,3:4),R_New,'rows');
Wall_Beside=or(Wall_Beside_1,Wall_Beside_2);
Wall_B_Temp=[Wall_B_Temp;Wall(Wall_Beside,:)];
Wall_B_Temp=unique(Wall_B_Temp,'stable','rows');%删除重复的
elseif (B1 && B2)
%如果墙两面的单元格都已经被访问过
%从列表里移除这面墙
Wall_B_Temp(Wall_Beside_R_Id,:)=[];
end
end
figure(1)
imagesc(Maze)
figure(2)
DrawMaze(Maze,1,3)
%求解迷宫
M_In=[2,2];%迷宫入口
M_Out=[2*Y,2*X];%迷宫出口
Maze_S=Maze;%定义求解迷宫
Maze_S(1,:)=0;Maze_S(:,1)=0;Maze_S(:,end)=0;Maze_S(end,:)=0;%把求解迷宫的入口出口封闭
Maze_S=SloveMaze(Maze_S,M_In,M_Out);
figure(3)
imagesc(Maze_S)
colormap([0,0,0;1,1,1;0,1,0;1,0,0])
function Maze_Sub_New=Move_R1_R2_Wall(R1_sub,R2_sub)
I1=R1_sub(1);J1=R1_sub(2);
I2=R2_sub(1);J2=R2_sub(2);
Maze_Sub_New=[0,0];
if I1==I2
Maze_Sub_New=[2*I1,2*min(J1,J2)+1];
elseif J1==J2
Maze_Sub_New=[2*min(I1,I2)+1,2*J1];
end
end
function DrawMaze(Maze,Wall_Width,Room_Width)
[I,J]=size(Maze);
Y=(I-1)/2;
X=(J-1)/2;
%先,列方向
Maze_New1=zeros(Y*Room_Width+(Y+1)*Wall_Width,J);
%最开始
t=1;
Maze_New1(t:t+Wall_Width-1,:)=repmat(Maze(1,:),Wall_Width,1);
t=t+Wall_Width;
for k=1:Y
%先Room行
Maze_New1(t:t+Room_Width-1,:)=repmat(Maze(2*k,:),Room_Width,1);
t=t+Room_Width;
%后Wall行
Maze_New1(t:t+Wall_Width-1,:)=repmat(Maze(2*k+1,:),Wall_Width,1);
t=t+Wall_Width;
end
%后,行方向
Maze_New2=zeros(Y*Room_Width+(Y+1)*Wall_Width,X*Room_Width+(X+1)*Wall_Width);
%最开始
t=1;
Maze_New2(:,t:t+Wall_Width-1)=repmat(Maze_New1(:,1),1,Wall_Width);
t=t+Wall_Width;
for k=1:X
%先Room行
Maze_New2(:,t:t+Room_Width-1)=repmat(Maze_New1(:,2*k),1,Room_Width);
t=t+Room_Width;
%后Wall行
Maze_New2(:,t:t+Wall_Width-1)=repmat(Maze_New1(:,2*k+1),1,Wall_Width);
t=t+Wall_Width;
end
colormap(gray)
imagesc(Maze_New2)
axis equal
axis off
end
function Maze_S=SloveMaze(Maze,M_In,M_Out)
%输入迷宫矩阵。0是墙,1是空间
%输出求解结果。0是墙,1是未探索空间,2是正确路线,3是求解过程中的失败路线
%只能上下左右走
Maze_S=Maze;
k=0;
M_Now=M_In;%起始点
S_Walk=[];%定义路径
Maze_S(M_In(1),M_In(2))=2;%走过的路记录为2
S_Walk=[S_Walk;M_Now];%把起点添加到路径
while k==0
%如果该格周围有1,则随机前进一格
M_Now_B=[Maze_S(M_Now(1)-1,M_Now(2)),Maze_S(M_Now(1)+1,M_Now(2)),...
Maze_S(M_Now(1),M_Now(2)-1),Maze_S(M_Now(1),M_Now(2)+1)];%当前点上下左右的值
M_Now_B_Ind=find(M_Now_B==1);%等于1的索引
if ~isempty(M_Now_B_Ind)
Walk_Direction=M_Now_B_Ind(randi(length(M_Now_B_Ind)));
if Walk_Direction==1
M_Now=[M_Now(1)-1,M_Now(2)];
elseif Walk_Direction==2
M_Now=[M_Now(1)+1,M_Now(2)];
elseif Walk_Direction==3
M_Now=[M_Now(1),M_Now(2)-1];
elseif Walk_Direction==4
M_Now=[M_Now(1),M_Now(2)+1];
end
S_Walk=[S_Walk;M_Now];%添加到路径
Maze_S(M_Now(1),M_Now(2))=2;%定义为2
else
%如果该格周围没有1,则后退1个路径
M_Now_B_Ind=find(M_Now_B==2);%等于2的索引
Maze_S(M_Now(1),M_Now(2))=3;%定义为3错误路线
M_Now=S_Walk(end-1,:);
S_Walk(end,:)=[];
end
if M_Now(1)==M_Out(1) && M_Now(2)==M_Out(2)
break
end
end
end
以32×32的迷宫生成为例:
Prim算法的正确路线往往趋近于直线连接,而且迷宫具有非常多的长度为1的小分叉。Prim算法生成的分叉较多,迷宫比较自然,在未知条件下的求解难度较大。
参考:
1 三大迷宫生成算法 (Maze generation algorithm) – 深度优先,随机Prim,递归分割:
https://blog.csdn.net/juzihongle1/article/details/73135920/
2 随机迷宫生成算法整理分析
https://indienova.com/u/cocolate/blogread/1493
3 游戏常用算法-四种迷宫生成算法
https://www.cnblogs.com/millionsmultiplication/p/9568766.html
4 Wiki官网 - Maze generation algorithm
递归分割算法就是把空间用十字分成四个子空间,然后在其中的三面墙上随机挖洞。之后对每个子空间继续做这件事直到空间不足以继续分割为止。
1 将空间用墙分割为4个矩形区域
2 随机选择步骤1中4面墙的其中3面墙,在墙上随机开孔
3 对每个子空间矩形区域,重复步骤1-3,直至无法分割
为了编程简单,我采用2分法进行对称分割。但实际上完全可以采用不对称的分割方式创建子区域,以增强多样性(需要保证子空间是偶数,且最终分割区域为2×2大小)。
clear
clc
close all
%递归分割算法
T=4;
X=2^T;
Y=2^T;
Maze=zeros(2*Y+1,2*X+1);
Maze(2:end-1,2:end-1)=1;%1代表不是墙wall,可以行走的区域room
Split=[];
for j=1:T
N=2^(j-1);
Split_I=Split_Maze_Id(1,2*X+1,N);
Split=[Split;Split_I];
end
for j=1:size(Split,1)
Maze_T=Maze(Split(j,1):Split(j,2),Split(j,3):Split(j,4));
Maze_T=Split_Maze_Make(Maze_T);
Maze(Split(j,1):Split(j,2),Split(j,3):Split(j,4))=Maze_T;
end
figure(1)
imagesc(Maze)
figure(2)
DrawMaze(Maze,1,3)
%求解迷宫
M_In=[2,2];%迷宫入口
M_Out=[2*Y,2*X];%迷宫出口
Maze_S=Maze;%定义求解迷宫
Maze_S(1,:)=0;Maze_S(:,1)=0;Maze_S(:,end)=0;Maze_S(end,:)=0;%把求解迷宫的入口出口封闭
Maze_S=SloveMaze(Maze_S,M_In,M_Out);
figure(3)
imagesc(Maze_S)
colormap([0,0,0;1,1,1;0,1,0;1,0,0])
function Maze1=Split_Maze_Make(Maze)
%把迷宫中间进行十字形分割,并随机留下口
%输入迷宫为带墙迷宫,即周围全部为0;
[I,J]=size(Maze);
S_I=(I-1)/2+1;
S_J=(J-1)/2+1;
Maze1=Maze;
%生成墙
Maze1(S_I,:)=0;
Maze1(:,S_J)=0;
%随机生成三个缺口
Rand_Direction=randperm(4,3);
for k=1:3
if Rand_Direction(k)==1
%上
%可供拆开的墙编号:
Can_Break=2:2:(S_I-1);
Break_Wall=Can_Break(randi(length(Can_Break)));
Maze1(Break_Wall,S_J)=1;%开口
elseif Rand_Direction(k)==2
%下,可供拆开的墙编号:
Can_Break=(S_I+1):2:(I-1);
Break_Wall=Can_Break(randi(length(Can_Break)));
Maze1(Break_Wall,S_J)=1;%开口
elseif Rand_Direction(k)==3
%左
Can_Break=2:2:(S_J-1);
Break_Wall=Can_Break(randi(length(Can_Break)));
Maze1(S_I,Break_Wall)=1;%开口
elseif Rand_Direction(k)==4
%右
Can_Break=(S_J+1):2:(J-1);
Break_Wall=Can_Break(randi(length(Can_Break)));
Maze1(S_I,Break_Wall)=1;%开口
end
end
end
function Split_I=Split_Maze_Id(Min_M,Max_M,N)
%切成N块后的矩阵索引
Split_I=zeros(N^2,4);
S_Id=linspace(Min_M,Max_M,N+1);
t=1;
for j=1:N
for k=1:N
Split_I(t,:)=[S_Id(j),S_Id(j+1),S_Id(k),S_Id(k+1)];
t=t+1;
end
end
end
function DrawMaze(Maze,Wall_Width,Room_Width)
[I,J]=size(Maze);
Y=(I-1)/2;
X=(J-1)/2;
%先,列方向
Maze_New1=zeros(Y*Room_Width+(Y+1)*Wall_Width,J);
%最开始
t=1;
Maze_New1(t:t+Wall_Width-1,:)=repmat(Maze(1,:),Wall_Width,1);
t=t+Wall_Width;
for k=1:Y
%先Room行
Maze_New1(t:t+Room_Width-1,:)=repmat(Maze(2*k,:),Room_Width,1);
t=t+Room_Width;
%后Wall行
Maze_New1(t:t+Wall_Width-1,:)=repmat(Maze(2*k+1,:),Wall_Width,1);
t=t+Wall_Width;
end
%后,行方向
Maze_New2=zeros(Y*Room_Width+(Y+1)*Wall_Width,X*Room_Width+(X+1)*Wall_Width);
%最开始
t=1;
Maze_New2(:,t:t+Wall_Width-1)=repmat(Maze_New1(:,1),1,Wall_Width);
t=t+Wall_Width;
for k=1:X
%先Room行
Maze_New2(:,t:t+Room_Width-1)=repmat(Maze_New1(:,2*k),1,Room_Width);
t=t+Room_Width;
%后Wall行
Maze_New2(:,t:t+Wall_Width-1)=repmat(Maze_New1(:,2*k+1),1,Wall_Width);
t=t+Wall_Width;
end
colormap(gray)
imagesc(Maze_New2)
axis equal
axis off
end
function Maze_S=SloveMaze(Maze,M_In,M_Out)
%输入迷宫矩阵。0是墙,1是空间
%输出求解结果。0是墙,1是未探索空间,2是正确路线,3是求解过程中的失败路线
%只能上下左右走
Maze_S=Maze;
k=0;
M_Now=M_In;%起始点
S_Walk=[];%定义路径
Maze_S(M_In(1),M_In(2))=2;%走过的路记录为2
S_Walk=[S_Walk;M_Now];%把起点添加到路径
while k==0
%如果该格周围有1,则随机前进一格
M_Now_B=[Maze_S(M_Now(1)-1,M_Now(2)),Maze_S(M_Now(1)+1,M_Now(2)),...
Maze_S(M_Now(1),M_Now(2)-1),Maze_S(M_Now(1),M_Now(2)+1)];%当前点上下左右的值
M_Now_B_Ind=find(M_Now_B==1);%等于1的索引
if ~isempty(M_Now_B_Ind)
Walk_Direction=M_Now_B_Ind(randi(length(M_Now_B_Ind)));
if Walk_Direction==1
M_Now=[M_Now(1)-1,M_Now(2)];
elseif Walk_Direction==2
M_Now=[M_Now(1)+1,M_Now(2)];
elseif Walk_Direction==3
M_Now=[M_Now(1),M_Now(2)-1];
elseif Walk_Direction==4
M_Now=[M_Now(1),M_Now(2)+1];
end
S_Walk=[S_Walk;M_Now];%添加到路径
Maze_S(M_Now(1),M_Now(2))=2;%定义为2
else
%如果该格周围没有1,则后退1个路径
M_Now_B_Ind=find(M_Now_B==2);%等于2的索引
Maze_S(M_Now(1),M_Now(2))=3;%定义为3错误路线
M_Now=S_Walk(end-1,:);
S_Walk(end,:)=[];
end
if M_Now(1)==M_Out(1) && M_Now(2)==M_Out(2)
break
end
end
end
可以看到递归分割算法生成的迷宫非常具有规律性。然而这并不意味着迷宫的解法更简单。迷宫的解法路径长度介于深度优先和Prim方法之间。
参考:
1 三大迷宫生成算法 (Maze generation algorithm) – 深度优先,随机Prim,递归分割:
https://blog.csdn.net/juzihongle1/article/details/73135920/
2 Python 四大迷宫生成算法实现(3): 递归分割算法
https://blog.csdn.net/marble_xu/article/details/89310401
3 [迷宫中的算法实践]迷宫生成算法——递归分割算法
https://www.cnblogs.com/WayneShao/p/6087950.html
Wilson算法,是利用循环擦除随机游走(Loop-erased random walk)为基础的算法。
具体方法感觉自己理解的不太到位。我试着自己实践了一下,不知道是否掌握了该算法的精髓。
1 确定初始点,并加入已标记单元格,作为迷宫通路。
2 如果存在单元格未被标记
1 随机寻找已标记单元格旁边的未标记单元格,作为当前单元格
2 以当前单元格为起点,进行无循环的随机游走(即随机游走过程中发生自相交,则擦除相交圆环或重复路径)
3 当随机游走到已标记单元格上,随机游走停止,将游走路线上所有单元格加入已标记单元格。
4 将随机游走路径加入迷宫通路,连接到步骤(3)中已标记单元格上
具体过程如下:
其中红色代表每个新增路径的起点,绿色代表新增路径的终点
matlab实现代码如下:
clear
clc
close all
%Wilson算法 循环擦除随机游走算法 Loop-erased random walk wilson maze
%初始迷宫
X=20;
Y=20;
Maze=zeros(2*Y+1,2*X+1);
%Maze(2:2:end-1,2:2:end-1)=1;%1代表不是墙wall,可以行走的区域room
Room=zeros(Y,X);%所有空间区域,0代表未被访问
M_Start=[1,1];
Room(M_Start(1),M_Start(2))=1;
while ~all(Room(:))
%如果Room内存在未被访问的区域,则一直循环
%生成起始点
Walk_Start=Find_Walk_Start(Room);
%生成随机游走路径
Walk_Way=Random_Walk(Room,Walk_Start);
%将随机游走路径转换成迷宫路线
Maze=Way2Maze(Walk_Way,Maze);
%标记路径上所有经过的Room
Room_Way_Ind=sub2ind(size(Room),Walk_Way(:,1),Walk_Way(:,2));
Room(Room_Way_Ind)=1;
end
function Walk_Start=Find_Walk_Start(Room)
%寻找标记和未标记相邻的区块,输出所有未标记点的坐标
%也就是找到相邻坐标分别为0和1的块,然后输出0的坐标
[I,J]=size(Room);
Room_Diff=Room(1:end-1,:)-Room(2:end,:);
[BI1,BJ1]=find([Room_Diff;zeros(1,J)]==-1);%0在1上
%N1=size(BI1,1);
[BI2,BJ2]=find([zeros(1,J);Room_Diff]==1);%0在1下
%N2=size(BI2,1);
Room_Diff=Room(:,1:end-1)-Room(:,2:end);
[BI3,BJ3]=find([Room_Diff,zeros(I,1)]==-1);%0在1左
%N3=size(BI3,1);
[BI4,BJ4]=find([zeros(I,1),Room_Diff]==1);%0在1右
%N4=size(BI4,1);
%合并
BI=[BI1;BI2;BI3;BI4];
BJ=[BJ1;BJ2;BJ3;BJ4];
Walk_Start_List=[BI,BJ];
Walk_Start_List=unique(Walk_Start_List,'stable','rows');%删除重复的
N=size(Walk_Start_List,1);
%随机选一个
N_R=randi(N);
Walk_Start=Walk_Start_List(N_R,:);
%W_I=Walk_Start(1);W_J=Walk_Start(2);
end
function Walk_Way=Random_Walk(Room,Walk_Start)
%随机游走的路径记录
[I,J]=size(Room);
%第一步,记录起点
Walk_Last=Walk_Start;
Walk_Way=Walk_Start;
while true
%随机游走
D_R=randi(4);
if D_R==1
%向上走一步
if Walk_Last(1)==1
%如果上面碰壁,跳过
continue
else
Walk_Next=[Walk_Last(1)-1,Walk_Last(2)];
end
elseif D_R==2
%向下走一步
if Walk_Last(1)==I
%如果碰壁,跳过
continue
else
Walk_Next=[Walk_Last(1)+1,Walk_Last(2)];
end
elseif D_R==3
%向左走一步
if Walk_Last(2)==1
%如果碰壁,跳过
continue
else
Walk_Next=[Walk_Last(1),Walk_Last(2)-1];
end
elseif D_R==4
%向右走一步
if Walk_Last(2)==J
%如果碰壁,跳过
continue
else
Walk_Next=[Walk_Last(1),Walk_Last(2)+1];
end
end
%判断是否自相交
Cross_Id=ismember(Walk_Way,Walk_Next,'rows');
if any(Cross_Id)
%如果相交,把该点之后的所有路径删除
Cross_T=find(Cross_Id==true);
Walk_Way(Cross_T+1:end,:)=[];
Walk_Last=Walk_Way(end,:);
else
%如果不相交
%添加到路径
Walk_Way=[Walk_Way;Walk_Next];
Walk_Last=Walk_Next;
%如果新的点是1
if Room(Walk_Last(1),Walk_Last(2))==1
break
end
end
end
end
function Maze=Way2Maze(Walk_Way,Maze)
%将随机游走路径转换成迷宫路线
%
N=size(Walk_Way,1);
for k=1:N-1
Walk1=Walk_Way(k,:);
Walk2=Walk_Way(k+1,:);
Maze(2*Walk1(1),2*Walk1(2))=1;
Maze((Walk1(1)+Walk2(1)),(Walk1(2)+Walk2(2)))=1;
end
end
和前几个一样,用32*32大小来对比
Wilson方法生成的迷宫同样也很随机,具有较多的岔路。而且其迷宫出口路径长度要大于Prim方法。但是当迷宫较大的时候,随机游走很可能会因为范围过大,导致很难产生符合条件的通路,使得前几次循环格外的慢。
参考:
1 Wiki官网 - Maze generation algorithm
2 Wiki官网 - Loop-erased random walk
https://encyclopedia.thefreedictionary.com/Loop-erased+random+walk
3 Visualizing Algorithms
https://bost.ocks.org/mike/algorithms/
4 wilson算法
https://blog.csdn.net/qq_33271461/article/details/90208441