算法设计方法概览
文章目录
递归算法
什么是递归
定义及分类
直接递归
在定义一个过程或者函数时,出现调用本过程或本函数的成分,称之为递归。如果调用自身,称之为直接递归;
间接递归
若过程或者函数p调用过程或者函数q,而q又调用p,称之为间接递归;
任何间接递归都可以等价地转换为直接递归;
尾递归
如果一个递归过程或递归函数中递归调用语句是最后一条执行语句,则称这种递归为尾递归;
使用场景
可以使用递归解决的问题,应该满足以下三个特点:
- 需要解决的问题可以转换为一个或多个子问题来求解,而这些子问题的求解方法和原问题完全相同,只是数据规模不同;
- 递归调用的次数必须是有限的;
- 必须有结束递归的条件来终止递归;
一般来说,在以下三种情况下,常常使用到递归的方法:
- 定义递归:问题本身的定义是递归的,如数列定义;
- 数据结构递归:问题涉及的数据结构本身是递归的,如单链表;
- 求解方法递归:问题的解决方法是递归的,如汉诺塔问题;
递归模型
递归模型是对递归问题的抽象,它反映一个递归问题的递归结构;
一般来说,递归模型有两部分组成:递归出口和递归体。前者决定递归过程何时结束,后者确定递归如何进行;
递归算法设计
递归与数学归纳法
第一数学归纳法
如果{P(1),P(2),P(3),…,P(n)}是命题序列,且满足以下两个性质,则所有命题为真:
- P(1)为真;
- 任何命题都可以从它的前一个命题推导出来;
第二数学归纳法
如果{P(1),P(2),P(3),…,P(n)}是命题序列且满足以下两个性质,则所有命题均为真:
- P(1)为真;
- 任何命题均可以由它前面所有命题推导得出;
数学归纳法是一种论证方法,而递归是算法和程序设计的一种实现技巧,数学归纳法是递归的基础;
递归算法设计的一般步骤
递归算法设计的核心是给出递归模型:
- 对原问题F(sn)进行分析,抽象出合理的小问题F(sn-1);
- 假设F(sn-1)可解,在此基础上给出F(sn)的解,即确定F(sn)和F(sn-1)的关系;相当于确定递归体;
- 确定一个特殊的情况,如F(0)或者F(1),由此得到递归出口;
前面提到递归处理手法的使用场景:问题定义、数据结构、求解方法;实际上围绕这三个点,我们就可以对其实施递归算法设计的一般步骤了;
分治算法
分治法概述
对于一个规模为n的问题,若该问题可以容易地解决(比如说规模n较小的时候)则直接解决,否则将其分解为K个规模较小的子问题。这些子问题相互独立且与原问题形式相同,递归地解决这些子问题,然后将各个子问题的合并得到原问题的解;
使用场景
- 该问题的规模缩小到一定程度就可以容易地解决;
- 该问题可以分解为若干个规模较小的相同问题;
- 利用该问题分解出的子问题的解,可以通过合并得到该问题的解;
- 该问题所分解出的各个子问题是相互独立的,也就是子问题之间不包含公共的子问题;
分治法的求解过程
分治法通常采用递归算法设计技术,在每一层递归上都包含三个步骤:
- 分解:将原问题分解为若干个规模较小,相互独立,与原问题形式相同的子问题;
- 求解子问题:若子问题规模较小而容易被解决则直接求解,否则递归求解各个子问题;
- 合并:将各个子问题的解合并为原问题的解;
蛮力法
蛮力法概述
蛮力法是一种简单直接地解决问题的方法,通常根据问题的描述和所涉及的概念定义找出所有可能的解;然后选择其中一种或者多种解进行测试,如果该解不可行,则探寻下一种可能的解;
使用场景
- 搜索所有解空间:问题的解存在于规模不大的解空间中;
- 搜索所有的路径:这类问题中不同的路径对应不同的解;
- 直接计算:基于问题的描述和所涉及的概念定义,直接进行计算,往往是一些简单题,不需要算法技巧;
- 模拟和仿真:按照求解问题的要求直接进行模拟或者仿真即可;
回溯法
问题的解空间
概述
一个复杂问题的解决方案是由若干个小的决策步骤组成的决策序列;解决一个问题的所有可能的决策序列构成该问题的解空间;
应用回溯法解决问题时,首先应该明确问题的解空间。解空间中满足约束条件的决策序列称为可行解;
一般来说,解任何问题都有一个目标,在约束条件下使目标达成的最优的可行解成为该问题的最优解;
种类
问题的解有一个不等长或者等长的解向量X={x1,x2,x3…xn}构成,其中xi表示第i步的决策;所有满足约束条件的解向量组构成问题的解空间;
问题的解空间一般用树来表示,也称为解空间树或者状态树。树中的每一个结点确定所求解问题的一个问题状态;
解空间树通常有两种类型:
- 子集树:当所给的问题是从n个元素的集合中选择满足某种性质的子集时,相应的解空间树称为子集树;
- 排列树:当所给的问题是确定n个元素的满足某种性质的排列时,相应的解空间树称为排列树;
而针对两种解空间树的类型,回溯法通常求解两类问题:找所有解和找最优解;
什么是回溯法
在包含问题所有解的解空间树中,按照深度优先搜索策略,从根结点出发搜索解空间树的方法;它体现了走不通就退回再走的思路;
回溯法在搜索解空间时,通常采用两种策略避免无效搜索,提高回溯的搜索效率:
- 用约束函数在扩展结点处剪除不满足约束的子树;
- 用限界函数剪去得不到问题解或者最优解的子树;
限界函数和约束函数都属于剪枝函数;剪枝函数存在的意义基于该观点:我们往往只需要解空间中的部分树或者最优的数;
使用回溯法的一般步骤
- 确定问题的解空间树;问题的解空间树应至少包含问题的一个(最优)解;
- 确定结点的扩展规则;
- 以深度优先方式搜索解空间树,并在搜索过程中可以采用剪枝函数来避免无效搜索,提高回溯的效率;
也就是说,回溯法=深度优先搜索+剪枝;
分枝限界法
什么是分枝限界法
分枝限界法类似于回溯法,也是在问题对的额解空间树上搜索问题解的算法;在一般情况下,分枝限界法与回溯法的求解目标并不相同;回溯法的目标是找出解空间中满足约束条件的所有解;分枝限界法的目标则是找出满足约束条件的一个解;或是在满足约束条件的解中找出使某一目标函数值达到极大或极小的解,即在某种意义下的最优解;
所为分枝,就是采用广度优先的策略,依次搜索活结点的所有分枝,也就是所有相邻结点;采用一个限界函数,计算限界函数值,选择一个最有利的子结点作为扩展结点,使搜索朝着解空间树上有最优解的分枝推进,以便尽快地找出一个最优解;
分枝限界法和回溯法的主要区别:
方法 | 回溯法 | 分枝限界法 |
---|---|---|
解空间搜索方法 | 深度优先 | 广度优先 |
存储结点的数据结构 | 栈 | 队列、优先队列 |
结点存储特性 | 活结点的所有可行子结点被遍历后才从栈中出栈 | 每个节点只有一次成为活结点的机会 |
常用应用 | 找出满足条件的所有解 | 找出满足条件的一个解或者特定意义的最优解 |
分枝限界法的设计思想
1. 设计合适的限界函数
在所有解空间树时,每个活结点可能有很多孩子结点,其中有些孩子结点搜索下去是不可能产生问题解或者最优解的;好的限界函数在扩展时将删除这些不必要的孩子结点从而提高搜索效率:
- 目标是求最大值:设计上界函数up(根节点的up值通常大于或者等于最优解的up);如果si是sj的双亲结点,应满足up(si)>=up(sj);当找到一个可行解up(sk)后,将所有小于up(sk)的结点剪枝;
- 目标是求最小值:设计下界函数lb(根节点的lb值一定要小于或等于最优解的lb值);若si是sj的双亲结点,应满足lb(si)<lb(sj),当找到一个可行解lb(sk)后,将所有大于lb(sk)的结点剪枝;
2. 组织活结点表
根据选择下一个扩展结点的方式来组织活结点表,不同的活结点表对应不同的分枝搜索方式:
- 队列分枝限界法:队列分枝限界法将活结点表组织成一个队列,并按照先进先出的原则选择下一个结点为扩展结点,其步骤如下:
- 将根节点加入活结点队列;
- 从活结点队列中取对头结点作为当前扩展结点;
- 对当前扩展结点,先从左到右地产生孩子结点,用约束条件检查,把所有满足约束条件的孩子结点加入活结点队列;
- 重复2-3,直到找到一个最优解;
- 优先队列分枝限界法:优先队列分枝限界法将活结点表组织成一个优先队列,并选取优先级最高的活结点成为当前扩展结点,其步骤如下:
- 计算起始结点(根结点)的优先级并加入优先队列;优先级往往由与特定问题相关的信息的函数值来决定;
- 从优先队列中选取出优先级最高的结点作为当前扩展结点,使搜索朝着解空间树上可能有最优解的分枝推进,以便能尽快地找出一个最优解;
- 对当前扩展结点,从左到右地产生它的所有孩子节点,然后用约束条件检查,对所有满足约束条件的孩子结点计算优先级并加入优先队列;
- 重复2-3,直到找到一个解或者优先队列为空为止;
3. 确定最优解的解向量
分枝限界法在搜索解空间树时,结点的处理是跳跃式的,回溯也不是单纯地沿着双亲结点一层一层地向上回溯;具体有两种方式确定最优解的解向量:
- 对每个结点保存从根结点到该结点的路径:每个结点都带有一个可能的解向量,这种方法实现起来比较简单,但是浪费空间;
- 在搜索过程中构建搜索经过的树结构:每个结点带有一个双亲结点指针,当找到最优解时,同过双亲指针找到对应的最优解向量;这种做法需要保存搜索经过的树结构,每个结点增加一个指向双亲结点的指针;
采用分枝限界法的三个关键问题
- 如何确定合适的限界函数?
- 如何组织待处理的活结点?
- 如何确定解向量的各个分量?
贪心法
贪心法概述
贪心法的基本思路是在求解问题时总是做出当前看来最好的选择,也就是说贪心法不从整体最优上考虑,所做出的仅是在某种意义上的局部最优选择;
但是人们通常希望得到整体最优解,所以在使用贪心法解决问题时需要证明所设计的算法确实是整体最优解或求解了它要解决的问题;
贪心法从问题的某一个初始解出发,采用逐步构造最优解的方法向给定目标前进,每一步都产生n元最优解向量的一个分量;
贪心法在每一步决策时所用的依据称为最优量度标准,也称为贪心准则;
每一次贪心选择都将问题简化为规模更小的子问题,并期望每次所做的局部最优选择产生出一个全局最优解;
贪心法应用约束
贪心选择性质
所谓贪心选择性质是指问题的整体最优解可以通过一系列局部最优解的选择,也就是贪心选择来达到;
贪心法仅在当前状态下做出最好选择,即局部最优选择,然后再去求解做出这个选择后产生的相应子问题的最优解;
最优子结构性质
如果一个问题的最优解包含其子问题的最优解,则称此问题具有最优子结构性质;问题的最优子结构性质是该问题可以通过动态规划算法或者贪心法求解的关键特性;
动态规划
动态规划的原理
动态规划是一种多阶段决策问题的优化方法;将多阶段过程转化为一系列单阶段问题,利用各阶段之间的关系,逐个求解;
动态规划求解的基本步骤
能采用动态规划问题求解的问题,一般具有3个性质:
- 最优性原理:如果问题的最优解所包含的子问题的解也是最优的,就称该问题具有最优子结构,即满足最优性原理;
- 无后效性:某阶段状态一旦确定,就不受这个状态以后决策的影响。也就是说,某状态以后的过程不会影响以前的状态,只与当前状态有关;
- 有重叠子问题:即子问题之间不是独立的,一个子问题在下一阶段决策中可能被多次使用到(该性质不是动态规划适用的必要条件,但是如果没有该性质,动态规划算法同其他算法相比就没有优势);
实际应用中简化的步骤:
- 分析最优解的性质,并刻画其结构特征;
- 递归地定义最优解;
- 以自底向上或者自顶向下的记忆化方式计算出最优解;
- 根据计算最优解时得到的信息,构造该问题的最优解;
动态规划与其他方法的比较
动态规划思想和分治法类似,都是将待求解问题分解为若干个子问题(阶段),然后按顺序求解子阶段,前一子问题为后一字问题的求解提供了有用的信息;
在求解任一子问题时,列出各种可能的局部解,通过决策保留那些有可能达到最优的局部解,丢弃其他局部解。依次解决各子问题,最后一个子问题的解就是初始问题的解;
动态规划又和贪心法有些类似,在动态规划中,可以将一个问题的解决方案视为一系列决策的结果;不同的是在贪心法中,每采用一次贪心准则就做出一个不可回溯的决策,还需要考察每个最优决策序列中是否包含一个最优子序列;