图搜索算法在计算机科学中占有重要地位,特别是在路径规划和问题求解领域。最佳优先搜索(Best-First Search, BFS)和广度优先搜索(Breadth-First Search, BFS)是两种常用的图搜索算法。虽然它们的简称相同,但实现细节和应用场景各异。本文将详细介绍这两种算法及其变种,并提供Kotlin代码实现。
一、最佳优先搜索(Best-First Search)
最佳优先搜索是一类通过优先考虑某些节点而进行搜索的算法。它的目的是尽快找到解,通常通过某种评价函数来决定扩展节点的优先级。最佳优先搜索的两个常见变种是启发式搜索(Greedy Best-First Search)和A*算法(A-Star Search)。
启发式搜索(Greedy Best-First Search)
原理
启发式搜索是一种贪心算法,只考虑启发式函数,即从当前节点到目标节点的估计代价。每次选择启发式代价最小的节点扩展。
执行步骤
-
初始化:
- 将起始节点加入优先队列,代价为。
- 初始化已访问节点集合为空。
-
选择节点:
- 从优先队列中取出值最小的节点作为当前节点。
-
检查目标:
- 如果当前节点是目标节点,返回路径。
-
扩展节点:
- 扩展当前节点的邻接节点,计算值,并加入优先队列。
-
重复步骤2-4,直到找到目标节点或优先队列为空。
Kotlin代码实现
import java.util.PriorityQueue
data class Node(val name: String, val h: Int) : Comparable<Node> {
override fun compareTo(other: Node) = this.h.compareTo(other.h)
}
fun greedyBestFirstSearch(
graph: Map<String, List<Pair<String, Int>>>,
start: String,
goal: String,
heuristic: Map<String, Int>
): List<String> {
val openList = PriorityQueue<Node>()
val cameFrom = mutableMapOf<String, String>()
openList.add(Node(start, heuristic[start] ?: 0))
while (openList.isNotEmpty()) {
val current = openList.poll()
if (current.name == goal) {
val path = mutableListOf<String>()
var temp = goal
while (temp != start) {
path.add(temp)
temp = cameFrom[temp] ?: start
}
path.add(start)
return path.reversed()
}
for ((neighbor, _) in graph[current.name] ?: emptyList()) {
val neighborNode = Node(neighbor, heuristic[neighbor] ?: 0)
if (!cameFrom.containsKey(neighbor)) {
cameFrom[neighbor] = current.name
openList.add(neighborNode)
}
}
}
return emptyList()
}
A*算法(A-Star Search)
原理
A*算法是最佳优先搜索的改进版,它结合了启发式函数和从起点到当前节点的实际代价。 评价函数决定了节点的优先级。
执行步骤
-
初始化:
- 将起始节点加入优先队列,。
- 初始化已访问节点集合为空。
-
选择节点:
- 从优先队列中取出值最小的节点作为当前节点。
-
检查目标:
- 如果当前节点是目标节点,返回路径。
-
扩展节点:
- 扩展当前节点的邻接节点,计算值和值,并更新优先队列。
-
重复步骤2-4,直到找到目标节点或优先队列为空。
Kotlin代码实现
import java.util.PriorityQueue
data class AStarNode(val name: String, val g: Int, val h: Int) : Comparable<AStarNode> {
val f: Int get() = g + h
override fun compareTo(other: AStarNode) = this.f.compareTo(other.f)
}
fun aStarSearch(
graph: Map<String, List<Pair<String, Int>>>,
start: String,
goal: String,
heuristic: Map<String, Int>
): List<String> {
val openList = PriorityQueue<AStarNode>()
val cameFrom = mutableMapOf<String, String>()
val gScores = mutableMapOf<String, Int>().apply { put(start, 0) }
openList.add(AStarNode(start, 0, heuristic[start] ?: 0))
while (openList.isNotEmpty()) {
val current = openList.poll()
if (current.name == goal) {
val path = mutableListOf<String>()
var temp = goal
while (temp != start) {
path.add(temp)
temp = cameFrom[temp] ?: start
}
path.add(start)
return path.reversed()
}
for ((neighbor, cost) in graph[current.name] ?: emptyList()) {
val tentativeG = current.g + cost
if (tentativeG < gScores.getOrDefault(neighbor, Int.MAX_VALUE)) {
cameFrom[neighbor] = current.name
gScores[neighbor] = tentativeG
openList.add(AStarNode(neighbor, tentativeG, heuristic[neighbor] ?: 0))
}
}
}
return emptyList()
}
二、广度优先搜索(Breadth-First Search)
原理
广度优先搜索是一种遍历图或树的算法,它按层次逐层访问节点。BFS使用队列数据结构,确保每次访问距离起始节点最短的节点。BFS特别适用于无权图中的最短路径问题。
执行步骤
-
初始化:
- 将起始节点加入队列,并标记为已访问。
-
出队列:
- 从队列中取出一个节点作为当前节点。
-
访问邻接节点:
- 对于当前节点的每一个未访问的邻接节点,将其加入队列并标记为已访问。
-
重复步骤2-3,直到队列为空。
Kotlin代码实现
import java.util.ArrayDeque
fun breadthFirstSearch(graph: Map<String, List<String>>, start: String): List<String> {
val visited = mutableSetOf<String>()
val queue = ArrayDeque<String>()
val order = mutableListOf<String>()
queue.add(start)
visited.add(start)
while (queue.isNotEmpty()) {
val current = queue.removeFirst()
order.add(current)
for (neighbor in graph[current] ?: emptyList()) {
if (neighbor !in visited) {
visited.add(neighbor)
queue.add(neighbor)
}
}
}
return order
}
三、区别及应用场景
区别
1. 搜索策略:
- 启发式搜索:只考虑启发式代价,不考虑实际代价,可能不保证最优解。
- A*算法:同时考虑和,能保证最优解(若是可接受的)。
- 广度优先搜索:无偏向的遍历算法,保证在无权图中找到最短路径。
2. 数据结构:
- 启发式搜索和A*算法:使用优先队列。
- 广度优先搜索:使用普通队列。
3. 复杂度:
- 启发式搜索和A*算法:时间复杂度取决于启发式函数的质量和具体实现。
- 广度优先搜索:时间复杂度为O(V + E),空间复杂度与节点和边的数量有关。
应用场景
1. 启发式搜索:
- 适用于启发式函数能较好估计的场景,如迷宫求解、路径规划等。
2. A*算法:
- 广泛用于路径规划、游戏AI、导航等领域,尤其是在需要最优解的情况下。
3. 广度优先搜索:
- 适用于无权图中的最短路径问题,如社交网络分析、网络爬虫等。
四、总结
最佳优先搜索和广度优先搜索是图搜索中的基本算法,它们各有优缺点和适用场景。启发式搜索是一种简单的贪心算法,A*算法在此基础上保证了最优解,而广度优先搜索则在无权图中有较好的性能和效果。了解和掌握这些算法的原理和实现,有助于在实际应用中选择