使用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)f(n)是每個節點的代價值,它由兩部分組成,g(n)g(n)表示從起點到搜索點的代價,h(n)h(n)表示從搜索點到目標點的代價,h(n)h(n)設計的好壞,直接影響到A*算法的效率。h(n)h(n)的權重越大,收斂速度越快,但不能保證得到最優解。
在採用A*算法對迷宮路徑求解中,g(n)g(n)h(n)h(n)函數都採用曼哈頓距離:
d(i,j)=x1x2+y1y2d(i,j)=|x_1−x_2|+|y_1−y_2|
即針對當前節點,都會計算其到起始節點和終止結點的曼哈頓距離。

具體代碼

#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();
  }
}
發佈了83 篇原創文章 · 獲贊 267 · 訪問量 43萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章