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

在上篇文章中,我们重点解决迷宫寻找路径。这篇文章,我们研究的是对迷宫最短路径进行寻找。
如果一个迷宫有很多条路径,那我们该如何找到这条最短的路径呢?
这里写图片描述
我们上篇文章以及讲过,每次判断一个点是否能够落脚后,入栈,然后进一系列操作。这里我们的思路是,开始创建两个栈,一个存放每次出去的路径的过程,一次存放最短的路径,最后打出最短的路径。这里需要让栈内元素为我们的结构体Point。在每个出口比较我们两个栈的size大小,达到最终一个栈内为最短的路径。我们对上述的一些操作先做基本实现。

typedef Point SeqStackType

typedef struct SeqStack {
    SeqStackType* data;
    size_t capacity;
    size_t size;
};

void SeqStackPrintShort(SeqStack* stack)//打印最短路径
{
  if(stack == NULL) {
    return;
  }

  size_t i = 0;
  for(; i < stack->size; ++i) {
    printf("(%d,%d)\n",stack->data[i].row, stack->data[i].col);
  }
  printf("\n");
}



void SeqStackAssign(SeqStack* from, SeqStack* to)//赋值栈,即将size较小的赋值
{
  if(from == NULL || to == NULL) {
    return;
  }

  size_t i = 0;

  to->capacity = from->capacity;
  to->size = from->size;
  to->data = (SeqStackType*)malloc(to->capacity * sizeof(SeqStackType));

  for(; i < to->size; ++i) {
    to->data[i] = from->data[i];
  }

  return;
}

接下来就是我们对迷宫的操作实现了

int _GetShortPath(Maze* maze, Point cur, Point entry, SeqStack* cur_path, SeqStack* short_path)//获取最短路径
{
  if(maze == NULL) {
    return 0;
  }
  //判断是否能够落脚
  if(!CanStay(maze, cur)) {
    return 0;
  }

  //如果可以落脚,那么标记,并且对其进行入栈至cur_path
  MarkStay(maze, cur);
  SeqStackPush(cur_path, cur);
  //判断是否是出口,如果是出口,比较cur_path与short_path
  //比较两者size
  if(isExit(maze, cur, entry)) {
    printf("找到一条路径\n");
    if(cur_path->size < short_path->size || short_path->size == 0) {
      SeqStackAssign(cur_path, short_path);
    }//这里比较两个栈内的size
    SeqStackPop(cur_path);
    return 1;
  }
  //如果不是出口则探测其四周
  Point up = cur;  
  up.row -= 1;
  _GetShortPath(maze, up, entry, cur_path, short_path);

  Point right = cur;  
  right.col += 1;
  _GetShortPath(maze, right, entry, cur_path, short_path);

  Point down = cur;  
  down.row += 1;
  _GetShortPath(maze, down, entry, cur_path, short_path);

  Point left = cur;  
  left.col -= 1;
  _GetShortPath(maze, left, entry, cur_path, short_path);

  SeqStackPop(cur_path);

  return 1;
}

void GetShortPath(Maze* maze, Point entry)
{
  if(maze == NULL) {
    return;
  }

  SeqStack cur_path;//每次入栈
  SeqStack short_path;//最后比较入栈,这里面存放的是最短路径
  SeqStackInit(&cur_path);
  SeqStackInit(&short_path);

  entry.row = 0;
  entry.col = 1;

  _GetShortPath(maze, entry, entry, &cur_path, &short_path);
  printf("最短路径是\n");
  SeqStackPrintShort(&short_path);
}

再解决了这个问题后,还有一个拓展,就是倘若我们的迷宫带有环的路径,是否还能够找到最短的路径?答案肯定是不行的。接下来我们来解决一下带环的的最短路径探测。我们这里利用标记与判断落脚点进行处理,使得每次的落脚点都是前一个落脚点的加一,这样的话能够更好的处理路径,并且可以寻找最短路径。
实现如下:

void MazeInitCycle(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,1},
    {1,1,1,1,0,0},
    {0,0,1,0,0,0},
    {0,0,1,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;
}

