使用AStar算法求解二维迷宫问题

严正声明:本文系作者davidhopper原创,未经许可,不得转载。

题目描述

定义一个二维数组N*M(其中2<=N<=10;2<=M<=10),如5 × 5数组下所示:

int maze[5][5] = {
        0, 1, 0, 0, 0,
        0, 1, 0, 1, 0,
        0, 0, 0, 0, 0,
        0, 1, 1, 1, 0,
        0, 0, 0, 1, 0,
};

它表示一个迷宫,其中的1表示墙壁,0表示可以走的路,只能横着走或竖着走,不能斜着走,要求编程序找出从左上角到右下角的最短路线。入口点为[0,0],即第一空格是可以走的路。

Input

一个N × M的二维数组,表示一个迷宫。数据保证有唯一解,不考虑有多解的情况,即迷宫只有一条通道。

Output

左上角到右下角的最短路径,格式如样例所示。

Sample Input
5 5
0 1 0 0 0
0 1 0 1 0
0 0 0 0 0
0 1 1 1 0
0 0 0 1 0
Sample Output
(0, 0)
(1, 0)
(2, 0)
(2, 1)
(2, 2)
(2, 3)
(2, 4)
(3, 4)
(4, 4)

输入描述

输入两个整数,分别表示二位数组的行数,列数。再输入相应的数组,其中的1表示墙壁,0表示可以走的路。数据保证有唯一解,不考虑有多解的情况,即迷宫只有一条通道。

输出描述

左上角到右下角的最短路径,格式如样例所示。

示例1

输入
6 5
0 0 0 1 1 
1 1 0 1 1 
1 1 0 0 1 
1 1 1 0 1 
1 1 1 0 1 
1 1 1 0 0 
输出
(0,0)
(0,1)
(0,2)
(1,2)
(2,2)
(2,3)
(3,3)
(4,3)
(5,3)
(5,4)

答案

一般都会使用回溯法求解,本文使用效率更高的A*算法求解(杀鸡用牛刀)。A*算法的原理可参考这篇文章:A星算法详解
如果不考虑具体实现代码,A*算法是相当简单的。有两个集合,OPEN集和CLOSED集。其中OPEN集保存待考察的结点。开始时,OPEN集只包含一个元素:初始结点。CLOSED集保存已考查过的结点。开始时,CLOSED集是空的。如果绘成图,OPEN集就是被访问区域的边境(frontier),而CLOSED集则是被访问区域的内部(interior)。每个结点同时保存其父结点的指针,以便反向溯源。
在主循环中重复地从OPEN集中取出最好的结点nf值最小的结点)并检查之。如果n是目标结点,则我们的任务完成了。否则,从OPEN集中删除结点n并将其加入CLOSED集。然后检查它的邻居n’。如果邻居n’CLOSED集中,表明该邻居已被检查过,不必再次考虑(若你确实需要检查结点n’g值是否更小,可进行相关检查,若其g值更小,则将该结点从CLOSED集中删除。除特殊情况外,一般不需如此处理,本文不进行这种检查);如果n’OPEN集中,那么该结点今后肯定会被考察,现在不必考虑它。否则,把它加入OPEN集,把它的父结点设为n
A*搜索算法的核心就在于如何设计一个好的启发代价函数:
f ( n ) = g ( n ) + h ( n ) f(n)=g(n)+h(n) f(n)=g(n)+h(n)
其中 f ( n ) f(n) f(n)是每个节点的代价值,它由两部分组成, g ( n ) g(n) g(n)表示从起点到搜索点的代价, h ( n ) h(n) h(n)表示从搜索点到目标点的代价, h ( n ) h(n) h(n)设计的好坏,直接影响到A*算法的效率。 h ( n ) h(n) h(n)的权重越大,收敛速度越快,但不能保证得到最优解。
在采用A*算法对迷宫路径求解中, g ( n ) g(n) g(n) h ( n ) h(n) h(n)函数都采用曼哈顿距离:
d ( i , j ) = ∣ x 1 − x 2 ∣ + ∣ y 1 − y 2 ∣ d(i,j)=|x_1−x_2|+|y_1−y_2| d(i,j)=x1x2+y1y2
即针对当前节点,都会计算其到起始节点和终止结点的曼哈顿距离。

具体代码

#include <algorithm>
#include <iostream>
#include <list>
#include <memory>
#include <vector>

struct PathNode {
  PathNode(const int row, const int col,
           const int f = std::numeric_limits<int>::max(),
           const std::shared_ptr<PathNode>& parent = nullptr)
      : row_(row), col_(col), f_(f), parent_(parent) {}

  bool operator==(const PathNode& other) const {
    return row_ == other.row_ && col_ == other.col_;
  }

  int row_;
  int col_;
  // f = g + h
  int f_;

