利用matlab创建与解决迷宫[深度优先、Prim、递归分割、Wilson]

利用matlab创建与解决迷宫[深度优先、Prim、递归分割、Wilson]

  • 1 深度优先算法
  • 2 Prim算法
  • 3 递归分割算法
  • 4 Wilson算法

本文利用matlab,实现了常见的三种迷宫算法:深度优先算法、Prim算法、递归分割算法,和Wilson算法(Loop-erased random walk)。并通过求解路径,对比三种迷宫不同的特点。

迷宫均为标准网格化的2维迷宫,规定迷宫内只能进行上下左右4个方向进行移动。入口和出口均只有一个。

对于一般迷宫而言,我们要找的是一种连接所有通路,且不存在回路和死路的方法。入口和出口的位置反而是次要的,而且可以在迷宫生成之后再定义。

基于马尔科夫链思想的MarkovJunior方法生成迷宫可见:
利用MarkovJunior方法生成迷宫和图形的MATLAB演示[迷宫生成、贪吃蛇、地图生成、图案生成]

1 深度优先算法

深度优先(递归回溯)算法可以表示为:

1.设置一个起点。将起点作为当前迷宫单元,并标记为已访问
2.当还存在未标记的迷宫单元,进行循环
	1.如果当前迷宫单元有未被访问过的的相邻的迷宫单元
		1.随机选择一个未访问的相邻迷宫单元
		2.将当前迷宫单元入栈
		3.移除当前迷宫单元与相邻迷宫单元的墙
		4.标记相邻迷宫单元已访问,并用它作为当前迷宫单元
	2.如果当前迷宫单元不存在未访问的相邻迷宫单元,并且栈不空
		1.栈顶的迷宫单元出栈
		2.令其成为当前迷宫单元

大概思路是先随机走,如果走不通了,则保存路径。之后逐步后退,直到能继续随机走。最终走完所有格点,则停止程序。
具体过程可以参考下面动图:
利用matlab创建与解决迷宫[深度优先、Prim、递归分割、Wilson]_第1张图片

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的迷宫生成为例:
利用matlab创建与解决迷宫[深度优先、Prim、递归分割、Wilson]_第2张图片
第一行为生成的迷宫,第二行为迷宫对应的解法(绿色为正确解法,红色为计算机求解过程中的错误路径)。

可以看到深度优先算法生成的迷宫,正确路径往往非常曲折,长度较长。而且错误路径同样很曲折,一旦走错,需要很长的时间才能发现。

参考:
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

2 Prim算法

Prim算法可以表示为:

1.让迷宫全是墙.
2.选一个单元格作为迷宫的通路,然后把它的邻墙放入墙列表
3.当列表里还有墙时
	1.从墙列表里随机选一个墙,如果这面墙分隔的两个单元格只有一个单元格被访问过
		1.那就从列表里移除这面墙,即把墙打通。
		2.让未访问的单元格成为迷宫的通路,标记为访问
		3.把这个格子周围的邻墙加入墙列表
	2.如果墙两面的单元格都已经被访问过,那就从列表里移除这面墙

和上一个深度优先利用格子作为循环判断不同,Prim算法以墙作为判断循环体进行循环。根据墙两边空间的状态决定打通或者保持封闭。

具体过程可以参考以下动图:
利用matlab创建与解决迷宫[深度优先、Prim、递归分割、Wilson]_第3张图片
matlab代码如下:

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的迷宫生成为例:
利用matlab创建与解决迷宫[深度优先、Prim、递归分割、Wilson]_第4张图片
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

3 递归分割算法

递归分割算法就是把空间用十字分成四个子空间,然后在其中的三面墙上随机挖洞。之后对每个子空间继续做这件事直到空间不足以继续分割为止。

1 将空间用墙分割为4个矩形区域
2 随机选择步骤1中4面墙的其中3面墙,在墙上随机开孔
3 对每个子空间矩形区域,重复步骤1-3,直至无法分割

算法的过程如下:
利用matlab创建与解决迷宫[深度优先、Prim、递归分割、Wilson]_第5张图片
matlab代码如下:

为了编程简单,我采用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

依然以32*32的对比
利用matlab创建与解决迷宫[深度优先、Prim、递归分割、Wilson]_第6张图片

可以看到递归分割算法生成的迷宫非常具有规律性。然而这并不意味着迷宫的解法更简单。迷宫的解法路径长度介于深度优先和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

4 Wilson算法

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大小来对比
利用matlab创建与解决迷宫[深度优先、Prim、递归分割、Wilson]_第7张图片
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

你可能感兴趣的:(matlab,迷宫,prim,Wilson算法,递归分割)