利用栈和队列共同解决迷宫问题

简介: 利用栈和队列共同解决迷宫问题

什么是迷宫问题?

迷宫问题是一道经典的算法问题,旨在寻找一条从起点到终点的最短路径。通常迷宫由一个二维矩阵表示,其中0代表可通过的空地,1代表墙壁不可通过。

在此条件下,需要运用数据结构中的图算法如广度优先搜索(BFS)或深度优先搜索(DFS)等找出一条从起点到终点的最短路径。

  • 例如,以下就是一个迷宫:
111111111111111
100010001000001
101110101011001
100000101000101
111110111010101
100000001010101
101111111010101
101000001010001
101111101110101
100010000000101
111110111110101
100000100000101
111110101011101
100000001000001
111111111111111

(其中1表示墙壁不可通过,0表示可以通过的路径。起点为(1,1),终点为(15,15))


如何解决迷宫问题?

DFS(深度优先搜索)

对于深搜的基础知识,可以看我之前的博客:【算法基础】深搜

  • 使用深度优先搜索(DFS)算法来解决迷宫问题。具体思路如下:
  1. 选定起点,建立一个 visited 数组记录每个点是否已经遍历过。
  2. 对于起点开始向四周探索,如果能够到达未曾遍历的点,则继续向该点前进,直到无法前进或者到达出口。
  3. 在探索的过程中,将每个点的状态更新到visited数组中并标记已经访问。
  4. 如果走到了出口,则找到了一条可行路径;否则回溯到上一个节点分别从其它方向继续探索,直到所有状态被访问。

但是,深搜并不适合解决迷宫问题,甚至有时得到的解是错误的。

在实现时需要注意,DFS 算法虽然容易理解和实现,但是存在回溯次数多、时间复杂度高等缺点。因此,在实际应用中,需要仔细考虑算法的时间复杂度,并选择合适的数据结构和优化手段来提高程序效率。

  • 下面来看一个错误的案例:
    用二维数组来表示迷宫,每个元素表示该位置上的状态(例如墙壁、通道等)。其中,’O’表示可到达点,’X’表示不可到达点,’S’表示起始点,’E’表示终点。
  • 首先,初始数组的文件是这样的:

  • 最短路径显然是从S直接到E,一步就可以解决问题,但事实是dfs走了27步。。。

因为dfs的代码是这样的:

//ei, ej表示终点坐标,si, sj表示起点坐标,i, j表示现在的坐标 
int dfs(int ei, int ej, int si, int sj, int i, int j){
  int flag = 0;     //标记是否到达终点 
  a[i][j] = '.';      //标记此位置,表示已经访问过 
  if(i == ei && j == ej){
    flag = 1;
    if(cnt <= min_cnt) min_cnt = cnt;
  }
  //往右 
  if(flag != 1 && j+1 <= n && (a[i][j+1] == 'O' || a[i][j+1] == 'E')){
    push(s, i, j);
    if(dfs(ei ,ej, si, sj, i, j+1)==1){
      cnt++;
      flag = 1;
    }
  }
  //往下 
  if(flag != 1 && i+1 <= m && (a[i+1][j] == 'O' || a[i+1][j] == 'E')){
    push(s, i, j);
    if(dfs(ei, ej, si, sj, i+1, j) == 1){
      cnt++;
      flag = 1;
    }
  }
  //往左 
  if(flag != 1 && j-1 > 0 && (a[i][j-1] == 'O' || a[i][j-1] == 'E')){
    push(s, i, j);
    if(dfs(ei, ej, si, sj, i, j-1) == 1){
      cnt++;
      flag = 1;
    }
  }
  //往上
  if(flag != 1 && i-1 > 0 && (a[i-1][j] == 'O' || a[i-1][j] == 'E')){
    push(s, i, j);
    if(dfs(ei, ej, si, sj, i-1, j) == 1){
      cnt++;
      flag = 1;
    }
  } 
  if(flag != 1){
    a[i][j] == 'O';
    pop(s);
    cnt--;
  }
  return flag;
}

并没有找到最短路径,而是按照程序既定的顺序寻找终点。当然,我们也可以完善以下程序使寻路变得更加只能,但是这样写出来代码过于复杂。

BFS(广度优先搜索)

与BFS不同,BFS 算法的基本思路是从起点开始进行多层级别的搜索,逐渐向外扩展,直到找到终点或者所有状态被访问为止。

  • 具体步骤如下:
  1. 选定起点,以其为根节点建立一个 BFS 树,将其压入队列中。
  2. 对于每个节点,枚举所有可走的方向,生成该节点的子节点,并将其加入队列尾部。
  3. 在生成子节点时需要判断是否合法,如果不合法则忽略。这里建议使用 visited 数组记录每个点是否已经遍历过,以免出现死循环。
  4. 每次从队列头部取出一个节点并访问直到队列为空或找到终点
  5. 最终,如果想要打印出从起点到终点的路径,需要用到和BFS相配合。
  • 实现链栈的基本操作:栈在本实验中用于记录解决迷宫的路径,要实现基本的初始化、入栈、出栈和判空等操作。
  • 实现链式队列的基本操作:bfs算法借助队列实现迷宫路径的查找,所以要实现基本的初始化、入队、出队等操作。

BFS 算法不需要使用递归函数,因此比起 DFS 更容易实现和调试,并且可以找到最短路线。但是其空间复杂度较高,因此在实际应用中需要注意算法效率和内存占用情况。

其次,BFS算法是同时向外扩展多个路径,每一次扩展时,每一条路径都是相同的步数,所以首先到达终点的那条路径一定是步数最少的,也就是最短路径。

