算法设计与分析——树搜索算法

树搜索算法

树搜索策略的引入

三个基本问题:

  1. 什么样的问题能转化为树?
  2. 转化为树有什么好处?
  3. 转化后的树节点/边有什么含义,代表了什么?

以下面的布尔表达式、8-Puzzle问题、寻找哈密顿环为例,解答这三个问题

布尔表达式可满足性问题

image-20231222160428130

输入:n个布尔变量x1,x2,···,xn;关于这n个变量的k个析取布尔式

输出:找到一种使这k个布尔析取式均为真的一种x1~xn的赋值

对于这个问题,最开始想到的就是穷举了,将所有x1~xn的赋值情况都列出来,然后选择那些满足k个析取式为真的赋值组合。问题是如何穷举,怎么穷举有次序,达到不重不漏

首先想到的是按照x1~xn顺序去赋值:首先给x1进行赋值(T/F),然后继续给x2进行赋值(T/F)····如果到了xi,发现不能使这k个析取式均为真,则重新对xi进行赋值。最后得到一组赋值组合,使这k个析取式为真

我们可以将这个问题转化为树,利用树的路径是唯一的,保证某个赋值组合只被穷举过一次;而且可以通过从叶子节点回溯到root根节点,回溯得到这个赋值组合;如果当前对xk的赋值不能满足使析取式为真,则可以回溯到它的父亲节点,进行另一种赋值

image-20231222161712418

将树的边作为对xi的赋值情况,树中每一个节点可以看做是经过从根节点到该节点的赋值过程后这k个析取式的状态(为真/为假)

最终可以从叶子节点回溯到root节点,记录各边的赋值情况,即为该问题的一个可行解

image-20231222162925961

8-Puzzle问题

image-20231222162943857

输入:具有8个编号小方块的魔方

输出:移动系列,经过这些移动,魔方可以达到如上图所示的状态

同样思考穷举,每次移动一个编号,找最终可以达到上面这种状态的移动系列

image-20231222163311042

将问题转化为树,每次只移动一个编号,树的节点代表了当前魔方的状态,由于每一步的状态都依赖于上一步的状态,这种关系可以用树中的父子关系来表示——将父亲节点代表的魔方状态移动某一个编号,即为该父亲节点的儿子节点。最终的解可以从叶子节点回溯到根节点得到

哈密顿环问题

image-20231222163808167

输入: 具有n个节点的连通图G=(V, E)

输出: G中是否具有Hamiltonian环

image-20231222163837016

这是NP-hard问题,可以使用穷举进行求解。思路:从一个节点出发,然后到达与该点邻接的节点,若遍历所有节点并最终回到起点,则找到了一条哈密顿环,若路径的中间重复出现了某个非起点的节点,或者不能回到起点,则说明该路径不能获得一条哈密顿环

image-20231222165242839

将问题转化为树:将当前遍历到的节点用树的节点表示,若最终遍历到root节点,则从该叶子结点回溯到root节点,可以得到这条哈密顿环

一个规模更小的例子:

image-20231222165539149

总结

由这三个问题的例子,可以得到树搜索问题的基本特征:

  1. 什么样的问题能转化为树?
    • 需要穷举得到解
    • 每一步的状态都依赖于上一步的状态,可以用树中的父子节点表示这种关系
    • 问题的解是一个系列,需要回溯得到,树中从叶子到root节点的回溯可以得到这样的解
  2. 转化为树有什么好处?
    • 从树的root节点到某个叶子节点的路径是唯一的,这保证了穷举的唯一性,即某一个解系列不会被重复列举,做到了不重
    • 树的节点可以表示状态,树的边可以表示操作,通过树的搜索过程,能将所有的情况都穷举出来,做到了不漏
    • 若某一步操作/状态不能满足约束,可以在树中回溯节点(经过的节点会储存在栈或队列中),回到某个仍然满足约束的节点,然后选择另一条路径,继续进行搜索
    • 树中的一条路径就代表一个穷举过程。最终的解是树中一条从叶子节点回溯到root节点的路径
  3. 转化后的树有什么含义(怎么设计出这个树)?
    • 树的节点可以表示状态,树的边可以表示操作
    • 首先要确定出穷举策略,然后用树的节点/边对应穷举过程中的状态/操作
    • 适当的树搜索策略对节点进行展开,获得问题的可行解

下面介绍两种启发式搜索策略,它们能有目的地展开特定的节点,从而尽可能提高效率,获得可行解。注意:只是可行解,不是最优解

基本的树搜索策略

image-20231222171900556

就是BFS与DFS

BFS搜索策略

image-20231222171925558

image-20231222171950348

