数据结构之迷宫求解问题(一)

我们小时候都玩过迷宫,走迷宫可以说是非常有意思了。而在我们大脑里是如何对这个游戏进行思考的呢?其实我们在玩这个游戏的是,大多是一条路走到黑,如果到达出口那么就走出来了,如果是死胡同,那么回到刚才的分叉口,再找一条路再一条路走到黑,以此类推。而我们在实现迷宫求解的时候也是利用这种方法,这种方法又称作回溯法。

我们要实现迷宫求解问题,首先应当是创建一个迷宫。这里我们用一个二维数组当做一个平面,然后用01序列当做墙与路,最终通过走1的路,看是否能够到达出口。

《数据结构之迷宫求解问题(一)》
如上图,此时1就是可以走的路,而0就是墙,无法行走。
我们可以创建一个结构体,在结构体内放置地图map。

#define ROW 6
#define COL 6

typedef struct Maze {
    int map[ROW][COL];//为了方便我们对地图的控制,这里设置两个全局的行、列
}Maze;

在定义完地图以后,我们考虑的是,如何在地图中走?以及定义规则。
首先我们应该确定入口点的位置。设为entry,而在我们的二维数组中,区别元素的唯一法则就是下标与内容,这里不考虑内容,下标应该是最理想的,所以我们应该再定义一个结构体来存放下标。

typedef struct Point{
    int row;//行
    int col;//列
}Point;

这样我们在定义入口点,以及接下来每一个走的点都可以用这个Point来处理操作。
接下来就是如何走的问题了。
我们这里采用的是从出口点开始,以顺时针为方向,依次判断入口点上、右、下、左是否能够落脚,而落脚的判断就是这四个点内的元素值是否为1,如果为1,那么证明可以落脚,然后对落脚点进行标记并入栈,落脚以后利用递归对落脚点四周继续判断,以此类推,直至走到出口,或者是无路可走,出栈并返回。然后再判断入口点右、下、左。
《数据结构之迷宫求解问题(一)》
《数据结构之迷宫求解问题(一)》
所以此时一旦落脚了(1,1)这个点,那么就应该对其进行标记及入栈,接下来应该判断这个点是否为出口,显然不是,那么接着以这个点为主,判断它的四周,以此类推。接下来我们实现。

void MazeInit(Maze* maze)//初始化地图
{
  if(maze == NULL) {
    return;
  }

  int map[ROW][COL] = {
    {0,1,0,0,0,0},
    {0,1,1,1,0,0},
    {0,1,0,1,1,0},
    {1,1,0,0,1,0},
    {0,0,0,0,0,0},
    {0,0,0,0,0,0}
  };

  size_t i = 0;
  size_t j = 0;
  for(; i < ROW; ++i) {
    for(j = 0; j < COL; ++j) {
      maze->map[i][j] = map[i][j];
    }
  }

  return;
}

void MazeMapPrint(Maze* maze)//打印地图
{
  if(maze == NULL) {
    return;
  }

  printf("\n");
  size_t i = 0;
  size_t j = 0;
  for(; i < ROW; ++i) {
    for(j = 0; j < COL; ++j) {
      printf("%2d ",maze->map[i][j]);
    }
    printf("\n");
  }
}

int CanStay(Maze* maze, Point cur)//是否能够落脚
{
  if(maze == NULL) {
    return 0;
  }

  if(cur.col < 0 || cur.col >= COL || \
     cur.row < 0 || cur.row >= ROW) {
    return 0;
  }

  if(maze->map[cur.row][cur.col] == 1) {
    return 1;
  }
  return 0;
}

void MarkStay(Maze* maze, Point cur)//标记落脚点
{
  if(maze == NULL) {
    return;
  }
  maze->map[cur.row][cur.col] = 2;
}

int isExit(Maze* maze, Point cur, Point entry)//判断是否是出口点
{
  if(maze == NULL) {
    return 0;
  }
  if((cur.row == 0 || cur.row == ROW - 1 || \
     cur.col == 0 || cur.col == COL - 1) && \
     (cur.col != entry.col || cur.row != entry.row)) {
    return 1;
  }
  return 0;
}