  // parent node
  std::shared_ptr<PathNode> parent_;
};

void AStarSearch(const std::vector<std::vector<int>>& maze_mat,
                 std::list<std::shared_ptr<PathNode>>& solution);

int main() {
  int row, col;
  while (std::cin >> row && std::cin >> col) {
    std::vector<std::vector<int>> maze_mat(row, std::vector<int>(col));
    for (int i = 0; i < row; ++i) {
      for (int j = 0; j < col; ++j) {
        std::cin >> maze_mat[i][j];
      }
    }

    std::list<std::shared_ptr<PathNode>> solution;
    AStarSearch(maze_mat, solution);
    for (const auto& node : solution) {
      std::cout << "(" << node->row_ << "," << node->col_ << ")" << std::endl;
    }
  }

  return 0;
}

void AStarSearch(const std::vector<std::vector<int>>& maze_mat,
                 std::list<std::shared_ptr<PathNode>>& solution) {
  if (maze_mat.empty() || maze_mat[0].empty()) {
    return;
  }
  int start_row = 0;
  int start_col = 0;
  int end_row = maze_mat.size() - 1;
  int end_col = maze_mat[0].size() - 1;

  std::list<std::shared_ptr<PathNode>> open_list, closed_list;
  std::shared_ptr<PathNode> end_node = nullptr;
  auto start_node = std::make_shared<PathNode>(start_row, start_col);
  open_list.emplace_back(start_node);

  // Internal functions written in Lambda expressions
  auto calc_f_val_func = [&start_row, &start_col, &end_row, &end_col](
                             const int row, const int col) {
    int g_val = std::abs(row - start_row) + std::abs(col - start_col);
    int h_val = std::abs(end_row - row) + std::abs(end_col - col);
    // The heuristic coefficient has a great influence on the search speed. The
    // larger it is, the faster the convergence speed, but it cannot
    // guarantee the optimal solution is obtained.
    int h_coeff = 3;
    return g_val + h_coeff * h_val;
  };

  auto not_exist_func = [](const std::list<std::shared_ptr<PathNode>>& list,
                           std::shared_ptr<PathNode>& node) {
    return std::find(list.begin(), list.end(), node) == list.end();
  };

  auto handle_child_node_func = [&open_list, &closed_list, &start_row,
                                 &start_col, &end_row, &end_col, &maze_mat,
                                 &calc_f_val_func, &not_exist_func](
                                    const std::shared_ptr<PathNode>& cur_node,
                                    const int row, const int col) {
    if (row < start_row || col < start_col || row > end_row || col > end_col) {
      return;
    }
    // Wall
    if (maze_mat[row][col] > 0) {
      return;
    }

    auto child_node = std::make_shared<PathNode>(row, col);
    if (not_exist_func(open_list, child_node) &&
        not_exist_func(closed_list, child_node)) {
      child_node->f_ = calc_f_val_func(row, col);
      child_node->parent_ = cur_node;
      open_list.emplace_back(child_node);
    }
  };

  // A* algorithm
  while (!open_list.empty()) {
    // Get the node with minimal f value
    auto min_iter = std::min_element(
        open_list.begin(), open_list.end(),
        [](const std::shared_ptr<PathNode>& lhs,
           const std::shared_ptr<PathNode>& rhs) { return lhs->f_ < rhs->f_; });
    if (min_iter == open_list.end()) {
      break;
    }
    auto min_node = *min_iter;
    // std::cout << "min_node: " << min_node << ", " << min_node->row_ << ", "
    //           << min_node->col_ << ", " << min_node->f_ << std::endl;
    closed_list.emplace_back(min_node);
    open_list.erase(min_iter);

    if (min_node->row_ == end_row && min_node->col_ == end_col) {
      end_node = min_node;
      break;
    } else {
      // Handle child nodes in four directions.
      // North
      handle_child_node_func(min_node, min_node->row_ - 1, min_node->col_);
      // East
      handle_child_node_func(min_node, min_node->row_, min_node->col_ + 1);
      // South
      handle_child_node_func(min_node, min_node->row_ + 1, min_node->col_);
      // West
      handle_child_node_func(min_node, min_node->row_, min_node->col_ - 1);
    }
  }

  if (end_node != nullptr) {
    do {
      solution.emplace_back(end_node);
      end_node = end_node->parent_;
    } while (end_node != nullptr);

    // Reverse the list.
    solution.reverse();
  }
}
<think>好的,我现在需要帮助用户了解如何使用A*算法解决迷宫问题。首先,我应该回忆一下A*算法的基本概念和工作原理。A*算法结合了Dijkstra算法和贪心算法的优点,通过启发式函数来估计到目标的代价,从而高效地找到最短路径。 用户可能已经知道A*算法的一些基本知识,但需要具体的实现方法。我需要从迷宫表示开始,比如使用二维数组,0代表通路,1代表障碍。然后,节点的结构应该包含坐标、g值(实际代价)、h值(启发式估计)和f值(总和),以及父节点用于回溯路径。 接下来,算法流程是关键。需要详细说明开放列表和封闭列表的作用,以及每一步如何选择当前节点,扩展邻居节点,更新代价等。特别是要强调启发式函数的选择,比如曼哈顿距离或欧几里得距离,并确保这些函数是可接受的,即不会高估实际代价,这样才能保证找到最短路径。 在代码实现部分,提供一个Python示例会更直观。需要包括节点类、启发式函数、A*算法的核心循环,以及路径回溯的方法。同时,要注意处理边界情况,比如没有路径存在的情况,避免无限循环。 用户可能还会关心如何生成随机迷宫,或者在图形界面中的交互实现,但根据引用内容,用户主要关注A*算法的实现,所以这部分可以简要提及,或作为相关问题推荐。 最后,生成的相关问题需要覆盖性能优化、不同启发式函数的影响以及与其他算法的比较,帮助用户进一步学习。确保引用正确,引用标识放在相关段落末尾,如迷宫生成的算法引用[^2],A*的保证最短路径引用[^3]。 需要检查LaTeX格式是否正确,所有数学表达式用$...$,独立公式用$$...$$。例如,曼哈顿距离的公式要正确呈现。同时,代码块用Python语法高亮,保持示例代码简洁,没有错误。 总结步骤:迷宫表示、节点定义、算法流程、代码示例、注意事项、相关问题。确保结构清晰,逐步引导用户理解实现方法。</think>以下是如何使用A*算法解决迷宫问题的分步说明: ### 一、迷宫问题建模 1. **迷宫表示** 使用二维数组表示迷宫,例如: ```python maze = [ [0, 1, 0, 0], [0, 0, 0, 1], [1, 1, 0, 0], [0, 0, 0, 0] ] ``` - 0表示可通行路径 - 1表示障碍物 2. **节点定义** 每个节点需要存储以下信息: ```python class Node: def __init__(self, x, y): self.x = x # 坐标x self.y = y # 坐标y self.g = 0 # 起点到当前节点的实际代价 self.h = 0 # 当前节点到终点的启发式估计代价 self.f = self.g + self.h # 总代价 self.parent = None # 路径回溯指针 ``` ### 二、A*算法实现步骤 1. **初始化开放列表和封闭列表** - 开放列表(优先队列):存储待探索节点,按$f$值排序 - 封闭列表:存储已探索节点 2. **核心算法流程** ```python def astar(maze, start, end): open_list = [] closed_set = set() heapq.heappush(open_list, start) while open_list: current = heapq.heappop(open_list) if (current.x, current.y) == (end.x, end.y): return reconstruct_path(current) # 路径回溯 closed_set.add((current.x, current.y)) for neighbor in get_neighbors(maze, current): if (neighbor.x, neighbor.y) in closed_set: continue tentative_g = current.g + 1 if neighbor not in open_list or tentative_g < neighbor.g: neighbor.g = tentative_g neighbor.h = heuristic(neighbor, end) neighbor.f = neighbor.g + neighbor.h neighbor.parent = current if neighbor not in open_list: heapq.heappush(open_list, neighbor) return None # 无可行路径 ``` 3. **关键函数说明** - **启发式函数**:常用曼哈顿距离(适合4方向移动) $$h(n) = |x_{cur} - x_{end}| + |y_{cur} - y_{end}|$$ - **邻居节点获取**:检测上下左右四个方向的合法移动(需判断边界和障碍) ### 三、代码实现示例 ```python import heapq def heuristic(a, b): # 曼哈顿距离 return abs(a.x - b.x) + abs(a.y - b.y) def reconstruct_path(node): path = [] while node: path.append((node.x, node.y)) node = node.parent return path[::-1] # 示例用法 start = Node(0, 0) end = Node(3, 3) path = astar(maze, start, end) ``` ### 四、注意事项 1. **启发式函数选择** - 网格地图建议使用曼哈顿距离 - 允许对角线移动时可用欧几里得距离$h(n)=\sqrt{(x_2-x_1)^2 + (y_2-y_1)^2}$[^3] 2. **效率优化** - 使用优先队列(堆)维护开放列表,时间复杂度为$O(n \log n)$ - 采用更高效的数据结构如斐波那契堆可进一步优化 3. **路径平滑** 生成的路径可能存在锯齿状转折,可通过后处理算法进行平滑 [^1]: A*算法通过启发式搜索有效减少了不必要的节点扩展 [^2]: 迷宫生成可采用随机Prim算法或递归分割算法 [^3]: 曼哈顿距离在网格环境中是admissible heuristic,保证找到最优解
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值