使用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 
#include 
#include 
#include 
#include 

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();
  }
}

你可能感兴趣的:(C++)