本部分本书的举例不老套,线索较完整。所以本人便大致依照本书的来讲的,但伪代码还是改为了python代码以便大家理解。
最佳优先搜索,扩展评估函数f(n)最小的结点
评估函数f(n)=g(n)+h(n)
评估函数 :g(n)=0,f(n)=h(n),扩展“离目标最近”的结点“离目标最近”是一个估计值
A搜索是A搜索的变体,A算法具有以下性质:假如问题有解,则A*算法一定能找到最优解。
A*搜索如何保证最优性
可采纳性
一致性/单调性
定理1: 如果h(n)是可采纳的,那么A*的树搜索版本是最优的
定理2: 如果h(n)是一致的,那么A*的图搜索版本是最优的,即一定能找到最优解
import heapq # 用于实现优先队列
def Astar(graph, start, goal, heuristic):
queue = [] # 初始化优先队列
visited = set() # 初始化已访问节点的集合
# 将起始节点的路径以及到达该节点的总成本(启发式估计值)加入队列
# (路径成本+启发式估计值,路径)作为元组存入队列,路径成本+启发式估计值作为优先级
heapq.heappush(queue, (0 + heuristic[start], [start]))
while queue: # 当队列不为空时
(cost, path) = heapq.heappop(queue) # 取出当前成本+启发式估计值最小的路径
node = path[-1] # 获取路径中的最后一个节点
if node not in visited: # 如果该节点没有被访问过
if node == goal: # 如果该节点是目标节点
return path # 返回该路径
visited.add(node) # 将该节点加入已访问节点的集合
for neighbor in graph[node]: # 遍历当前节点的所有邻居节点
new_cost = cost + graph[node][neighbor] # 计算新路径的总成本
new_path = list(path) # 创建一个新路径
new_path.append(neighbor) # 将邻居节点加入新路径
heapq.heappush(queue, (new_cost + heuristic[neighbor], new_path)) # 将新路径加入队列
return "无解" # 如果没有从起始节点到目标节点的路径,则返回"无解"
八数码问题
在一个 3×3的网格中,1∼8这 8个数字和一个 X 恰好不重不漏地分布在这 3×3的网格中。把 X 与上下左右方向数字交换的行动记录为 u、d、l、r。现在,给你一个初始网格,请你通过最少的移动次数,得到正确排列。
string Astar(string start)
{
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
char op[4] = {'u', 'r', 'd', 'l'};
string end = "12345678x";
unordered_map<string, int> dist;
unordered_map<string, pair<string, char>> prev;
priority_queue<pair<int, string>, vector<pair<int, string>>, greater<pair<int, string>>> heap;
heap.push({f(start), start});
dist[start] = 0;
while (heap.size())
{
auto t = heap.top();
heap.pop();
string state = t.second;
if (state == end) break;
int step = dist[state];
int x, y;
for (int i = 0; i < state.size(); i ++ )
if (state[i] == 'x')
{
x = i / 3, y = i % 3;
break;
}
string source = state;
for (int i = 0; i < 4; i ++ )
{
int a = x + dx[i], b = y + dy[i];
if (a >= 0 && a < 3 && b >= 0 && b < 3)
{
swap(state[x * 3 + y], state[a * 3 + b]);
if (!dist.count(state) || dist[state] > step + 1)
{
dist[state] = step + 1;
prev[state] = {source, op[i]};
heap.push({dist[state] + f(state), state});
}
swap(state[x * 3 + y], state[a * 3 + b]);
}
}
}
string res;
while (end != start)
{
res += prev[end].second;
end = prev[end].first;
}
reverse(res.begin(), res.end());
return res;
}
迭代加深的A*算法
迭代加深的A算法(IDA)是一种启发式搜索算法,通过逐渐增加搜索深度的方式,在较小的内存消耗下逼近最优解。它结合了深度优先搜索和A*算法的思想,使用启发式函数评估节点代价,并剪枝超过当前最优解代价上界的节点,直到找到最优解或达到搜索限制
def ida_star_search(root, heuristic, cost_limit):
cost_limit = heuristic(root) # 初始化代价上界为启发式函数的值
while True:
result, cost_limit = depth_limited_search(root, heuristic, cost_limit)
if result is not None:
return result
def depth_limited_search(node, heuristic, cost_limit):
if node is None:
return None, cost_limit
if heuristic(node) == 0:
return node, cost_limit
if heuristic(node) > cost_limit:
return None, heuristic(node) # 返回节点的启发式函数值作为新的代价上界
min_cost = float('inf')
for child in expand(node):
result, new_cost_limit = depth_limited_search(child, heuristic, cost_limit)
if result is not None:
return result, cost_limit
min_cost = min(min_cost, new_cost_limit)
return None, min_cost
递归最佳优先搜索
def recursive_best_first_search(node, goal, heuristic, f_limit):
if node == goal:
return [node], f_limit # 找到目标节点,返回当前节点作为解决方案和当前节点的f值
neighbors = get_neighbors(node) # 获取当前节点的邻居节点
if not neighbors:
return None, float('inf') # 没有邻居节点,无解,返回无穷大的f值
# 根据启发式评估函数对邻居节点进行排序
sorted_neighbors = sorted(neighbors, key=lambda n: heuristic(n, goal))
best_f = float('inf') # 最佳f值,默认为无穷大
best_path = None # 最佳路径,默认为空
for neighbor in sorted_neighbors:
f = heuristic(neighbor, goal)
if f > f_limit:
return None, f # 当前节点的f值超过了f_limit,返回无解和当前节点的f值
result, f = recursive_best_first_search(neighbor, goal, heuristic, f_limit)
if result is not None and f < best_f:
best_f = f
best_path = [node] + result # 更新最佳f值和路径
return best_path, best_f # 返回最佳路径和最佳f值
两个常用的启发式函数
刻画启发式函数的因素
占优
对于任意结点n, h1(n)<=h2(n),称h2比h1占优
定理3: 若h2比h1占优,采用h2的A算法永远不会比采用h1的A算法扩展更多的结点
每个满足f(n)
松弛问题:减少动作限制的问题
例. 八数码问题中,若允许每个棋子任意移动位置,则h1是精确步数
例. 八数码问题中,若允许每个棋子可以移动到被其它棋子占据的位置,则h2是精确步数
原问题与松弛问题的关联
利用松弛问题的最优解代价设计启发式函数
例. 八数码问题的松弛问题
新的启发式函数的构造
假定已经设计了无法比较的可采纳的启发式函数h1,h2,…,hm,则可以定义新的启发式函数h(n)=max{h1(n),h2(n),…,hm(n)}
利用子问题的最优解代价设计启发式函数
h(n)=hsub*(n)<=h*(n),f(n)<=f*(n),故该启发式函数是可采纳的
从经验中学习构造启发式h(n),即求解大量的八数码问题,每个八数码问题的最优解都成为可供h(n)学习的实例。也就是说,从例子中,学习算法构造h(n),它能够预测搜索过程中所出现的其他状态的解代价,如n结点的h(n)的表达式。这里的学习算法可以采用包括神经网络、决策树或其他学习技术。
本章介绍了在确定性的、 可观察的、静态的和完全可知的环境下,Agent 可以用来选择行动的方法。在这种情况下,Agent可以构造行动序列以达到目标;这个过程称为搜索。
在这种环境下,问题由形式化由良定义问题和搜索树抽象为图的问题,再根据问题的规模和要求以及搜索策略的性质选择搜索策略求的解。
搜索策略可分为无信息搜索和有信息搜索,无信息搜索中三个基础的搜索是dfs,bfs,ucs,他们之间差别边缘表的不同,dfs使用栈存储待扩展的节点,bfs使用队列存储待扩展的节点,ucs使用优先队列存储待扩展的节点,深度受限和迭代加深往往再一起使用,它俩是dfs的变体,迭代加深利用深度受限逐渐增加深度来实现Bfs的效果 双向搜索可利用dfs或bfs同时搜索减少时间复杂度。
有信息搜索和无信息搜索的区别在于是否有启发函数,而有信息搜索的内部的区别便是评估函数,如果f(x) = 0 ,则是贪婪最佳,如果f(x) != 0,最佳搜索(A搜索),在A搜索的基础上如果h(x)能保证一致性和可采纳性则是A*搜索。
启发函数的设计的好坏往往很能影响搜索的效率,设计启发函数利用松弛问题、子问题、经验中学习等方式。