同样使用上面的迷宫表示方法,我们来看一下BFS的代码和性能:

// bfs算法,求出从起点到终点的最短路径,并输出路径中每一个点的坐标
int bfs(Point start, Point end) {
    Queue queue;
    initQueue(&queue);
    enQueue(&queue, start);
    vis[start.x][start.y] = 1;
  //bfs 
    while (queue.front != NULL) {
        Point current = deQueue(&queue);
    //已经到达终点 
        if (isEndPoint(maze, current)) {
            showPath(current, start, end);
            return 1;
        }
        // 上下左右四个方向搜索可达点
        Point up = {current.x - 1, current.y};
        Point down = {current.x + 1, current.y};
        Point left = {current.x, current.y - 1};
        Point right = {current.x, current.y + 1};
    //向上 
        if (up.x > 0 && isAccessible(maze, up) && !vis[up.x][up.y]) {
            enQueue(&queue, up);
            vis[up.x][up.y] = current.x * MAX_LEN + current.y;
        }
    //向下 
        if (down.x <= m && isAccessible(maze, down) && !vis[down.x][down.y]) {
            enQueue(&queue, down);
            vis[down.x][down.y] = current.x * MAX_LEN + current.y;
        }
    //向左 
        if (left.y > 0 && isAccessible(maze, left) && !vis[left.x][left.y]) {
            enQueue(&queue, left);
            vis[left.x][left.y] = current.x * MAX_LEN + current.y;
        }
    //向右 
        if (right.y <= n && isAccessible(maze, right) && !vis[right.x][right.y]) {
            enQueue(&queue, right);
            vis[right.x][right.y] = current.x * MAX_LEN + current.y;
        }
    }    
    printf("Error: no path found!\n");
    return 0;
}

具体代码可以从这里下载:

初始迷宫是这样的:

可以找到最短路径并输出:

如果需要以上的全部代码可以看本博客上传的代码包。


总结

迷宫问题是求解从起点到终点的路径,使得路径能够遍历迷宫所有有效格子的问题。这个问题在计算机科学中被广泛研究,有很多种算法可以解决。

  • 在深度优先搜索(DFS)算法中,我们需要递归地向前探索,直到找到终点或无法继续前进为止。DFS 算法比较容易实现,但是可能会导致出现死循环和非最优解。
  • 广度优先搜索(BFS)算法则采用分层扩展的方式,从起点开始进行多层级别的搜索,逐渐向外扩展,直到找到终点或者所有状态被访问为止。BFS 算法能够找到最短路径,并且不会出现死循环的情况,但是空间复杂度比 DFS 更高。

另外,A* 算法是一种启发式搜索算法,也可以很好的解决迷宫问题。它是基于估价函数对每个节点的代价进行评估,并根据代价来选择下一个扩展的节点。使用 A* 算法可以更快地找到最短路径,但是需要设计合适的估价函数。

除此之外,还有其他一些算法,如Dijkstra算法、IDA*算法等,均有自己的特点和应用场景。

相关文章
|
1月前
|
C语言
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
159 9
|
1月前
|
存储 算法
非递归实现后序遍历时,如何避免栈溢出?
后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
26 1
|
17天前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
38 5
|
1月前
|
存储 算法 Java
数据结构的栈
栈作为一种简单而高效的数据结构,在计算机科学和软件开发中有着广泛的应用。通过合理地使用栈,可以有效地解决许多与数据存储和操作相关的问题。
|
1月前
|
存储 JavaScript 前端开发
执行上下文和执行栈
执行上下文是JavaScript运行代码时的环境,每个执行上下文都有自己的变量对象、作用域链和this值。执行栈用于管理函数调用,每当调用一个函数,就会在栈中添加一个新的执行上下文。
|
1月前
|
存储
系统调用处理程序在内核栈中保存了哪些上下文信息?
【10月更文挑战第29天】系统调用处理程序在内核栈中保存的这些上下文信息对于保证系统调用的正确执行和用户程序的正常恢复至关重要。通过准确地保存和恢复这些信息,操作系统能够实现用户模式和内核模式之间的无缝切换,为用户程序提供稳定、可靠的系统服务。
51 4
|
25天前
|
算法
数据结构之购物车系统(链表和栈)
本文介绍了基于链表和栈的购物车系统的设计与实现。该系统通过命令行界面提供商品管理、购物车查看、结算等功能,支持用户便捷地管理购物清单。核心代码定义了商品、购物车商品节点和购物车的数据结构,并实现了添加、删除商品、查看购物车内容及结算等操作。算法分析显示,系统在处理小规模购物车时表现良好,但在大规模购物车操作下可能存在性能瓶颈。
42 0
|
2月前
|
算法 程序员 索引
数据结构与算法学习七:栈、数组模拟栈、单链表模拟栈、栈应用实例 实现 综合计算器
栈的基本概念、应用场景以及如何使用数组和单链表模拟栈,并展示了如何利用栈和中缀表达式实现一个综合计算器。
43 1
数据结构与算法学习七:栈、数组模拟栈、单链表模拟栈、栈应用实例 实现 综合计算器
|
1月前
|
算法 安全 NoSQL
2024重生之回溯数据结构与算法系列学习之栈和队列精题汇总(10)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构王道第3章之IKUN和I原达人之数据结构与算法系列学习栈与队列精题详解、数据结构、C++、排序算法、java、动态规划你个小黑子;这都学不会;能不能不要给我家鸽鸽丢脸啊~除了会黑我家鸽鸽还会干嘛?!!!
|
2月前
初步认识栈和队列
初步认识栈和队列
61 10

热门文章

最新文章