算法设计与分析(二)五大算法--分治法、贪心方法、动态规划、回溯法、分支界限法

参考

https://my.oschina.net/HuoQibin/blog/1632769

 

分治法

定义:

将原问题分解为几个规模较小但类似于原问题的子问题,递归地求解这些子问题,然后再合并这些子问题的解来建立原问题的解。----《算法导论》

1.分治法基本策略

1)将问题分解为规模较小的子问题,子问题与原问题相互独立且同质

2)迭代或者递归解决每个子问题

3)将子问题的解合并得到原问题解

 

2.分治算法特征分析

分治法能解决的问题一般具有以下几个特征:

1) 该问题的规模缩小到一定程度就可以容易的解决;

2) 该问题可以分解为若干个规模较小的相同问题,即该问题具有最优子结构性质;

3) 利用分解出的子问题的解,可以合并为该问题的解;

4) 该问题所分解出的各个子问题是相互独立的,即子问题之间不包含公共的子子问题;

分治算法大多采用递归实现,第二条特征就反应了递归思想的引用。

如果满足了第一条特征和第二条特征,不满足第三条特征,可以考虑用贪心法或动态规划法。

如果不满足第四条特征,也可以用分治法,但是要做很多不必要的工作,重复的解公共的子问题,所以一般用动态规划法比较好。

3.实际应用场景

二分查找,阶乘计算,归并排序,堆排序、快速排序、傅里叶变换都用了分治法的思想。

4.依据分治法设计程序的思维过程

实际上类似数学归纳法,找到解决本问题的求解方程公式,然后根据方程公式设计递归程序。

1) 一定是先找到最小问题规模时的求解方法

2) 然后考虑随着问题规模增大时的求解方法

3) 找到求解的递归函数后,设计递归程序即可。

算法框架:

SolutionType DandC(ProblemType P){
    ProblemType P1,P2,P3,...Pn;
    if(Small(P))    return S(P);    //子问题P足够小,直接求解
    else{
        Divide(P1,P2,P3,...Pn);    //将问题P分解为子问题P1,P2,...Pn
        Return Combine(DandC(P1),DandC(P2),...,DandC(Pk));    //求解子问题,并合并解
    }
} 

相关算法:

贪心法

贪心法是求解最优化问题的一种设计策略。贪心法通过分步决策来求解问题。

定义:

贪心算法就是,在对问题求解时,总是做出在当前这一步看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的是在某种意义上的局部最优解。

最优化问题:问题给出某些约束条件,满足这些约束条件的解称为可行解;为了衡量可行解的好坏,问题还给出了目标函数,使目标函数取最大(小)值的可行解称为最优解。

  • 注:贪心法在每一步上用作决策依据的选择准则被称为最优量度标准 或 贪心准则,这种量度标准通常只考虑局部最优性。
  • 注:贪心算法没有固定的算法框架,算法设计的关键是贪心策略的选择。必须注意的是,贪心算法不是对所有问题都能得到整体最优解,选择的贪心策略必须具备无后效性,即某个状态以后的过程不会影响以前的状态,只与当前状态有关。所以对所采用的贪心策略一定要仔细分析其是否满足无后效性。

贪心法基本要素:

贪心选择性质最优度量标准所谓贪心选择性质是指所求问题的整体最优解可以通过一系列局部最优的选择(通过最优度量标准来判断),即贪心选择来达到。这是贪心算法可行的第一个基本要素,也是贪心算法与动态规划算法的主要区别。选择最优度量标准是使用贪心法求解问题的核心问题。贪心算法每步做出的选择可以依赖以前做出的选择,但绝不依赖将来的选择(也称无后效性),也不依赖子问题的解。也正是如此,贪心法的特征是自顶向下(与动态规划相反),一步一步地做出贪心决策。

最优子结构(最优化原理)当一个问题的最优解中包含了子问题的最优解时,则称该问题具有最优子结构特性。一个可用贪心法求解的问题往往呈现最优子结构性质。

     

贪心策略适用的前提是:局部最优策略能导致产生全局最优解。
实际上,贪心算法适用的情况很少。一般,对一个问题分析是否适用于贪心算法,可以先选择该问题下的几个实际数据进行分析,就可做出判断。

 