int CanStayWithCycle(Maze* maze, Point cur, Point pre)//判断是否可以落脚
{
  if(maze == NULL) {
    return 0;
  }
  if(maze->map[cur.row][cur.col] == 1) {//如果落脚点的map值为1那么直接返回可以落脚
    return 1;
  }

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

void MarkStayWithCycle(Maze* maze, Point cur, Point pre)//标记为前一个点的下标+1
{
  if(maze == NULL) {
    return;
  }
  maze->map[cur.row][cur.col] = maze->map[pre.row][pre.col] + 1;
}

int _GetPathWithCycle(Maze* maze, Point cur, Point entry,
                      SeqStack* cur_path, SeqStack* short_path, Point pre)
{
//  printf("(%d, %d)\n",cur.row, cur.col);
  if(maze == NULL)
  {
    return 0;
  }
  //首先判断是否能落脚,这里判断是否能落脚时是跟前面不一样的
  //需要比较落脚地方与前面地方的值的大小
  if(!CanStayWithCycle(maze, cur, pre)) {
    return 0;
  }
  //如果可以落脚,那么就对落脚点进行标记,标记方式也与前面不同
  //并入栈
  MarkStayWithCycle(maze, cur, pre);//标记
  SeqStackPush(cur_path, cur);//入栈cur_path
  pre = cur;
  //如果是出口点,那么比较cur_path与short_path的size大小进行比较
  if(isExit(maze, cur, entry)) {
    printf("找到一条路径\n");
    if(cur_path->size < short_path->size || short_path->size == 0) {
      SeqStackAssign(cur_path, short_path);
    }
    SeqStackPop(cur_path);
    return 1;
  }
  //如果不是出口点,那么探寻上、右、下、左四点
  Point up = cur;
  up.row -= 1;
  _GetPathWithCycle(maze, up, entry, cur_path, short_path, pre);

  Point right = cur;
  right.col += 1;
  _GetPathWithCycle(maze, right, entry, cur_path, short_path, pre);

  Point down = cur;
  down.row += 1;
  _GetPathWithCycle(maze, down, entry, cur_path, short_path, pre);

  Point left = cur;
  left.col -= 1;
  _GetPathWithCycle(maze, left, entry, cur_path, short_path, pre);

  SeqStackPop(cur_path);
  return 1;
}

void GetPathWithCycle(Maze* maze, Point entry)//探寻带环路径
{
  if(maze == NULL) {
    return;
  }

  SeqStack cur_path;
  SeqStack short_path;
  SeqStackInit(&cur_path);
  SeqStackInit(&short_path);

  entry.row = 0;
  entry.col = 1;

  _GetPathWithCycle(maze, entry, entry, &cur_path, &short_path, entry);
  printf("找到一条最短路径\n");
  SeqStackPrintShort(&short_path);
}

测试代码如下:

void TestInit()
{
  HEAD;
  Maze maze;
  MazeInit(&maze);
  MazeMapPrint(&maze);
}

void TestGetPath()
{
  HEAD;
  Maze maze;
  Point entry;
  MazeInit(&maze);
  MazeMapPrint(&maze);
  GetPathMaze(&maze, entry);
  MazeMapPrint(&maze);
}

void TestGetPathByMyStack()
{
  HEAD;
  Maze maze;
  Point entry;
  MazeInit(&maze);
  MazeMapPrint(&maze);
  GetPathMazeByMyStack(&maze, entry);
  MazeMapPrint(&maze);
}

void TestGetPathWithCycle()
{
  HEAD;
  Maze maze;
  Point entry;
  MazeInitCycle(&maze);
  MazeMapPrint(&maze);
  GetPathWithCycle(&maze, entry);
  MazeMapPrint(&maze);
}

void TestGetShortPath()
{
  HEAD;
  Maze maze;
  Point entry;
  MazeInit(&maze);
  MazeMapPrint(&maze);
  GetShortPath(&maze, entry);
  MazeMapPrint(&maze);
}

int main()
{
  TestInit();
  TestGetPath();
  TestGetPathByMyStack();
  TestGetShortPath();
  TestGetPathWithCycle();

  printf("\n");
  printf("\n");
  printf("\n");
  printf("\n");
  printf("\n");
  printf("\n");
  return 0;
}


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

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值