10.2 Best-First Search (最佳优先搜索)算法
Best-First Search(最佳优先搜索)是一种启发式搜索算法,它每次扩展具有最小启发式值的节点。与传统的深度优先搜索或广度优先搜索不同,最佳优先搜索不考虑已经走过的路径,只是简单地选择当前最有希望的节点进行扩展。
10.2.1 Best-First Search算法的基本思想
最佳优先搜索(Best-First Search)的基本思想是每次扩展具有最小启发式值的节点,直到找到目标节点或者达到搜索的终止条件。这个算法通常用于解决最优化问题,例如在路径规划中寻找最短路径或者在游戏中寻找最佳解。在搜索过程中,最佳优先搜索不保留之前访问的节点,因此可能会陷入无限循环或者无法找到解的情况,但它通常能够以较快的速度找到一个接近最优解的解。
最佳优先搜索算法的基本思路如下所示。
(1)启发式评估:在搜索过程中,每个节点都会被分配一个启发式值,这个值是根据一个启发式函数计算得到的。启发式函数用来估计从当前节点到目标节点的代价或者潜在价值,通常是一个启发性的估计,并不一定是准确的。
(2)节点扩展:算法每次选择具有最小启发式值的节点进行扩展。换句话说,它倾向于首先探索那些离目标节点最近的节点,希望能够尽快找到解决方案。这就是为什么它被称为“最佳”优先搜索,因为它总是选择当前看起来最有希望的路径。
(3)搜索终止:算法会一直扩展节点直到找到目标节点,或者搜索空间已经完全被探索,没有更多的节点可供扩展。在前者情况下,会找到解决方案;而在后者情况下,可能意味着没有找到解决方案,或者搜索空间太大,算法无法在可接受的时间内完成搜索。
在实现最佳优先搜索时,通常需要定义一个启发式函数(heuristic function),用于评估每个节点的潜在价值。这个启发式函数通常是一种估计,它估计从当前节点到目标节点的代价。基于这个启发式函数的值,算法可以选择具有最小启发式值的节点进行扩展。
总的来说,最佳优先搜索算法的基本思想是在搜索过程中根据节点的启发式值选择下一个要扩展的节点,以期望能够更快地找到解决方案。然而,需要注意的是,由于启发式函数可能并不总是准确的,因此最佳优先搜索并不保证总是能够找到最优解,而可能会停留在局部最优解。
注意:最佳优先搜索的一个常见应用是在人工智能的领域,特别是在问题求解、规划和游戏中。例如,在迷宫寻路问题中,可以使用最佳优先搜索来找到从起点到终点的最短路径。然而,需要注意的是,最佳优先搜索并不保证总是能够找到最优解,因为它只是简单地选择当前最有希望的节点进行扩展,而不考虑已经走过的路径。
10.2.3 Best-First Search算法的实现步骤
具体来说,实现Best-First Search算法的基本步骤如下所示。
(1)定义节点结构:创建一个表示节点的结构体或类,其中包括节点的位置坐标、距离起点的实际代价、到目标节点的启发式值等信息。此外,需要重载比较运算符,以便在优先队列中对节点进行排序。
(2)初始化起始节点:将起始节点添加到优先队列中,初始化其实际代价和启发式值。
(3)循环搜索:不断从优先队列中取出具有最小启发式值的节点进行扩展,直到找到目标节点或者搜索空间已经完全探索。
(4)扩展节点:对于当前节点,根据问题的规则生成所有可能的后继节点,并计算它们的实际代价和启发式值。将这些后继节点加入到优先队列中,按照启发式值的大小进行排序。
(5)判断终止条件:检查每次从优先队列中取出的节点是否为目标节点,如果是,则算法结束;如果优先队列为空,表示没有找到目标节点,算法结束。
(6)标记节点状态:在搜索过程中,需要记录已经访问过的节点,以避免重复访问和陷入循环。可以使用一个标记数组或者哈希表来记录节点的访问状态。
(7)返回结果:如果找到了目标节点,可以返回到达目标节点的最短路径或者最优解的相关信息。如果搜索失败,可以返回相应的失败信息或者特定的错误代码。
以上步骤是Best-First Search算法的一般实现过程。在具体应用中,可能需要根据问题的特点进行适当的调整和优化。例如下面是一个简单的 C++例子,演示了使用最佳优先搜索算法来解决迷宫寻路问题的过程。在这个例子中,我们假设迷宫是一个二维数组,其中 0 表示可以通过的空格,1 表示障碍物。我们的目标是从起点 (startX, startY) 到达终点 (endX, endY)。
实例10-1:使用Best-First Search算法解决迷宫问题(codes/10/Mi.cpp)
实例文件Mi.cpp的具体实现代码如下所示。
#include <iostream>
#include <queue>
#include <vector>
#include <cmath>
using namespace std;
// 定义迷宫的大小
#define ROW 5
#define COL 5
// 定义节点的结构体
struct Node {
int x, y; // 节点坐标
int distance; // 起点到当前节点的距离
int heuristic; // 启发式值
Node(int _x, int _y, int _distance, int _heuristic) : x(_x), y(_y), distance(_distance), heuristic(_heuristic) {}
// 重载小于运算符,用于优先队列的排序
bool operator<(const Node& other) const {
return (distance + heuristic) > (other.distance + other.heuristic);
}
};
// 定义最佳优先搜索算法
int bestFirstSearch(int maze[][COL], int startX, int startY, int endX, int endY) {
// 定义四个方向的移动偏移量
int dx[] = {-1, 1, 0, 0};
int dy[] = {0, 0, -1, 1};
// 定义一个优先队列,用于节点扩展
priority_queue<Node> pq;
// 将起点加入优先队列
pq.push(Node(startX, startY, 0, abs(endX - startX) + abs(endY - startY)));
// 创建一个二维数组,用于记录节点是否被访问过
vector<vector<bool>> visited(ROW, vector<bool>(COL, false));
while (!pq.empty()) {
// 取出当前优先级最高的节点
Node current = pq.top();
pq.pop();
int x = current.x;
int y = current.y;
int distance = current.distance;
// 到达终点,返回距离
if (x == endX && y == endY) {
return distance;
}
// 将当前节点标记为已访问
visited[x][y] = true;
// 在四个方向上尝试扩展节点
for (int i = 0; i < 4; i++) {
int nx = x + dx[i];
int ny = y + dy[i];
// 如果新节点在迷宫范围内且未被访问过且可通行,则加入优先队列
if (nx >= 0 && nx < ROW && ny >= 0 && ny < COL && !visited[nx][ny] && maze[nx][ny] == 0) {
pq.push(Node(nx, ny, distance + 1, abs(endX - nx) + abs(endY - ny)));
}
}
}
// 没有找到路径,返回 -1
return -1;
}
int main() {
int maze[ROW][COL] = {
{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}
};
int startX = 0, startY = 0;
int endX = 4, endY = 4;
int distance = bestFirstSearch(maze, startX, startY, endX, endY);
if (distance == -1) {
cout << "No path found." << endl;
} else {
cout << "Shortest distance from start to end: " << distance << endl;
}
return 0;
}
对上述代码的具体说明如下所示:
- 首先,在代码的开始处包含了必要的头文件和命名空间声明,以及预处理器定义迷宫的大小,ROW和COL分别表示迷宫的行数和列数。
- 然后,定义了节点结构体 Node,它包含了节点的坐标信息 (x, y),起点到当前节点的距离 distance,以及节点的启发式值 heuristic。同时,重载了小于运算符,以便在优先队列中对节点进行排序。
- 接着,实现了最佳优先搜索算法 bestFirstSearch,其中使用了一个优先队列 priority_queue<Node> 用于节点扩展。优先搜索算法算法首先将起点加入优先队列,并创建一个二维数组 visited 用于记录节点是否被访问过。然后,在循环中不断从优先队列中取出具有最小启发式值的节点进行扩展,直到找到目标节点或者搜索空间已经完全探索。在节点扩展过程中,算法会在四个方向上尝试扩展节点,并将符合条件的新节点加入到优先队列中。
- 最后,在主函数 main() 中定义了一个迷宫 maze,并调用 bestFirstSearch 函数来寻找从起点到终点的最短路径。根据返回的距离值,输出了从起点到终点的最短距离或者提示找不到路径。
整体上,本实例实现了使用最佳优先搜索算法解决迷宫最短路径问题的功能。执行后会输出:
Shortest distance from start to end: 8