算法过程与原来的BFS没什么区别,就是要增加一个判断:看当前队列Q中的第一个元素x是否为目标节点。如果是,则结束BFS,获得一个可行解,否则就继续执行BFS

DFS搜索策略

image-20231222172146435

image-20231222172155167

从S中挑出一个数字集合,求和等于9

树的节点代表当前和的状态(Sum)

树的边代表加上某个数字的操作(+num)

image-20231222172201471

与BFS搜索策略一样,如果DFS维护的栈中的栈顶元素Top(S)为目标节点时,就停止DFS搜索,否则继续进行DFS

这两种策略都有盲目性,穷举最大的特点就是问题的规模会很大,很难解决,如果盲目地搜索,时间复杂度会非常高

优化的树搜索策略

image-20231222172751345

第三个分支限界搜索是专门针对组合优化问题,它是爬山法和best-first策略的进一步拓展,所以它的基本策略还是爬山法或best-first策略

爬山法

基本思想

爬山法是贪心法+DFS,每次在叶子结点中,选择当前最接近正确解的节点,然后对它进行扩展

image-20231222173122019

为了描述这种与正确解的接近程度——引入测度函数f(n)

image-20231222173446162

在8-Puzzle问题中,用节点n中,处于错误位置的方块数作为测度。测度越小,则越接近于正确解

最后要达到的状态:

image-20231222202323845

这里2、8、1处于错误的位置,则f(n)=3

image-20231222202404661

爬山法解决8-Puzzle问题:

在前一步展开的节点的儿子中(DFS,向深处理),选择测度函数值最小的节点(贪心),然后继续对该子节点进行展开。重复上述步骤,直至得到正确解,或者栈S为空,算法结束

伪代码

image-20231222203036738

对比DFS搜索策略与爬山法的步骤,发现二者只有第4步是不同的

DFS将S所有的孩子无特定顺序地压入栈中,而爬山法将S的子节点按照其启发测度由大到小的顺序压入S中,每次压入之后要检查栈顶元素是否为目标节点。可以看到爬山法较DFS,对节点的展开有更加的针对性,每次都优先展开更加趋于正确解的节点。

然而爬山法的缺点也是明显的,这种贪心策略是局部的,并不能使整体效率最优:爬山法每次只对树的一支进行贪心、展开,而这一支不能保证有正确解,或者正确解需要大量的穷举才能得到,可能会导致这一支都穷举结束后,再返回到root节点,展开另一支,效率不高

Best-First搜索策略

基本思想

Best-First搜索策略是 广度优先思想+贪心

借鉴广度优先的思路,Best-First策略将当前树中所有可能展开的点都纳入贪心范围内,找其中最接近正确解的节点。这样就不会陷入树的一支,而是每次选出全局的最优情况,这样的贪心能提高整体搜索的效率。

仔细回想一下我们在最短路径算法时也用到了类似的搜索策略,从这个节点向外扩张,不断更新所有节点到达这个节点的最小代价,不断更新这个代价,重复这个过程,每次都选最短的路径进行扩张,期待每次最后能得到到该顶点的最短路径.

image-20231222203920142

伪代码

image-20231222204808548

使用堆作为数据结构,测度最大 or 最小的节点在堆顶处,每次展开这个节点即可

实例:8-Puzzle问题

image-20231222205001360

组合优化问题的搜索策略——分支限界

上述两种搜索策略最终只能得到一个可行解,而组合优化问题要求出最优解,所以上述两种策略不适用于组合优化问题的树搜索

组合优化问题-CSDN博客

分支限界法-CSDN博客

基本思想

image-20231222205847135

本质仍然是穷举,去找最优的那个解。但是在找最优解过程中,我们可以通过当前最接近于最优解的可行解去确定某一支穷举的策略一定得不到最优解,从而排除一部分节点,即不再展开这些节点,进而缩小解空间,提高效率。而获得可行解可以使用上面的两种策略,所以分支限界中的搜索策略仍然是爬山法 or Best-First搜索策略

可以先用爬山法尽快的获得一个可行解,然后再用Best-First搜索策略再去搜索,寻找最优解。

image-20231223002602771

多阶段图搜索问题(最短路径)

image-20231222210727658

输入:多阶段图

输出:从v0到v3的最短路径

image-20231222211055474

树的节点是图中遍历到的节点,树边的权值是两个节点之间的距离

爬山法进行分支限界搜素

image-20231222211107546

由于要获得最优解,所以要将整棵树都进行搜索,直至没有节点可以展开(最终叶子节点要么是可行解,要么是被可行解排除掉的)

注意:获得的可行解都是最优解的上界

自己模拟一遍这个过程,体会一下使用可行解排除掉部分节点的过程