int _GetPathMaze(Maze* maze, Point cur, Point entry)
{
  printf("(%d,%d)\n",cur.row, cur.col);
  if(maze == NULL) {
    return 0;
  }
  //判断是否能落脚
  if(!CanStay(maze, cur)) {
    return 0;
  }
  //标记落脚点
  MarkStay(maze, cur);
  //判断是否是出口点
  if(isExit(maze, cur, entry)) {
    printf("找到一条路径\n");
    return 1;
  }
  //如果不是出口点那么就判断它的四周
  //预定顺序为上、右、下、左
  Point up = cur;  
  up.row -= 1;
  _GetPathMaze(maze, up, entry);

  Point right = cur;  
  right.col += 1;
  _GetPathMaze(maze, right, entry);

  Point down = cur;  
  down.row += 1;
  _GetPathMaze(maze, down, entry);

  Point left = cur;  
  left.col -= 1;
  _GetPathMaze(maze, left, entry);

  return 1;
}

void GetPathMaze(Maze* maze,Point entry)//查找路径
{
  if(maze == NULL) {
    return;
  }
  entry.row = 0;
  entry.col = 1;

  _GetPathMaze(maze, entry, entry);
}

我们发现,上面的代码并没有入栈用任何的入栈出栈操作,但是我们的思路里面却说入栈出栈,这是为什么呢?其实这里我们是利用了系统给予我们的栈,函数调用时每次调用函数都会形成自己的栈,函数返回后,栈销毁,数据释放。所以我们不必维护栈,就可以实现这个目的。

接下来,我们利用自己的栈来实现一下这一系列操作。

int _GetPathMazeByMyStack(Maze* maze, Point entry) 
{
  if(maze == NULL) {
    return 0;
  }
  SeqStack stack;
  SeqStackInit(&stack);
  //首先判断是否能够落脚
  if(!(CanStay(maze, entry))) {
    return 0;
  }
  //如果能够落脚,那么这个时候就标记落脚点,并且入栈
  MarkStay(maze, entry);
  SeqStackPush(&stack, entry);
  //判断是否是出口,如果是出口就进行打印并标记
  //如果不是出口点,此时应该判断其四周的点,是否能够落脚
  //如果可以落脚就标记并入栈依次循环
  Point cur;
  while(SeqStackGetFront(&stack, &cur)) {
    if(isExit(maze, cur, entry)) {
      MarkStay(maze, cur);
      printf("找到一条路径\n");
      SeqStackPop(&stack);
      continue;
    }

    Point up = cur;
    up.row -= 1;
    if(CanStay(maze, up)) {
      MarkStay(maze, up);
      SeqStackPush(&stack, up);
      continue;
    }

    Point right = cur;
    right.col += 1;
    if(CanStay(maze, right)) {
      MarkStay(maze, right);
      SeqStackPush(&stack, right);
      continue;
    }

    Point down = cur;
    down.row += 1;
    if(CanStay(maze, down)) {
      MarkStay(maze, down);
      SeqStackPush(&stack, down);
      continue;
    }

    Point left = cur;
    left.col -= 1;
    if(CanStay(maze, left)) {
      MarkStay(maze, left);
      SeqStackPush(&stack, left);
      continue;
    }

    SeqStackPop(&stack);
  }
  return 1;
}

void GetPathMazeByMyStack(Maze* maze, Point entry) 
{
  if(maze == NULL) {
    return;
  }
  entry.row = 0;
  entry.col = 1;

  _GetPathMazeByMyStack(maze, entry);

  return;
}

这里我们利用自己的栈,之前写过的栈操作这里直接使用,不再多说。
我们每次判断能够落脚以后首先先进行入栈操作,而接着判断是否是出口点,如果不是那么进入循环,取栈顶元素判断它的四周是否可以落脚以及是否是出口点,可以落脚就再次入栈并直接continue进行下次循环,再去栈顶元素,比较,以此类推。直至循环结束。

欢迎大家共同讨论,如有错误及时联系作者指出,并改正。谢谢大家!

    原文作者:迷宫问题
    原文地址: https://blog.csdn.net/liuchenxia8/article/details/79999644
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