常见的算法设计策略
常见的算法设计策略
1.分治
分治法的设计思想是,将一个难以直接解决的大问题,分割成k个规模较小的子问题,这些子问题相互独立,且与原问题相同,然后各个击破,分而治之。
分治法常常与递归结合使用:通过反复应用分治,可以使子问题与原问题类型一致而规模不断缩小,最终使子问题缩小到很容易求出其解,由此自然导致递归算法。
根据分治法的分割原则,应把原问题分割成多少个子问题才比较适宜?每个子问题是否规模相同或怎样才为适当?这些问题很难给与肯定的回答。但人们从大量实践中发现,在使用分治法时,最好均匀划分,且在很多问题中可以取k=2。这种使子问题规模大致相等的做法源自一种平衡子问题的思想,它几乎总是比使子问题规模不等的做法好。
2.动态规划
动态规划法与分治法类似,其基本思想也是将原问题分解成若干个子问题。与分治法不同的是,其分解出的子问题往往不是相互独立的。这种情况下若用分治法会对一些子问题进行多次求解,这显然是不必要的。动态规划法在求解过程中把所有已解决的子问题的答案保存起来,从而避免对子问题重复求解。
动态规划常用于解决最优化问题。对一个最优化问题可否应用动态规划法,取决于该问题是否具有如下两个性质:
1.最优子结构性质
当问题的最优解包含其子问题的最优解时,称该问题具有最优子结构性质。
要证明原问题具有最优子结构性质,通常采用反证法。假设由问题的最优解导出的子问题的解不是最优的,然后再设法说明在该假设下可构造出比原问题的最优解更好的解,从而导致矛盾。
2.子问题重叠性质
子问题重叠性质是指由原问题分解出的子问题不是相互独立的,存在重叠现象。
用动态规划法解题过程中,应当先找出最优解的结构特征,即原问题的最优解与其子问题的最优解的关联。然后有如下两种程序设计方法:
1.自底向上递归法
利用问题的最优子结构性质,以自底向上的方式递归地从子问题的最优解逐步构造出整个问题的最优解。
2.自顶向下递归法(即备忘录法)
利用问题的最优子结构性质,用与直接递归法相同的控制结构自顶向下地进行递归求解。初始时在表格中为每个子问题存入一个标识解。在求解过程中,对每个待求子问题,首先查看表格中相应的记录项。若记录项为初始时的标识值,则表示该子问题是初次遇到,此时应利用问题的最优子结构性质进行递归求解,并将结果存入表格,以备以后查看。否则则说明该问题已被求解过,直接返回表格中相应的值即可,不必重新计算。
当一个问题的所有子问题都要求解时,应当用自底向上递归法。当子问题空间中的部分子问题可不必求解时,自底向上递归法会进行多余的计算,此时应采用自顶向下递归法。
3.贪心
当一个问题具有最优子结构性质时,可用动态规划法求解。但有时会有比动态规划更简单更直接效率更高的算法——贪心法。贪心法总是做出在当前看来最好的选择,也就是说贪心法并不从整体最优考虑,它所做出的选择只是在某种意义上的局部最优选择。虽然贪心法并不能对所有问题都得到整体最优解,但是对许多问题它能产生整体最优解。有些情况下,贪心法虽然不能得到整体最优解,但其最终结果却是最优解的很好的近似。
贪心法常用于解决最优化问题。对一个最优化问题可否应用贪心法,取决于该问题是否具有如下两个性质:
1.贪心选择性质
贪心选择性质是指原问题总有一个整体最优解可通过当前的局部最优选择,即贪心选择来达到。
对于一个具体问题,要确定它是否具有贪心选择性质,通常可考察问题的一个整体最优解,并证明可修改这个最优解,使其以贪心选择开始。由此证明该问题总有一个最优解可通过贪心选择得到,即具有贪心选择性质。
2. 最优子结构性质
这一点与动态规划相同。做出贪心选择后,由于最优子结构性质,原问题简化为规模更小的类似子问题。如果将子问题的最优解和之前所做的贪心选择合并,则可得到原问题的一个最优解。
贪心问题的整体最优解可通过一系列局部的最优选择,即贪心选择来达到。这也是贪心法与动态规划的主要区别。在动态规划中,每一步所做出的选择往往依赖于相关子问题的解。因而只有在解出相关子问题后,才能做出选择。而在贪心法中,仅做出当前状态下的最好选择,即局部最优选择。然后再去解做出这个选择之后产生的相应的子问题。贪心法所做出的贪心选择可以依赖于以往所做过的选择,但绝不依赖于将来所做的选择,也不依赖于子问题的解。正是由于这种差别,动态规划通常以自顶向上的方式解各子问题,而贪心法通常以自顶向下的方式进行,以迭代的方式做出相继的贪心选择,每做出一次贪心选择就将所求问题简化为规模更小的子问题。
4.回溯
回溯法是对问题的解空间树进行深度优先搜索 ,但是在对每个节点进行DFS之前,要先判断该节点是否有可能包含问题的解。如果肯定不包含,则跳过对以该节点为根的子树的搜索,逐层向其祖先节点回溯。如果有可能包含,则进入该子树,进行DFS。
回溯法通常的解题步骤如下:
(1)定义问题的解空间。
(2)将解空间组织成便于进行DFS的结构,通常采用树或图的形式。
(3)对解空间进行DFS,并在搜索过程中用剪枝函数避免无效搜索。
用回溯法解题时并不需要显式地存储整个解空间,而是在DFS过程中动态地产生问题的解空间。在任何时刻,算法只保存从根节点到当前节点的路径。如果解空间树的高度为h,则回溯法的空间复杂度通常为O(h)
用回溯法解题时,常会遇到以下两类典型的解空间树:
(1)当所给的问题是从n个元素的集合S中找出S满足某种性质的子集时,相应的解空间树称为子集树。
(2)当所给的问题是找出n个元素满足某种性质的排列时,相应的解空间树称为排列树。
回溯法中的剪枝函数通常分为两类:
(1)用约束函数在指定节点处剪去不满足约束的子树。
(2)用限界函数在指定节点处剪去得不到最优解的子树。
5.分支限界
回溯法是对解空间进行深度优先搜索,事实上任何搜索遍整个解空间的算法均可解决问题。所以采用通用图搜索(树可抽象为特殊的图)的任何实现作为搜索策略均可解决问题,只要做到穷举即可。除了深度优先搜索之外,我们还可采用广度优先搜索,而分支限界法则是对解空间进行优先级优先搜索。
分支限界法的搜索策略是,在当前节点处,先生成其所有的子节点(分支),并为每个满足约束条件的子节点计算一个函数值(限界),再将满足约束条件的子节点全部加入解空间树的活结点优先队列。然后再从当前的活节点优先队列中选择优先级最大的节点(节点的优先级由其限界函数的值来确定) 作为新的当前节点。重复这一过程,直到到达一个叶节点为止。所到达的叶节点就是最优解。