严正声明:本文系作者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],即第一空格是可以走的路。
一个N × M的二维数组,表示一个迷宫。数据保证有唯一解,不考虑有多解的情况,即迷宫只有一条通道。
左上角到右下角的最短路径,格式如样例所示。
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
(0, 0)
(1, 0)
(2, 0)
(2, 1)
(2, 2)
(2, 3)
(2, 4)
(3, 4)
(4, 4)
输入两个整数,分别表示二位数组的行数,列数。再输入相应的数组,其中的1表示墙壁,0表示可以走的路。数据保证有唯一解,不考虑有多解的情况,即迷宫只有一条通道。
左上角到右下角的最短路径,格式如样例所示。
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
集中取出最好的结点n
(f
值最小的结点)并检查之。如果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)=∣x1−x2∣+∣y1−y2∣
即针对当前节点,都会计算其到起始节点和终止结点的曼哈顿距离。
#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, ¬_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();
}
}