树的搜索问题2——分支界限和A*算法(多阶段图问题、人员安排问题和旅行商问题)

前言

在上一篇博客中我们简单提了一下深度优先和广度优先,然后就开始了爬山法和best-first算法。

尽管貌似我们已经说了很多了,但是我们上一篇博客都是在围绕一个问题——所有的问题都是有一个确定的解,我们是在找解出问题的路径。

但如果是一个优化解问题呢?比如在一个多阶段图中寻找从起点到终点的最短路径?
这些算法其实是不能使用的,但这一类问题却很多,我们需要给出新的算法。
(之前的算法也不是没有用,和深度优先广度优先差不多,也都是在给我们即将到来的算法做铺垫)

分支界限法

分支界限法又叫剪枝法
顾名思义,其实就是通过一种方式快速的找到一个较优化解(一般来说是爬山法),然后利用得到的界限来将一些不可能的解删除掉,减少我们的搜索时间(当前的搜索方式为best-first

问题1,之前提到的多阶段图,这里我们需要计算从v0到v3的最短路径

在这里插入图片描述
而它的树结构长这样:
在这里插入图片描述
首先我们使用爬山法,先得到了一个长度为5的路径(0->11->23->3),然后将其作为我们剪枝的判准。
然后我们可以使用best-first算法来寻找最优解了,不过只要当前路径大于5,那么就直接舍弃掉,一定不可能为结果。

问题2,人员安排问题

我们有一个人的集合,和一个任务的集合,同时对应人和任务还有一个代价矩阵C。
要求:

  1. 其中人的集合需要从头往后安排,也就是一定要先安排第一个人;
  2. 任务是存在拓扑排序的;
  3. C[i][j]为第i个人分配第j个任务的代价。

我们需要寻找一个最小的代价。

拓扑排序

先把这个说了,比如我们学习过程中的高数、C语言、数据结构、算法和大物这些吧,
不学高数就不能学大物(因为有微积分),不学C就不能上数据结构,不学数据结构和C就不能学算法。
画一张图:
在这里插入图片描述
其中高数和C语言谁先上都无所谓,你大一上学期开三门数学课我都不管。
(不会吧不会吧,不会真的有学校大一上三门数学课吧)

这里举例子我们就给出四个活动吧:在这里插入图片描述

按照拓扑排序,我们得到了这样的一个活动安排顺序:(0代表根节点,没别的意思)
在这里插入图片描述
再加上我们给出的人员安排顺序,很明显第i行就是第i个人的任务(根节点算第零行)。

问题解决

既然我们已经得到了上述的树结构,再加上输入的矩阵,我们就可以为每一个点找到一个对应的代价。
(这里的代价应当是安排到当前的总代价)

当然可以直接使用分支界限法,但是我们还要讲一个优化办法:
我们假设矩阵是这样的:在这里插入图片描述

那么将其中一行都减去一部分会怎么样?
不失一般性,我们假设第一行吧,也就是任务1,当我们减去a(a小于等于12,保证减完不会出现负的),因为只能有一个人选择任务1,所以整体的代价算下来,也就相当于少了a。
然后我们在最后算代价的时候,将a加上就可以了。

同理对每一列我们减去一部分也是可以的。

优化解决办法:

  1. 将矩阵的每一行每一列都分别减去一个相同的数据,保证每行每列都有0
  2. 将减去的数据求和,放在树的根节点上作为代价下界。
  3. 然后正常使用分支界限法即可

为什么啊?为了帮助我们剪枝
因为有了初始的代价,我们缩小了分支之间的差距,但是加高了起点。
在这里插入图片描述
(仔细看其实这里有一点问题,我们使用爬山法得到的结果应该是73,而不是70,70是我们的最优解,但是不影响我们阐述思路)

问题3,旅行商问题

这里我们的旅行商问题还是经过了一定的简化,因为我们采取有向完全图

输入一个连通图G=(V,E),每一个顶点没有到自身的边,且每一对顶点都有一条非负的加权边。
需要返回从一个结点开始,遍历每一个结点然后返回的路径,保证该路径的代价最小。
这和哈密顿环还是有一定的区别,因为我们并没有那么严格,
首先可以经过一个顶点两次(当然这是没必要的);
另外我们没说边是否重复(当然也没必要,都有向完全图了)

杠这部分没什么意义,我们直接来看吧。

实现

首先是如何构建二叉树,也就是我们怎么选择左子树和右子树的问题。
这里我们的左子树为含有边(i,j)的图,而右子树是不含有这条边的图。
这个不含有比较有趣,这里我们采取
选择一条从i出发的边
一条到达j的边,当然这是不完全的,但是我们最后都是要将右子树剪掉的,所以就都行了。

然后是优化部分,这里我们采用两个主要的优化:

  • 首先是和人员安排问题相似,将矩阵的每一行每一列分别剪掉常数,得到0。

(使用矩阵存储边的关系)
这里我们是可以证明该问题和上述问题相同,都是可以这样做的,毕竟一个顶点只能到剩下顶点的一个。

  • 在保证每行每列都有0之后,我们就有很多个0可以选择,这时我们需要选择让左子树和右子树差距最大的 i 和 j ,加速剪枝

左子树都是0,所以这句话其实就是针对右子树的,也就是选一个i&j让从i出的最小边和进入j的最小边的和最大。
(是不是很绕?看一下例子就懂了)

举例

先给出矩阵
在这里插入图片描述
在经过消减3后,是这样的:(我们一共剪掉了96的代价,放在根节点上)
在这里插入图片描述
这里我们取的边是(4,6),因为这时从4出发、进入6的边的和最大,为32。
从4出发的应该从第4行找,最小32;
进入6的从第六列找,最小为0。
记得别找(4,6)就行……

这样我们就暂且得到了一棵树。
在这里插入图片描述
那么在左子树和右子树应该如何填值呢?

左子树:
我们取的边代价为0,所以应仍为96。
但这是我们需要修改矩阵,因为是有向完全图,所以我们不再需要从4出发,到达6的所有边了,并且(6,4)也没有用了。
(想一下么,有向完全图任意两个顶点都能相连,我们形成环的时候每一个顶点一个出度一个入度)

所以现在的矩阵是这样的:
在这里插入图片描述
我们还需要将剩下的每一行每一列消出0,所以第五行减3,最终左子树的代价应该写96+3 = 99

右子树:
因为右子树是不包含(4,6)的,所以只需要将这项置为无穷即可。
此时我们也是需要将矩阵的每一行每一列消出0,所以还需要加32
(其实这个32就是我们选择边的时候计算的32,两个定义其实本质相同)
所以我们在右子树填96+32 = 128

重复上述过程,我们得到了这样的一棵树:
在这里插入图片描述
这样就完了吗?
不,因为这只是在爬山,我们接下来还需要进行bf呢。
在bf的过程中,因为可能有的分支是有的边要,有的边不要,所以我们要注意不要连成环
这东西也挺麻烦的,在这里就不展示了。

A*算法

之前的分支界限法本质上其实就是在bf的过程中通过和已知的界限比较,及时规避不必要的分支;
而我们的A*算法则是在告诉你,在一定情况下,我们得到的一定是优化解,不需要再进行比较了。

我们是不是可以这样说:

  • 分支界限法其实就相当于dp,采用的是不断尝试
  • A*算法则是贪心,在特定情况下直接判定为优化解

而A*算法也是离不开best-first的。

代价函数

在一棵树中,我们先给出如下的定义,其中n为树中任意一个结点

  1. g(n)为从树根到n结点的路径长
  2. h*(n)为n到目标节点的优化路径代价
  3. f*(n) = g(n)+h*(n),为通过n的优化代价

但是很明显,我们没有h*(n)的值,因而得不到f*(n)的值。

所以我们给出一个估计的函数h(n),保证h(n) <= h*(n);
再给出f(n) = g(n)+h(n) <= f*(n)

举个例子

还是多阶段图的例子,我们要找到从S到T的最短路径
在这里插入图片描述
先给出g函数:
g(V1) = 2
g(V2) = 3
g(V3) = 4

这里我们定义的h函数为从顶点n出发的最小边,很明显这样的定义是符合我们的不等式的。
所以有:
h(V1) = 2,f(V1) = 4

因为是使用bf算法,所以我们需要构建堆,对f函数值进行比较,最终得到结果。

将这个过程延续下取,最终我们得到了这样的一张图:(其中黑字为边的代价,蓝字为f函数值)
在这里插入图片描述
但是还没有完,我们只是在整个堆中看到了T结点,而bf停止的标志是堆的根节点为目标节点
直到这样,我们才说找到了最优解。(虽然还是7)
在这里插入图片描述

证明

因为使用了贪心策略,之前几个是因为比较直观,所以没有给出证明。

我们需要证明的是,当n为目标结点时,我们得到的f函数值一定为优化解,也就是f(n) = f*(n)
因为当n为目标节点,h(n) = 0,所以我们说也就相当于证明f*(n) = g(n)
假设当前到达的结点为x,这个x是任意的。

  1. 因为我们采用的是bf策略,所以n的f函数值一定要比x小,即f(n) <= f(x) <= f*(x)。
  2. 对于任意的x总有一个f*(x0)为优化解,那么f(n) <= f*(x0),即g(n) =<= f*(x0)
  3. 我们得到的g(n)也是到达了最终节点n的,那么就说明这个也是一个可行解,所以我们说有g(t) =>f*(x0)
    所以最终我们得到g(t) = f*(x0),其实我们的优化解f*(x0)就是我们要的f*(n)

然后贴一下反证法的思路:

也可用反证法解定理1:使用Best-First策略搜索树,如果A如选择的节点是目标节点,则该节点表示的解是优化解。
证明(反证法):
(1)假设A
算法首先找到的路径P(n1,n2,…,TP)来到目标节点,而路径P并非真正的最小代价路径。
(2)若真正的最小代价路径A(n1,n2,…,ni,…,TA),那么A中必有节点ni不在路径P中,假设ni是离起点最远的一个节点(路径P与路径A在ni前面的节点都相同),那么f(ni)<=g(TA)<g(TP)=f(TP),即f(ni)<f(TP)
(3)按照Best-First算法,如果f(ni)<f(TP),那么应该选择扩展ni节点而非TP节点,与Best-First策略相矛盾。故A*算法选择的节点是目标节点,则该节点表示的解是优化解。

总结

在分支界限法中,主要感觉人员安排那部分的右子树处理上可能有一点欠缺,比如右子树只有两条边,而且只是写上没有某条边,却没有标记我们真正使用了哪两条边代替,但是对于剪枝思想来说,其实例子还是很棒的。

在A算法中,我们给出的例子包含了一个h函数定义,要注意不是所有的h函数都是这样定义的,不要被绕进去;
另外A
部分的证明写的确实不太好,主要是思想其实还是两边夹,反证那个看着感觉其实还好。

评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值