进阶搜索算法 学习笔记

RainPPR / 2023-09-03 / 原文

进阶搜索算法

前情提要~

  1. 双向广搜、双向深搜
  2. 堆优化的 Dijkstra
  3. 一颗小小的 A-STAR
  4. 不大聪明的 IDDFS(IDS)
  5. 可爱的 IDA-STAR

广搜、深搜

这是进阶搜索算法,不说了直接上例题

以“P1514 引水问题”为例:https://www.luogu.com.cn/paste/zo5e6pal

双向广搜、双向深搜

算法思想

  • 在搜索的时候,搜索出来的树太大了
  • 从起始状态和目标状态同时开始搜索一定层数
  • 把搜索出来的所有状态扔到 hash 表里面,看看有没有重复的
  • 能够提升一倍答案的效率,比如原来复杂度是 $O(2^n)$,现在可以变成 $O(2^{n / 2})$,相当于复杂度开根号

代码

以“U319719 八城堡问题”为例:https://www.luogu.com.cn/paste/uicfza9m

堆优化的 Dijkstra

算法思想

考虑当前走过的距离,不考虑剩下的距离,当前走的少的优先考虑

优先队列里不允许出现相同的元素,但是同一个元素可以入队和出队多次,当第二及更多次入队是,必然是遇到了更加优化的路径(到起点的距离更近)

代码

以“P3371 P4779 单源最短路径”为例:https://www.luogu.com.cn/paste/vzupxcqq

一颗小小的 A-STAR

基础知识

  • 估价函数 $f(i) = g(i) + h(i)$,每次要选取 $f(i)$ 最小的更新
  • $g(i)$:从起始状态到当前状态 $i$ 的代价
  • $h(i)$:从当前状态 $i$ 到目标状态的估计代价

基础思想

好东西:https://zhuanlan.zhihu.com/p/54510444

重点在设计估价函数,估价函数 $h(i)$ 若选取不当,则可能找不到解,或找到的解也不是最优解。

  • 定义 $h^*(i)$ 为从当前状态 $n$ 到目标状态的实际代价
  • 必须满足 $h(i) \le h^*(i)$,否则嘿嘿嘿
  • 估计总是过于乐观的

常见的估价函数

对于网格形式的图,有以下这些启发函数可以使用:

  1. 如果图形中只允许朝上下左右四个方向移动,则可以使用曼哈顿距离
function heuristic(node) =
	dx = abs(node.x - goal.x)
	dy = abs(node.y - goal.y)
	return D * (dx + dy)
  1. 如果图形中允许朝八个方向移动,则可以使用对角距离
function heuristic(node) =
	dx = abs(node.x - goal.x)
	dy = abs(node.y - goal.y)
	return D * (dx + dy) + (D2 - 2 * D) * min(dx, dy)
	// 这里的D2指的是两个斜着相邻节点之间的移动代价
  1. 如果图形中允许朝任何方向移动,则可以使用欧几里得距离
function heuristic(node) =
	dx = abs(node.x - goal.x)
	dy = abs(node.y - goal.y)
	return D * sqrt(dx * dx + dy * dy)
  1. 对于类似八数码问题的,可以使用各数码使用距离目标位置的曼哈顿距离之和
int f(string state)
{
	int res = 0;
	for (int i = 0; i < 9; ++i)
	{
		if (state[i] != '0')
		{
			int t = tar[state[i] - '1'];
			res += abs(i / 3 - t / 3) + abs(i % 3 - t % 3);
		}
	}
	return res;
}
  1. 对于数列(二维也可以)问题的,可以使用有多少个元素位置不正确,或其前驱、后继不正确
int f(string state)
{
	int res = 0;
	for (int i = 0; i < 9; ++i)
		res += state[i] != gend[i];
	return res;
}
int f()
{
	int res = 0;
	for (int i = 0; i + 1 < n; ++i)
		if (q[i + 1] != q[i] + 1)
			++res;
	return (res + 2) / 3;
}

设计要求

  • 不要太过分就好,一般来说没问题的
  • 脑洞要大

代码

以“P1379 八数码问题”为例:https://www.luogu.com.cn/paste/9olegdye

不大聪明的 IDDFS(IDS)

算法思想

迭代加深是一种每次限制搜索深度的深度优先搜索,目的是寻找最优解。

  • 给出一个限制 limit,规定:当 搜索层数 > limit 时直接剪枝
  • 在最外层 循环枚举 limit,如果无解就继续

特点

缺点:重复计算

与BFS的区别:BFS 的基础是队列,空间复杂度大,当状态比较多或者单个状态比较大时,迭代加深就类似于用 DFS 方式实现的 BFS,空间复杂度相对较小。

在大多数的题目中,广度优先搜索还是比较方便的,而且容易判重。当发现广度优先搜索在空间上不够优秀,而且要找最优解的问题时,就应该考虑迭代加深。

代码

以“LOJ10021 Addition Chains(加成序列问题)”为例:https://www.luogu.com.cn/paste/y81dwnc5

可爱的 IDA-STAR

算法思想

IDA-STAR 为采用了迭代加深(IDDFS)算法的 A-STAR 算法。

所以,在一般的问题中是这样使用 IDA-star 算法的:

  • (类似 IDDFS)循环枚举 limit,当$h(i) + g(i) >$ limit 时, 就停止继续往下搜索
  • 写成代码就是:if depth + h() > limit then return;
  • 没了

估价函数

往上门口(见 A-STAR)

特点

  • 省空间:各种游戏求最少步数,普通搜索会爆炸
  • 省时间:不需要判重,不需要排序,利于深度剪枝
  • 费时间(?):每次深度变大都要再次从头搜索,有时可能双向广搜会更快

代码

以“P2324 骑士精神”为例:https://www.luogu.com.cn/paste/71rlu8wf

题单推荐

  • https://www.luogu.com.cn/training/348219(来自我老师 QwQ)

部分资料来源

  • https://github.com/huzecong/oi-slides
  • https://zhuanlan.zhihu.com/p/54510444
  • https://oi-wiki.org/
  • https://www.acwing.com/