在之前文章中我们已经介绍了一些全遍历的搜索算法,详细情况可以这篇文章深度优先(DFS)与广度优先(BFS)附Python代码与具体应用_深度优先搜索的时间复杂度-CSDN博客,我们继续来看一种全遍历方法-统一代价搜索(Uniform Cost Search),以及启发式算法。启发式搜索是一种更智能的搜索方法,它通过使用启发式函数来估计每个节点到目标节点的距离,并据此指导搜索。它不像深度搜索和广度搜索那样简单地探索所有可能的路径,而是试图优先探索最有希望的路径。启发式搜索通常使用优先队列来维护待探索的节点,以保证先探索距离目标节点更近的节点。总的来说,深度搜索注重深度优先探索,广度搜索注重逐层地探索,而启发式搜索注重根据启发式函数指导探索
统一代价搜索 UCS (Uniform Cost Search)
思想: UCS 算法是一种无信息搜索算法,它只考虑实际路径的成本,不使用任何启发式信息。
时间复杂度: UCS 算法的时间复杂度取决于问题的规模和图的结构。在最坏情况下,它可能需要指数级别的时间来搜索全部可能的路径。
空间复杂度: UCS 算法的空间复杂度取决于问题的规模和所使用的数据结构。与 A* 算法相比,UCS 不需要维护额外的启发式信息,因此空间复杂度可能更低。
Optimality (最优性): 与 A* 算法相同,如果搜索到解决方案,UCS 算法能够保证找到最优解的路径。
Completeness (完备性): 与 A* 算法相同,UCS 算法在有限的空间和时间内能够找到解决方案的概率很高,但并不是绝对的。
def ucs(graph, start, goal):
frontier = PriorityQueue()
frontier.put(start, 0)
came_from = {}
cost_so_far = {}
came_from[start] = None
cost_so_far[start] = 0
while not frontier.empty():
current = frontier.get()
if current == goal:
break
for next_node in graph.neighbors(current):
new_cost = cost_so_far[current] + graph.cost(current, next_node)
if next_node not in cost_so_far or new_cost < cost_so_far[next_node]:
cost_so_far[next_node] = new_cost
frontier.put(next_node, new_cost)
came_from[next_node] = current
return reconstruct_path(came_from, start, goal)
Heuristic Search (Heuristic Function)
思想: 启发式搜索使用启发式函数来指导搜索过程。启发式函数提供了每个节点到目标节点的“好”的猜测,以加速搜索过程。
时间复杂度: 启发式搜索的时间复杂度取决于所使用的启发式函数和问题的复杂度。
空间复杂度: 启发式搜索的空间复杂度取决于问题的规模和所使用的数据结构。
Optimality (最优性): 启发式搜索不一定保证找到最优解,因为它的效果取决于启发式函数的质量。如果启发式函数是一种良好的估计,那么搜索结果有可能是最优的。
Completeness (完备性): 启发式搜索也不一定保证在有限的时间内找到解决方案,因为它可能会受到启发式函数的限制。
A* (A Star)
思想: A* 算法结合了 Dijkstra 算法的广度优先搜索和贪婪最佳优先搜索的思想。它使用启发式函数来评估每个节点,以选择最有希望的节点进行扩展。
时间复杂度: A* 算法的时间复杂度取决于所使用的启发式函数和问题的复杂度。在最坏情况下,它可能需要指数级别的时间来搜索全部可能的路径。但在实际应用中,通常能够找到最优解的路径,尤其是在启发式函数能够提供良好估计的情况下。
空间复杂度: A* 算法的空间复杂度也取决于问题的规模,以及所使用的数据结构。在搜索过程中,需要维护一个优先队列来存储待扩展的节点,因此空间复杂度可能是指数级别的。
Optimality (最优性): 如果所使用的启发式函数满足一定的条件(即启发式函数不能过于乐观),A* 算法能够保证找到最优解的路径。
Completeness (完备性): A* 算法在有限的空间和时间内能够找到解决方案的概率很高,但并不是绝对的。它在启发式函数
def astar(graph, start, goal, heuristic):
frontier = PriorityQueue()
frontier.put(start, 0)
came_from = {}
cost_so_far = {}
came_from[start] = None
cost_so_far[start] = 0
while not frontier.empty():
current = frontier.get()
if current == goal:
break
for next_node in graph.neighbors(current):
new_cost = cost_so_far[current] + graph.cost(current, next_node)
if next_node not in cost_so_far or new_cost < cost_so_far[next_node]:
cost_so_far[next_node] = new_cost
priority = new_cost + heuristic(goal, next_node)
frontier.put(next_node, priority)
came_from[next_node] = current
return reconstruct_path(came_from, start, goal)
def reconstruct_path(came_from, start, goal):
current = goal
path = []
while current != start:
path.append(current)
current = came_from[current]
path.append(start)
path.reverse()
return path
能够提供合理估计的情况下,通常是完备的。