分支限界策略的原理

image-20231222212124778

说白了就是用爬山法或者Best-First方法找到一个可行解,再利用这个可行解对其他扩展进行剪枝(分支限界法的关键)

人员安排问题

问题定义

image-20231222212352643

image-20231222212435201

任务是有偏序关系的,人有编号,人与工作是一一对应的关系,而且要求具有偏序关系的任务Ji<Jk,分配的人编号必须满足Pi<Pk

本质上就是给一些具有偏序关系的任务进行编号,编号从小到大,要求具有偏序关系的任务之间,先进行的任务编号<后进行的任务编号(编号小的先进行,大的后进行)

这自然联想到拓扑排序问题,毕竟AOE网就是这个东西。获得所有拓扑序列,然后从小到大编号即可

转换为树搜索问题

image-20231222213015367

image-20231222213414535

image-20231222213451995

用树表示所有拓扑排序序列,树的层数就相当于编号(根节点在第0层),这样就能将拓扑排序序列对应进行编号了

有点AOE网的感觉,父节点<子节点(表示偏序关系,不是小于)或者 父节点与子节点没有偏序关系,为了构成搜索树,要加一个根节点0,它的子节点是初始偏序集中所有没有前序元素的元素

拓扑序列树的生成算法
输入:偏序集合S,树根root
输出:由S的所有拓扑排序序列构成的树
生成树根root
选择偏序集中所有没有前序元素的元素,作为root的子节点
for root的每个子节点v do
	S=S-{v};
	把v作为根,递归地处理S.

实例

image-20231222230629362

Pij——第i个人分配第j项任务的代价

练习:使用爬山法和Best-First的分支限界搜索算法做一遍,感觉还是先进行一次爬山法,获得一个可行解,之后再使用best-first进行分支限界更好一些

image-20231223002032092 image-20231223002047806 image-20231223002115443

节点的值是对应的Pij的值,在进行分支限界时,要判断当前的指派方式的代价之和是否已经超过可行解的代价。剪枝——减掉的是当前获得的局部解一定差于最优解的策略

在分支限界搜索过程,由于分支之间的差距不大,导致往往遍历到一支中最后几个节点处才能剪枝,剪枝的效果很差。所以简单地使用分支限界的方法是不行的,需要做出优化

分支限界优化——解的下界

image-20231223003402846

每一种指派都有代价的下界(每一行 或 一列中最小的代价),则可以得到一个解的下界,相应的,代价矩阵要减去这个下界,则Pij为将第j个任务指派给第i个人,解的下界的增值。例如选择P12,则包含P12的解的下界要在初始下界54的基础上增加4,当前解的下界为58

image-20231223093841356

注意:解代价下界不必是可行解,如本例,解的下界中包含了P23与P43,并不满足问题要求

构造解的下界的技巧:代价矩阵每行或每列减去同一个数(该行或列的最小数),使每行和每列都至少有一个零,其余各元素非负

新的加权树表示

image-20231223094325662

根节点0的值为这个下界,其它节点的值是 父节点的值+Pij,是包含当前指派的的下界

练习:用爬山法再重新做一遍,体会引入解的下界如何提高剪枝的效率(增大了各个节点值之间的差距,方便剪枝,分支限界最好用于各个节点的值差距很大的情况)

image-20231223101455036

最终结果

image-20231223101648315

旅行商问题

问题定义

image-20231223101730502

输入:无向连通图G,G的特征:每个节点都没有到自己的边,没对节点之间都有一条非负加权边

输出:一条哈密顿回路,特征:一条由任意一个节点开始,经过每个节点一次,最后返回开始节点的路径,且该路径的代价最小(组合优化问题)

转换为树搜素问题

image-20231223102110368

image-20231223102501907

实例

image-20231223102519922

本例是在一个有向完全图中寻找哈密顿回路,是一般的哈密顿回路问题的简化

问题的划分

首先将代价矩阵转换为代价下界的增值矩阵——减去每行/每列中最小的值,使矩阵的各行各列都至少有一个0

结果如下:

image-20231223122922717

Cij:从i直接到j,代价下界增加的值

如从4直达6,C46=0,则在当前的代价下界96上加C46=0,就是包含从4直达6的解的代价下界,为96

f(i,j):从i到j经历其它节点(不直达),最小的代价下界增值

f(4,6),排除掉(4,6),第i行是从第i个节点到各个顶点的代价下界增值,第j列是从各个顶点到第j个节点的代价下界增值,所以在第4行寻找除了C46外最小的代价下界增值,为C41=32;然后再从第6列寻找除了C46外最小的代价下界增值,为C56=0,所以从4经过其它顶点到达6的最小代价下界增值为C41+C56=32而路径最终是从4->1->···->5->6,由于1->5之间的路径代价增值是非负的,所以4->6(不直达)的代价增值一定不小于32,所以32是4->6(不直达)的最小代价下界增值