注:并不是所有具有最优子结构特性的问题,都可以有幸的找到最优量度标准,此时可以考虑采用动态规划法求解。

 

贪心算法与动态规划的区别

  • 贪心算法和动态规划都具备最优子结构(满足最优化原理即是最优子结构问题),以及都满足无后效性,但动态规划不具备贪心选择性质
  • 动态规划算法和贪心算法有一个显著区别:

1)在动态规划算法中,以自底向上的方式来利用最优子结构,也就是说,首先找到子问题的最优解,解决子问题,然后找到问题的一个最优解。
2)在贪心算法中,以自顶向下的方式使用最优子结构,也就是说,贪心算法会先做出选择,在当时看起来是最优的选择,然后再求解一个结果子问题,而不是先求解子问题的最优解,然后再做出选择

  • 贪心算法作出的每步贪心决策都无法改变,因为贪心策略是由上一步的最优解推导下一步的最优解,而上一部之前的最优解则不作保留。 

动态规划算法的全局最优解中一定包含某个局部最优解,但不一定包含前一个局部最优解,因此需要记录之前的所有局部最优解;

利用贪心算法求解最优问题的步骤:

(1)选定合适的贪心选择的标准;(2)证明在此标准下该问题具有贪心选择性质;

(3)证明该问题具有最优子结构性质;(4)根据贪心选择的标准,写出贪心选择的算法,求得最优解

算法模板:

