在参加中级软件设计师的考试中,公认的最难的一部分就是算法。可是自从老师给我们讲完算法之后,就感觉算法其实也没什么。软考中,算法被分为分治法、动态规划法、贪心算法和回溯法。那么,今天我们就来说一说这几种算法。
一、概念
1、分治法
分治法的基本思想是将一个规模为n的问题分解为k个规模较小的子问题,这些子问题相互独立且与原问题相同。递归的解这些子问题,然后将各子问题的解合并得到原问题的解。
适用范围:
1) 该问题的规模缩小到一定的程度就可以容易地解决
2) 该问题可以分解为若干个规模较小的相同问题,即该问题具有最优子结构性质。
3) 利用该问题分解出的子问题的解可以合并为该问题的解;
4) 该问题所分解出的各个子问题是相互独立的,即子问题之间不包含公共的子子问题。
2、动态规划
和分治法一样,动态规划(dynamic programing)是通过组合子问题的解而解决整个问题的。不过与分治法不一样的地方就是,分解得到的子问题往往不是独立的,也就是说相同的子问题可能会被求解多次。
适用范围:
1) 最优子结构性质:一个最优化策略的子策略总是最优的。
2) 无后效性:每个状态都是过去历史的一个完整总结。
1) 子问题的重叠性:是指在用递归算法自顶向下对问题进行求解时,每次产生的子问题并不总是新问题,有些子问题会被重复计算多次。
3、贪心
贪心算法总是作出在当前看来最好的选择。也就是说贪心算法并不从整体最优考虑,它所作出的选择只是在某种意义上的局部最优选择。
当然,希望贪心算法得到的最终结果也是整体最优的。虽然贪心算法不能对所有问题都得到整体最优解,但对许多问题它能产生整体最优解。如单源最短路经问题,最小生成树问题等。在一些情况下,即使贪心算法不能得到整体最优解,其最终结果却是最优解的很好近似。
4、回溯
回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯”返回,尝试别的路径。
适用范围
在包含问题的所有解的解空间树中,按照深度优先搜索的策略,从根结点出发深度探索解空间树。
二、背包问题
1、贪心思想
背包问题
背包问题:与0-1背包问题类似,所不同的是在选择物品i装入背包时,可以选择物品i的一部分,而不一定要全部装入背包,1≤i≤n。
给定n个物品和一个容量为C的背包,物品i的重量是Wi,其价值为Vi。 背包问题是如何选择入背包的物品,使得装入背包的物品的总价值最大,注意和0/1背包的区别,在背包问题中可以将物品的一部分装入背包,但不能重复装入。
三种解决方案
(1)选择价值最大的物品优先,因为这可以尽可能快的增加背包的总价值,但是,虽然每一步选择获得了背包价值的极大增长,但背包容量却可能消耗的太快,使得装入背包的物品个数减少,从而不能保证目标函数达到最大。
(2)选择重量最轻的物品,因为这可以装入尽可能多的物品,从而增加背包的总价值。但是,虽然每一步选择使背包的容量消耗的慢了,但背包的价值却没能保证迅速的增长,从而不能保证目标函数达到最大。
(3)性价比最高,以上两种贪心策略或者只考虑背包价值的增长,或者只考虑背包容量的消耗,而为了求得背包问题的最优解,需要在背包价值增长和背包容量消耗二者之间寻找平衡。正确的贪心策略是选择单位重量价值最大的物品。
PS:由此可见,贪心的最高境界就是什么都贪,它不仅仅贪某一方面,而是综合各个方面的贪。这也是所有人的想法——贪,正是由于我们有这种贪的想法,才得出之后的一些个算法。
2、回溯法
0-1背包问题
给定n种物品和一个背包。物品i的重量是Wi,其价值为Vi,背包的容量为C。应如何选择装入背包的物品,使得装入背包中物品的总价值最大? 在选择装入背包的物品时,对每种物品i只有2种选择,即装入背包(1)或不装入背包(0)。不能将物品i装入背包多次,也不能只装入部分的物品i。
解决方案
(1)未优化
虽然0-1背包问题与背包问题不一样,但是它们都很贪,都是为了使包中的总价值最大。由于,在贪心算法中,我们已经知道了按照单位重量价值最大的先放才会使包中总价值最大,所以在回溯法中,同样按照重量价值最大的先放,不过,每一个物品分为两种情况,放入(用1表示)或不放入(用0表示)。
所以可以根据这些条件,构造一个二叉树,该算法遍历每一条路径(从根节点到最底层算一条路劲),然后算出每条路径得到的包的总价值,然后比较得到最大的总价值。
(2)优化
由于二叉树会产生很多分支,如果每个都遍历一遍,效率是很低的,因此,为了提高算法的效率,我们可以使用限界函数,其作用是缩小我们遍历的范围。
在0-1背包中,我们是按照贪心算法来实现的限界函数,每次放入或不放入背包时,我们求出放入后或不放入背包后的贪心最优解(贪心算法处理背包问题的最优解),然后根据比较结果,我们遍历贪心最优解较大的一边。当遍历到底之后,回溯直到发现其他可遍历的点,然后继续比较,循环。
这样,我们就能保证,每次都是从贪心最优解较大的节点开始遍历,从而使我们的遍历更接近最优解。
3、动态规划
对于0-1背包问题,动态规划采用分治的方法来考虑,我们可以想象当背包容量很小的时候,可能进入背包的物品也相对变少(因为有些物品的重量已经超过了背包容量),那么为了使背包总价值最大,我们选择它进不进背包,就很容易。但简而言之,当背包容量大的时候,可选项多,当背包容量小的时候,可选项(可选择进入物品)较少,因此使问题得到简化。
我们如何将背包问题简化呢?那就是将放入背包问题转化为取出背包问题,什么意思呢?就是说,我们先假设所有的物品都在背包中,然后随机拿出一个,然后根据以下情况求其最优解,最优解还要在剩余的几个物品中求解,所以我们继续往外拿,如此递归,直至背包中物品为0。
这里是书上的式子,
PS:i表示背包中物品总数,w表示背包所能承受的最大重量,Wi表示第i个物品的重量。c[i,w]表示背包重量为w,放入i个物品所得到的最大价值。
其实,这个不是什么公式,这只是分为三种情况。
(1)第一种情况0,不用说,是当背包中物品为0,或背包重量为0时。此时价值自然为0.
(2)第二种情况为C[i-1,w],说明取出第i个物品后,背包重量不变,也就是说,该物品本来就不在背包中。此时是Wi>W,即取出第i个物品的重量大于背包重量,也就是该物品本来就不在背包中。
(3)第三种情况是说,取出的这个物品的重量小于背包时,它之前可能在背包中,也可能不在背包中,然后分别比较这两种情况的总价值。
针对上面的表达式,强调两点:
第一点:我们对两种不同情况都利用了分治方法,但是这两种情况下的子问题的是不一样的,因为这两个子问题的约束条件不一样,一个背包容量为W-Wi,另一个为W。
第二点:我们看到要想求这两种情况下,谁的总价值大,必须依赖于前一个子问题的最优解。只有前一个子问题求解出来之后,当前问题才能解决。而在考虑前一个子问题的最优解时,我们还是以相同的方式,任选一个待考虑的物品,看它在背包里导致的子问题的价值大,还是不在里面导致的子问题的价值大。
PS:动态规划算法就在于动态和规划两个词,动态指,计算过程中随着那出物品,包的重量在动态减小;规划指,在计算过程中,通过在之前最优解上的比较情况,得出现在的最优解。
总结:
综上,我们知道了每一种算法都有其适用的情况,我们要根据实际情况来选择适当的算法。当然,我个人认为,所有的算法都是从贪心出发,以分而治之的方法解决。对于算法更深层次的理解,还需要我们在今后实际使用的过程中慢慢体会。