为了尽早地进行剪枝,我们要选择使子节点代价下界增加最大的划分边(4,6),作为树的划分

image-20231223125107470

这里(4,6)是各个Cij=0的边(i,j)中,最小代价下界增值f(i,j)最大的

所以用解中是否包括(4,6)作为问题的划分

计算左右子节点的代价下界

image-20231223125856825

image-20231223130102778

递归构造左右子树
构造左子树根对应的代价下界增值矩阵

image-20231223130117736

左子树是已经包含了(4,6)的解的集合,为了避免出现回路,左子树不能再出现4->···->6,且不能出现(6,4),所以要将所有4可达的C4j与所有可达6的C6i均删除掉(其实应该是对这些节点做标记,后续不再处理它们了,相当于删除掉了),然后对(6,4)赋值为无穷,避免后续选择它

image-20231223130518941

继续递归处理左子树,与前面相同,先检查是否有某一行或一列不为0的情况,若有,则同时减掉这一行或一列最小的值,然后在原来的代价下界上增加这个减去的值,即为左子树根的代价下界。上面的矩阵是第5行已经减掉3的结果了

构造右子树根对应的代价下界增值矩阵

初始矩阵:

image-20231223122922717

image-20231223131015107

右子树是不包含边(4,6)的解的集合,所以只需要把C46赋值为无穷(可以有4->i->j->k->···->6,所以第4行和第6列不需要删除)

image-20231223131521447

再检查各行各列是否至少有一个0,与前面操作相同,若有,则同时减掉这一行或一列最小的值,然后在原来的代价下界上增加这个减去的值,即为右子树根的代价下界。

当前树的状态

image-20231223131637289

爬山法扩展左子树

image-20231223131713510

image-20231223131838227

image-20231223131859072

0-1背包问题

问题定义

image-20231223132202620

转换为树搜索问题

image-20231223132209048

下界——贪心算法的可行解

上界——分数背包问题的最优解

image-20231223132351837

image-20231223132402045

实例

image-20231223132502041

image-20231223132508510

image-20231223132531419

image-20231223132539863

image-20231223132547755

image-20231223132554257
image-20231223132600597

image-20231223132606700

image-20231223132613427

A*算法(重要)

A*算法详解(个人认为最详细,最通俗易懂的一个版本)-CSDN博客

image-20231223153611283

A*最终得到的解一定是优化解,不需要将节点全部展开

A*算法常用Best-First算法搜素

image-20231223153756919

f*(n)=g(n)+h*(n),f*(n)是节点n的代价,g(n)是从root到n的代价,h*(n)是从n到目标节点的优化路径的代价

h*(n)需要估计,而且要估计出的h*(n)要比真实的h*(n)代价小,即h(n)≤h*(n)恒成立

实例

image-20231223153947538

image-20231223154149478

选择与节点v相连的边中最小的权值,作为h*(v)的估计h(v)

A*算法的规则

image-20231223154725078

image-20231223154751026

image-20231223154844304

image-20231223154852120

image-20231223154904526

image-20231223154912596

image-20231223154920742

image-20231223154926181

image-20231223154936985

当根据Best-First策略,要选择目标节点进行扩展时,A*算法结束,可以回溯出一个有化解

正确性证明

image-20231223154159027

  1. 根据Best-First策略可知,当进行最后一次展开时,要选择节点t,说明有f(t)≤f(n),又因为f(n)≤f*(n),则有f(t)≤f*(n)
  2. 假设有{f*(n)}有一个为最优解f*(s),则有f(t)≤f*(s)
  3. 又因为f(t)=g(t)是一个可行解,而f*(s)是有化解的代价,则有f(t)=g(t)≥f*(s)成立,两边夹,最终有f(t)=g(t)=f*(s)为有化解

反证法

image-20231223160133540

也就是ni不在路径里面,在进行Best-First过程中,要选择当前代价F最小的节点进行展开,由于f(ni)<f(TP),则应该优先展开节点ni,而不是TP,所以矛盾

例题

image-20231223162234066

image-20231223170132034

image-20231223170158716

image-20231223170208382

image-20231223170225332
参考:
哈工大算法设计与分析之树搜索问题_哈工大 算法设计与分析-CSDN博客
树的搜索问题2——分支界限和A*算法(多阶段图问题、人员安排问题和旅行商问题)_a star算法属不属于分支界限算法-CSDN博客

  • 22
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值