SolutionType Greedy(SType a[],int n){
    SolutionType solution = null;    //初始解向量不含任何分量
    for(int i = 0;i<n;i++){    //多步决策,每步选择一个分量
        SType x = Select(a);    //遵循最优度量标准选择一个分量
        if(Feasible(solution,x)    //判断新分量x加入后是否可行
            solution = Union(solution,x);    //形成新的最优解
    }
    return solution;    //返回生成的最优解
}

相关算法:

动态规划

 定义

按照多步决策方法(和贪心一样也是分步决策),一个问题的活动过程可以分成若干阶段,每个阶段可能包含一个或多个状态。多部决策求解方法就是从初始状态开始做出每个阶段的决策,形成一个决策序列,该决策序列也成为策略。对于每一个决策序列,可以用一个数值函数(目标函数)衡量该策略的优劣。问题求解的目标是获取最优决策序列

动态规划法基本要素:

最优性原理(最优子结构):和贪心法相同。最优性原理是指“多阶段决策过程的最优决策序列具有这样的性质:不论初始状态和初始决策如何,对于前面决策所造成的某一状态而言,其后各阶段的决策序列必须构成最优策略”这个最优性原理是动态规划的基础。tip:而如果问题的最优解所包含的子问题的解也是最优的,就称该问题具有最优子结构,即满足最优性原理。

无后效性:即某阶段状态一旦确定,就不受这个状态以后决策的影响。也就是说,某状态以后的过程不会影响以前的状态,只与当前状态有关。

重叠子问题:动态规划法的子问题往往是重叠的,如果采用与分治法类似的直接递归会非常费时。为了避免重复计算,动态规划法一般采用自底向上的方式进行计算,并保存已经求解的子问题的值。当这些子最优解值被重复引用时,无需重新计算。

设计动态规划法步骤:

  1. 刻画最优解的结构特性;
  2. 递归定义最优解值;
  3. 自底向上方式计算最优解值;
  4. 根据计算得到的信息构造一个最优解。

算法实现的说明

动态规划的主要难点在于理论上的设计,也就是上面4个步骤的确定,一旦设计完成,实现部分就会非常简单。

使用动态规划求解问题,最重要的就是确定动态规划三要素:

(1)问题的阶段 (2)每个阶段的状态

(3)从前一个阶段转化到后一个阶段之间的递推关系。

递推关系必须是从次小的问题开始到较大的问题之间的转化,从这个角度来说,动态规划往往可以用递归程序来实现,不过因为递推可以充分利用前面保存的子问题的解来减少重复计算,所以对于大规模问题来说,有递归不可比拟的优势,这也是动态规划算法的核心之处。

确定了动态规划的这三要素,整个求解过程就可以用一个最优决策表来描述,最优决策表是一个二维表,其中行表示决策的阶段,列表示问题状态,表格需要填写的数据一般对应此问题的在某个阶段某个状态下的最优值(如最短路径,最长公共子序列,最大价值等),填表的过程就是根据递推关系,从1行1列开始,以行或者列优先的顺序,依次填写表格,最后根据整个表格的数据通过简单的取舍或者运算求得问题的最优解。

相关算法:

回溯法

回溯法思路的简单描述是:把问题的解空间转化成了图或者树的结构表示,然后使用深度优先搜索策略进行遍历,遍历的过程中记录和寻找所有可行解或者最优解。

在许多递归问题当中,我们采取的方法都是穷尽所有的可能,从而找出合法的解。但是在某些情况下,当递归到某一层的时候,根据设置的判断条件,可以 judge 此解是不合法的。在这种情况下,我们就没必要再进行深层次的递归,从而可以提高算法效率。这一类算法我们称为“回溯法”,设置的判断条件称为“剪枝函数”。

使用剪枝函数深度优先生成状态空间树中的节点的求解方法称为回溯法广度优先生成结点,并使用剪枝函数的方法称为分枝限界法

基本思想类同于:

  • 图的深度优先搜索
  • 二叉树的后序遍历

为了生成问题状态,采用两种根本不同的方法。虽然, 这两种方法都是从根结点 开始然后生成其它结点。如果已生成一个结点而它的所有儿子结点还没有全部生成, 则这 个结点叫做活结点。当前正在生成其儿子结点的活结点叫 E-结点(正在扩展的结点 )。不再 进一步扩展或者其儿子结点已全部生成的结点就是死结点。在生成问题状态的两种方法 中,都要有一张活结点表。在第一种方法中, 当前的 E-结点 R 一旦生成一个新的儿子 C, 这 个儿子结点就变成一个新的 E-结点, 当完全检测了子树 C 之后, R 结点就再次成为 E-结点。 这相当于问题状态的深度优先生成。在第二种状态生成方法中, 一个 E-结点一直保持到变 成死结点为止。在这两种方法中, 将用限界函数去杀死还没有全部生成其儿子结点的那些 活结点。这样做要非常小心,以使得在处理结束时至少能生成一个答案结点; 如果这个问题 要求找出全部解,则要能生成所有的答案结点。使用限界函数的深度优先结点生成方法称 为回溯法( backtr acking) 。

E-结点一直保持到死为止的状态生成方法导致分枝-限界方法 ( br anch-and-bound) , 分枝-限界法在第 7 章讨论。在宽度优先生成中, 每个新结点都被放 入一个队列。当这个当前 E-结点已生成了它的所有儿子时, 在队列前面的下一个结点就变 成新的 E-结点。在深度优先生成中, 那些新结点被放入栈而不是放入队列。当涉及这两种方案之 一时,当前的术语就不能保持一致了。队列方法一般叫做宽度优先生成, 而栈方法则称为 D-检索( 深度检索)。

 

 

定义(太长不记)

回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯”返回,尝试别的路径。回溯法是一种选优搜索法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。

基本思想  

在包含问题的所有解的解空间树中,按照深度优先搜索的策略,从根结点出发深度探索解空间树。当探索到某一结点时,要先判断该结点是否包含问题的解,如果包含,就从该结点出发继续探索下去,如果该结点不包含问题的解,则逐层向其祖先结点回溯。(其实回溯法就是对隐式图的深度优先搜索算法)。

若用回溯法求问题的所有解时,要回溯到根,且根结点的所有可行的子树都要已被搜索遍才结束。

而若使用回溯法求任一个解时,只要搜索到问题的一个解就可以结束。

用回溯法一般步骤:   

(1)针对所给问题,确定问题的解空间: 首先应明确定义问题的解空间,问题的解空间应至少包含问题的一个(最优)解。

(2)确定结点的扩展搜索规则

(3)以深度优先方式搜索解空间,并在搜索过程中用剪枝函数避免无效搜索。

条件:

  1. 它的解具有n-y元组的形式
  2. 问题提供显式约束来确定状态空间树,并提供隐式约束来判定可行解
  3. 能设计有效的约束函数缩小检索空间

也有说法是 多米诺性质 :

    • 如果当前结点不满足约束条件,能够推导出它的子结点也不满足约束条件.
    • 如果子结点满足约束条件能够推导出其父结点满足约束条件.

 

  • 显示约束解空间:规定每个分量xi取值的约束条件称为显式约束。对给定的一个问题,显示约束规定了所有可能的元组,他们组成问题的候选解集,被称为该问题实例的解空间。
  • 隐式约束判定函数:隐式约束给出了判定一个候选解是否为可行解的条件。一般需要从问题描述的隐式约束出发,设计一个判定函数(有时候是使该判定函数取极值来得到最优解),程序根据判定函数判断一个解是否为可行解。
  • 最优解目标函数:目标函数,也称代价函数,用来衡量每个可行解的优劣。使目标函数取得最大(小)值的可行解为问题的最优解。
  • 剪枝函数:为了提高搜索效率,在搜索过程中使用约束函数,可以避免无谓地搜索那些已知不含答案状态的子树。如果是最优化问题,还可以使用限界函数剪去那些不可能含有最优解的子树。约束函数和限界函数目的相同,都是为了剪去不必要搜索的子树,减少问题求解所需实际生成的状态结点数,他们统称为剪枝函数。

回溯法算法框架:

Void RBacktrack(int k){
    for(每个x[k],使得x[k]∈T[x[0],...,x[k-1]&&B(x[0],...x[k]){ //T[x[0],...,x[k-1]]是所有结点x[i]的集合,B(x[0],...x[k]就是显式约束规定x值的范围
        if(x[o],x[1],...,x[k]是一个可行解)//通过隐式约束得到的判定函数判断是否可行
            输出(x[o],x[1],...,x[k]);
        RBacktrack(k+1);
    }
}

回溯法相关算法:

分支限界法

 

在图的检索方法中, BFS 和 D-检索这两种方法都是在对当前 E-结点检测完毕之后才检 测以队或栈的形式存放在活结点表中的其它结点。

  • FIFO分枝限界法的活结点表是先进先出队列,按队列的FIFO原则选取下一个结点为当前扩展结点;
  • LIFO分枝限界法的活结点表是堆栈(即D-搜索),按栈的LIFO原则选取下一个结点为当前扩展结点,所以一个E结点死后D-会从最后一个活节点处开始;
  • LC(least cost)分枝限界法的活结点表是优先权队列,按优先权队列中规定的结点优先级选取优先级最高的下一个结点为当前扩展结点。

将这两种方法一般化就成为分枝-限界 策略。分枝-限界法是在生成当前 E-结点全部儿子之后再生成其它活结点的儿子且用限界函数帮助避免生成不包含答案结点子树的状态空间的检索方法。在这总的原则下, 根据对 状态空间树中结点检索次序的不同又可将分枝-限界设计策略分为数种不同的检索方法, 其 中与 BFS 类似的状态空间检索称为 FIFO( first in first out)检索,它的活结点表采用一张先 进先出表(即队 ) ;类似于 D-检索的状态空间检索称为 LIFO(last in first out)检索, 它的活结点表是一张后进先出表(即栈) 。

类似于回溯法,也是一种在问题的解空间树T上搜索问题解的算法。但在一般情况下,分支限界法与回溯法的求解目标不同

回溯法的求解目标是找出T中满足约束条件的所有解,而分支限界法的求解目标则是找出满足约束条件的一个解,或是在满足约束条件的解找出使某一目标函数值达到极大或极小的解,即在某种意义下的最优解。

分支限界法常以广度优先或以最小耗费(最大效益)优先的方式搜索问题的解空间树。
在分支限界法中,每一个活结点只有一次机会成为扩展结点。活结点一旦成为扩展结点,就一次性产生其所有儿子结点。在这些儿子结点中,导致不可行解或导致非最优解的儿子结点被舍弃,其余儿子结点被加入活结点表中。
此后,从活结点表中取下一结点成为当前扩展结点,并重复上述结点扩展过程。这个过程一直持续到找到所需的解或活结点表为空时为止。
 

实现方法

(1)队列式(FIFO)分支限界法
按照队列先进先出(FIFO)原则选取下一个节点为扩展节点。
(2)优先队列式分支限界法
按照优先队列中规定的优先级选取优先级最高的节点成为当前扩展节点。
 
posted @ 2020-03-16 12:19  幽灵化石  阅读(2330)  评论(0编辑  收藏  举报