问题描述与分析
基于本次问题描述,待解决的有两个问题。
其一,如何自创一个随机的64*64的迷宫。
说到迷宫,首先一定要有一个迷宫的地图,让后还要规定寻找迷宫出口的一些规则
关于迷宫的地图,在这里创建一个二维数组来表示迷宫地图,并且约定
(1)位置0为墙(即不能走的地方),位置1为路
(2)走到边界点的位置,就为出路
(3)按照上、右、下、左(顺时针)的顺序来探测下一步出路,若下一步路,就往下走
(4)若当前点可以走,就将其标记为2,为了区分位置1,防止不知道自己走重复的路。
从而可以用两种算法生成这个随机迷宫
一、深度优先遍历算法
1.从第一个单元开始,检查当前单元是否堵塞(即周围四个单元是已被访问或不可访问)
2.若不堵塞,则随机选择一个相邻单元作为下一单元,检查是否可访问
3.若可访问,则打破当前单元与下一单元之间的墙壁,将当前单元入栈,将下一单元作为当前单元;若不不可访问,则回到步骤2
4.若当前单元堵塞,则回退至上一单元
5.如此遍历,直到所有的单元都被访问
二、随机普利姆算法
1.使迷宫全是墙,即每个单元之间无法连通
2.随机选一个单元格作为迷宫的通路,然后把它的邻墙放入列表
3.当列表里还有墙时:
从列表里随机选一个墙,如果这面墙分隔的两个单元只有一个单元被访问过:那就从列表里移除这面墙,即把墙打通,让未访问的单元成为迷宫的通路把这个单元的墙加入列表;
4.如果墙两面的单元都已经被访问过,那就从列表里移除这面墙
如此遍历,直到列表没有墙
相对于深度优先的算法,Randomized Prim’s Algorithm不是优先选择最近选中的单元格,而是随机的从所有的列表中的单元格进行选择,新加入的单元格和旧加入的单元格同样概率会被选择,新加入的单元格没有优先权。因此其分支更多,生成的迷宫更复杂,岔路更多,难度更大,也更自然。
其二,如何找到一条通路走出迷宫,即迷宫寻路算法。
迷宫问题的本质是图的遍历问题。在我们遇到的一些问题当中,有些问题我们不能够确切的找出数学模型,即找不出一种直接求解的方法,解决这一类问题,我们一般采用搜索的方法解决。搜索就是用问题的所有可能去试探,按照一定的顺序、规则,不断去试探,直到找到问题的解,试完了也没有找到解,那就是无解,试探时一定要试探完所有的情况(实际上就是穷举);从起点出发,先记录当前节点能探索的方向。然后依次向一个方向探索,直到这个方向不能继续探索再切换方向。如果当前位置所有方向的探索均结束,却没有到达终点,则沿路返回当前位置的前一个位置,并在此位置还没有探索过的方向继续进行探索;直到所有可能的路径都被探索到为止。为了保证在任何位置上都能原路返回,因此需要使用一个后进先出的存储结构来保存从起点到当前位置的路径以及在路径上各位置还可能进行探索的方向。因此在迷宫问题中使用堆栈是自然的。
由此给出三种算法:
- 用自定义栈实现的算法
如何走迷宫(此处用栈走迷宫作为示例),首先不能走自己已经走过的路,其次不能走地图中不可走的路,当排除这两个条件后在判断这个点剩下可以走的路将该点压入栈,如果走到最后但并没有到达终点说明进入了一个死胡同,则将该点弹出并标记为不可走的点,然后判断上一个点是否可走,以此继续,总结如下:
1,判断该点是否可走
2,该点走的下一个方向是哪里
3,走到该点,继续判断
4,如果出现死路
1)将该点标记为不可走并弹出
2)判断目前栈顶元素的点循环上一过程
(2)广度优先搜索算法
从图的某一结点出发,首先依次访问该结点的所有邻接结Vi1,Vi2,Vi3,……Vin,再按这些顶点被访问的先后次序依次访问与它们相邻接的所有未被访问的顶点,最后重复此过程,直至所有顶点均被访问位置。广度优先搜索使用队列(queue)来实现,整个过程也可以看做一个倒立的树形:
1、把根节点放到队列的末尾。
2、每次从队列的头部取出一个元素,查看这个元素所有的下一级元素,把它们放到队列的末尾。并把这个元素记为它下一级元素的前驱。
3、找到所要找的元素时结束程序。
4、如果遍历整个树还没有找到,结束程序。
(3)深度优先搜索算法
深度优先搜索属于图算法的一种,英文缩写为DFS即Depth First Search.其过程简要来说是对每一个可能的分支路径深入到不能再深入为止,而且每个节点只能访问一次.
顾名思义DFS就是从一个节点出发直到不能访问然后回转到上一层 也就是所说的 回溯+递归 实现方法就是从开始节点出发递归其四周只有满足要求就调用函数进行递归最后返回;所以我设置了两个数组dis,visit;dis存放步数,visit判断是否被访问;
(1) 把初始状态放入数组中,设为当前状态;
(2) 扩展当前的状态,产生一个新的状态放入数组中,同时把新产生的状态设为当前状态;
(3) 判断当前状态是否和前面的重复,如果重复则回到上一个状态,产生它的另一状态;
(4) 判断当前状态是否为目标状态,如果是目标,则找到一个解答,结束算法。
(5) 如果数组为空,说明无解。
对于pascal语言来讲,它支持递归,在递归时可以自动实现回溯(利用局部变量)所以使用递归编写深度优先搜索程序相对简单,当然也有非递归实现的算法。
#用栈实现的迷宫求解问题
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include<Windows.h>
#include<math.h>
#include<conio.h>
#define L 64
//墙和路径的标识
#define WALL 0
#define ROUTE 1
typedef struct
{
int i;
int j;
int di;
}Box;
typedef struct//顺序栈
{
Box data[L*L];
int top; //栈顶指针
}SqStack;
void InitStack(SqStack *&s)
{
s =(SqStack *)malloc(sizeof(SqStack));
s->top =-1;
}
void DestroyStack(SqStack *&s)
{
free(s);
}
int StackEmpty(SqStack *s)
{
return (s->top ==-1);
}
bool Push(SqStack *&s,Box e)
{
if(s->top==L*L)
return false;
s->top++;
s->data[s->top] = e;
return true;
}
bool Pop(SqStack *&s,Box &e)
{
if (s->top ==-1)
return false;
e = s->data [s->top ];
s->top --;
return true;
}
bool GetTop(SqStack *s,Box &e)
{
if (s->top ==-1)
return false;
e = s->data [s->top ];
return true;
}
//生成迷宫
void CreateMaze(int **maze, int x, int y);
bool mgpath (int xi,int yi,int xe,int ye);
int **Maze = (int**)malloc(L * sizeof(int *));
int main(void) {
srand((unsigned)time(NULL));
for (int i = 0; i < L; i++) {
Maze[i] = (int*)calloc(L, sizeof(int));
}
//最外围层设为路径的原因,为了防止挖路时挖出边界,同时为了保护迷宫主体外的一圈墙体被挖穿
for (int i = 0; i < L; i++){
Maze[i][0] = ROUTE;
Maze[0][i] = ROUTE;
Maze[i][L - 1] = ROUTE;
Maze[L - 1][i] = ROUTE;
}
//创造迷宫,(2,2)为起点
CreateMaze(Maze, 2, 2);
//画迷宫的入口和出口
Maze[2][1] = ROUTE;
int index = 0;
//由于算法随机性,出口有一定概率不在(L-3,L-2)处,此时需要寻找出口
for (int i = L - 3; i >= 0; i--) {
if (Maze[i][L - 3] == ROUTE) {
index = i;
Maze[i][L - 2] = ROUTE;
break;
}
}
//画迷宫
for (int i = 0; i < L; i++) {
for (int j = 0; j < L; j++) {
if (Maze[i][j] == ROUTE) {
printf(" ");
}
else {
printf("囻");
}
}
printf("\n");
}
printf("%d\n",index);
if(!mgpath(2,1,index,L-2))
printf("该迷宫没有解\n");
for (int i = 0; i < L; i++) free(Maze[i]);
free(Maze);
system("pause");
return 0;
}
bool mgpath (int xi,int yi,int xe,int ye)
{
printf("此处运行!\n");
Box path[L*L],e;
int i,j,di,il,jl,k;
bool find;
SqStack *st;
InitStack(st);
e.i = xi;e.j=yi;e.di = -1;
Push(st,e);
Maze[xi][yi] = -1;
while(!StackEmpty(st))
{
//printf("2,此处运行!\n");
GetTop(st,e);
i=e.i;j = e.j;di = e.di;
printf("%d,%d\t",i,j);
find = false;
if (i==xe&&j==ye)
{
printf("\n一条迷宫路径如下:\n");
k=0;
while(!StackEmpty(st))
{
Pop(st,e);
path[k++]=e;
}
while(k>=1)
{
k--;
printf("\t(%d,%d)",path[k].i,path[k].j);
if((k+2)%10==0)//每输出10个方块换行
printf("\n");
}
printf("\n");
DestroyStack(st);
return true;
}
while(di < 4 && !find)
{
di++;
switch(di)
{
case 0:il = i-1;jl = j;break;
case 1:il = i;jl = j+1;break;
case 2:il = i+1;jl = j;break;
case 3:il = i;jl = j-1;break;
}
if(Maze[il][jl]==1) find = true;
}
if(find)
{
st->data[st->top].di=di;
e.i=il;e.j=jl;e.di=-1;
Push(st,e);
Maze[il][jl] = -1;
}
else
{
Pop(st,e);
Maze[e.i][e.j] = 1;
}
}
DestroyStack(st);
return false;
}
void CreateMaze(int **maze, int x, int y) {
maze[x][y] = ROUTE;
//确保四个方向随机
int direction[4][2] = { { 1,0 },{ -1,0 },{ 0,1 },{ 0,-1 } };
for (int i = 0; i < 4; i++) {
int r = rand() % 4;
int temp = direction[0][0];
direction[0][0] = direction[r][0];
direction[r][0] = temp;
temp = direction[0][1];
direction[0][1] = direction[r][1];
direction[r][1] = temp;
}
//向四个方向开挖
for (int i = 0; i < 4; i++) {
int dx = x;
int dy = y;
int range = 1 ;
while (range>0) {
dx += direction[i][0];
dy += direction[i][1];
//排除掉回头路
if (maze[dx][dy] == ROUTE) {
break;
}
//判断是否挖穿路径
int count = 0;
for (int j = dx - 1; j < dx + 2; j++) {
for (int k = dy - 1; k < dy + 2; k++) {
//abs(j - dx) + abs(k - dy) == 1 确保只判断九宫格的四个特定位置
if (abs(j - dx) + abs(k - dy) == 1 && maze[j][k] == ROUTE) {
count++;
}
}
}
if (count > 1) {
break;
}
//确保不会挖穿时,前进
--range;
maze[dx][dy] = ROUTE;
}
//没有挖穿危险,以此为节点递归
if (range <= 0) {
CreateMaze(maze, dx, dy);
}
}
}
用队列实现的迷宫求解算法
#include<bits/stdc++.h>
#include<Windows.h>
#include<time.h>
#include<math.h>
using namespace std;
//不用考虑越界问题
//地图长度L,包括迷宫主体40,外侧的包围的墙体2,最外侧包围路径2(之后会解释)
#define L 68
//墙和路径的标识
#define WALL 0
#define ROUTE 1
bool st[L][L];
int px=0,py=0;//用来记录迷宫终点
const int M=100005;
struct node{
int x,y;
}w[M];
int ans[M];//用来存走的步数
//用向量来向上下左右四个方向广搜
int dxx[4]={1,0,-1,0};
int dyy[4]={0,1,0,-1};
//控制迷宫的复杂度,数值越大复杂度越低,最小值为0
static int Rank = 0;
//生成迷宫
void CreateMaze(int **maze, int x, int y);
void dfs(int **maze,int u);
void bfs(int **maze,int x,int y);
int main(void) {
//生成随机迷宫
//dos控制台窗口大小设置
system("mode con cols=230 lines=230"); //调整窗口大小
srand((unsigned)time(NULL));//每次生成的随机数不一样
int **Maze = (int**)malloc(L * sizeof(int *));//可以改!!!如果用二维数组就不用free
for (int i = 0; i < L; i++) {
Maze[i] = (int*)calloc(L, sizeof(int));
}
//最外围层设为路径的原因,为了防止挖路时挖出边界,同时为了保护迷宫主体外的一圈墙体被挖穿,一定不会走到最外层墙去!!!
for (int i = 0; i < L; i++){
Maze[i][0] = ROUTE;
Maze[0][i] = ROUTE;
Maze[i][L - 1] = ROUTE;
Maze[L - 1][i] = ROUTE;
}
//创造迷宫,(2,2)为起点
Maze[2][1] = 1;
CreateMaze(Maze, 2, 1);
//画迷宫的入口和出口
//由于算法随机性,出口有一定概率不在(L-3,L-2)处,此时需要寻找出口
//可以记录一下最后一个点!!!
for (int i = L - 3; i >= 0; i--) {
if (Maze[i][L - 3] == ROUTE) {
Maze[i][L - 2] = ROUTE;
px=i;
py=L-3;
break;
}
}
// cout<<px<<' '<<py<<endl;
//画迷宫
for (int i = 0; i < L; i++) {
for (int j = 0; j < L; j++) {
if(Maze[i][j] == 1)cout << " ";
else cout << "墙";
}
printf("\n");
}
bfs(Maze,2,1);
cout<<'('<<px<<','<<py+1<<')'<<' ';
Maze[2][1]=2;
Maze[px][py]=2;
Maze[px][py+1]=2;
for(int i=0;i<L;i++){
for(int j=0;j<L;j++){
if(Maze[i][j] == 1)cout << " ";
else if(Maze[i][j] == 0)cout << "墙";
else cout<<"√" ;
}
cout<<endl;
}
for (int i = 0; i < L; i++) free(Maze[i]);//释放空间
free(Maze);
system("pause");//可以删除
return 0;
}
void CreateMaze(int **maze, int x, int y) {
//maze[x][y] = ROUTE;
//确保四个方向随机
int direction[4][2] = { { 1,0 },{ -1,0 },{ 0,1 },{ 0,-1 } };//左,右,下,上
for (int i = 0; i < 4; i++) {
int r = rand() % 4;
int temp = direction[0][0];//打乱方向顺序
direction[0][0] = direction[r][0];//
direction[r][0] = temp;
temp = direction[0][1];
direction[0][1] = direction[r][1];
direction[r][1] = temp;
}
//向四个方向开挖
for (int i = 0; i < 4; i++) {
int dx = x;
int dy = y;
//控制挖的距离,由Rank来调整大小
int range = 1;//每次挖的长度
while (range>0) {
dx += direction[i][0];
dy += direction[i][1];
//排除掉回头路
if (maze[dx][dy] == ROUTE) {
break;
}
//判断是否挖穿路径
int count = 0;//判断这个点是否能继续挖
//count=1时,就说明可以走,来的地方
for (int j = dx - 1; j < dx + 2; j++) {
for (int k = dy - 1; k < dy + 2; k++) {
//abs(j - dx) + abs(k - dy) == 1 确保只判断九宫格的四个特定位置
//保证只有上下左右四个点 ,(j,k)是下一个点
if (abs(j - dx) + abs(k - dy) == 1 && maze[j][k] == ROUTE) {
count++;
}
}
}
if (count > 1) {
break;
}
//确保不会挖穿时,前进
--range;
maze[dx][dy] = ROUTE;
}
//没有挖穿危险,以此为节点递归
if (range <= 0) {
CreateMaze(maze, dx, dy);
}
}
}
int count2=0;
void dfs(int **maze,int u){
//只有未初始化的ans[0]=0满足ans[u]==u,其他的ans[u]>u;
//有点没太懂
if(ans[u]==u){
cout<<"(2,1)"<<endl;
return;
}
dfs(maze,ans[u]);
cout<<'('<<w[u].x<<','<<w[u].y<<')'<<' ';
maze[w[u].x][w[u].y]=2;
count2++;
if(count2==20){
cout<<endl;
count2=0;
}
maze[w[u].x][w[u].y]=2;
}
void bfs(int **maze,int x,int y){
int head=0,tail=0;
int flag=0;//flag用来标记是否到达了终点
w[tail++]={x,y};//模拟队列
// cout << x << ' ' << y << '\n';
//head=tail说明队列为空,所以队列不为空时
while(head!=tail){
for(int i=0;i<4;i++){//广搜,分别向四个方向扩展
int nx=w[head].x+dxx[i];
int ny=w[head].y+dyy[i];
//cout << nx << ' ' << ny << '\n';
//判断该点是否满足条件
if(nx<1||nx>=L||ny<1||ny>=L||maze[nx][ny]==WALL) continue;//
//如果满足条件就将该点入队
if(st[nx][ny])continue;
st[nx][ny] = true;
w[tail]={nx,ny};
// 这里利用初始的时候haed和tail不相等来存步数
//即走到tail步的前一点的坐标是(w[head].x,w[head].y)
ans[tail]=head;//记录该点的前一点在哪
tail++;
if(nx==px&&ny==py){
flag=1;
break;
}
}
if(flag==1){
dfs(maze,tail-1);//开始输出操作
break;
}
head++;
}
}
用深度优先搜索递归回溯的算法:
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include<Windows.h>
#include<math.h>
#include<conio.h>
#define L 68
//墙和路径的标识
#define WALL 0
#define ROUTE 1
const int u = 0;
int end_x,end_y;//终点坐标:(endx, endy)
int step;
//顺时针探路
int kx[4] = { 1,0,-1,0 }; //:首先我们移动方向一般只有 上、下、左、右 这四个方向,如果有斜向的方向另说,因此我们可以制定一个顺序,去依次访问这些方向的单元格(道路)。
int ky[4] = { 0,1,0,-1 };
int Flag[L][L] ={0};//记录路是否走过,以防止回头的现象出现
void dfs(int x,int y,int step);
//生成迷宫
void CreateMaze(int **maze, int x, int y);
int **Maze = (int**)malloc(L * sizeof(int *));//建立一个二维数组用来存储墙壁和道路信息
int main(void) {
srand((unsigned)time(NULL));
for (int i = 0; i < L; i++) {
Maze[i] = (int*)calloc(L, sizeof(int));//分配num个长度为size的连续空间,返回指向分配起始地址的指针
}
//最外围层设为路径的原因,为了防止挖路时挖出边界,同时为了保护迷宫主体外的一圈墙体被挖穿
for (int i = 0; i < L; i++){
Maze[i][0] = ROUTE;
Maze[0][i] = ROUTE;
Maze[i][L - 1] = ROUTE;
Maze[L - 1][i] = ROUTE;
}
//创造迷宫,(2,2)为起点
CreateMaze(Maze, 2, 2);
//画迷宫的入口和出口
Maze[2][1] = ROUTE;
int index = 0;
//由于算法随机性,出口有一定概率不在(L-3,L-2)处,此时需要寻找出口
for (int i = L - 3; i >= 0; i--) {
if (Maze[i][L - 3] == ROUTE) {
index = i;
Maze[i][L - 2] = ROUTE;
break;
}
}
//画迷宫
for (int i = 0; i < L; i++) {
for (int j = 0; j < L; j++) {
if (Maze[i][j] == ROUTE) {
printf(" ");
}
else {
printf("囻");
}
}
printf("\n");
}
printf("迷宫出口为 end_x = %d , end_y= %d \n",index,L-2);
end_x = index;
end_y = L-2;
// printf("%d %d\n",end_x,end_y);
//printf("用dfs探路得一条简单路径为:\n");
dfs(2,1,0);
for (int i = 0; i < L; i++) free(Maze[i]);
free(Maze);
system("pause");
return 0;
}
void dfs(int x,int y,int step)
{
//如果当前步数小于最小值,就更新
if (x == end_x && y == end_y) {
printf("\n寻找到一条简单路径,运行成功!");
exit(0);//因为本次只求其一条最深路径,所以直接跳出函数
//printf("一条迷宫路径如下:\n");
// for (int i = 0; i < L; i++) {
// for (int j = 0; j < L; j++) {
// if (Flag[i][j] == 1) {
// printf("(%d,%d)\t",i,j);
// }
// }
// }
return ;
}
//printf("此处运行!\n");
for (int k = 0; k < 4; k++) {
int tx = x + kx[k];//计算下一步的X坐标
//计算下一步的Y坐标
int ty = y + ky[k];
Flag[x][y] = 1;
if (Maze[tx][ty] == 1 && Flag[tx][ty] == 0) { //当前面能走并且没有走过
Flag[tx][ty] = 1;
if(Flag[tx][ty] ==1)
printf("(%d,%d)\t",tx,ty);
dfs(tx, ty, step + 1);
Flag[tx][ty] = 0;//回溯时候要把i重新回到未标记的状态,不用恢复path[u] = 0是因为它会被覆盖
//然后继续循环
//循环完了后依然会回到上一层
}
}
return;//碰到死胡同,return回溯
//printf("%d\n",min);
}
void CreateMaze(int **maze, int x, int y) {
maze[x][y] = ROUTE;
//确保四个方向随机
int direction[4][2] = { { 1,0 },{ -1,0 },{ 0,1 },{ 0,-1 } };
for (int i = 0; i < 4; i++) {
int r = rand() % 4;
int temp = direction[0][0];
direction[0][0] = direction[r][0];
direction[r][0] = temp;
temp = direction[0][1];
direction[0][1] = direction[r][1];
direction[r][1] = temp;
}
//向四个方向开挖
for (int i = 0; i < 4; i++) {
int dx = x;
int dy = y;
int range = 1 ;
while (range>0) {
dx += direction[i][0];
dy += direction[i][1];
//排除掉回头路
if (maze[dx][dy] == ROUTE) {
break;
}
//判断是否挖穿路径
int count = 0;
for (int j = dx - 1; j < dx + 2; j++) {
for (int k = dy - 1; k < dy + 2; k++) {
//abs(j - dx) + abs(k - dy) == 1 确保只判断九宫格的四个特定位置
if (abs(j - dx) + abs(k - dy) == 1 && maze[j][k] == ROUTE) {
count++;
}
}
}
if (count > 1) {
break;
}
//确保不会挖穿时,前进
--range;
maze[dx][dy] = ROUTE;
}
//没有挖穿危险,以此为节点递归
if (range <= 0) {
CreateMaze(maze, dx, dy);
}
}
}