分治法:将问题实例划分成几个较小的子问题(可以递归的划分),然后对这些较小的子问题求解,合并这些较小的子问题,以得到原始问题的解。有点类似Map/Reduce思想。主要区别在于:Map/Reduce的关键是把一个大的任务,拆分成尽可能多的小任务,再合并子任务的解。任务本身往往并不复杂,主要是计算量大,基本做一次水平的拆分就够了。而分治法则是把一个不太容易解决的问题,拆分成几个小点儿的问题(拆分的数量相对较少),再将子问题递归的拆分成更小的子问题,直到某些子问题可以轻松的解决。所以它的本质是垂直的递归的拆分任务。
动态规划:动态规划和分治法有点类似,也是把一个问题递归的拆分成多个子问题,对子问题求解,最后合并,得到原始问题的解。动态规则的特别之处,在于先把每个子问题的结果都计算出来,并缓存结果,这样如果遇到相同的子问题时,无须重复计算,是一种空间换取时间的算法。所以动态规则相比分治法,更适合,拆分子问题不是相互独立的场景。因为遇到相同子问题不需要重新递归计算,效率较高。
下面给出个分治法与动态规划算法的例子: 问题:一个n级的台阶,每次只能走1阶或者2阶,求走到顶层台阶的方法数。 代码实现如下:
package algorithm;
public class Test {
public static void main(String[] args) {
long count = 0;
int n = 40;
long startTime, endTime;
startTime = System.currentTimeMillis();
count = divideConquer(n);
endTime = System.currentTimeMillis();
System.out.println("result: " + count + ",cost: " + (endTime - startTime));
startTime = System.currentTimeMillis();
count = dynamicProgramming(n);
endTime = System.currentTimeMillis();
System.out.println("result: " + count + ",cost: " + (endTime - startTime));
}
public static int divideConquer(int n) {
if (1 == n) {
return 1;
} else if (2 == n) {
return 2;
} else {
return divideConquer(n-1) + divideConquer(n - 2);
}
}
public static int dynamicProgramming(int n) {
int[] f = new int[n];
f[0] = 1;
f[1] = 2;
for (int i = 2; i < n; i++) {
f[i] = f[i-1] + f[i-2];
}
return f[n-1];
}
}
程序运行结果:
result: 165580141,cost: 359
result: 165580141,cost: 0
从运行结果可以看出,对于包含大量相同子问题时,动态规则要比分治法快得多,因为使用分治法有大量重复递归计算。当台阶数达到50时,分治法已经很难计算出结果,而动态规则仍然很快。
贪心算法:从最小的问题开始,不从整体最优考虑,每次根据当前状态,查找局部最优解,最后得到一个局部最优解,不能用于查找最优解。人们遇到问题时,最容易想到的就是贪心算法,只是可能你不知道它的名字而以。贪心算法没有固定的算法框架,算法设计的关键是贪心策略的选择。该算法比较简单,这里不举例说明。
回溯法:回溯算法是一种低效,类枚举的搜索算法。以深度优先的搜索方式搜索,遇到不满足求解条件,或者到达分支结尾时,就回溯返回,尝试别的路径。回溯法是一种试错的算法,非常通用,许多复杂,规模较大的问题都可以用使用回溯法。
分支限界法:根据某个界值,按照广度优先策略搜索问题的解空间树,裁剪超过界值的分支路径。再根据裁剪后的空间树,搜索想要的路径解。选取的界值越接近最优解,算法的效率越高。我们一般可以先通过一些简单的算法求取这个界值,如贪心算法。
分支限界法和回溯法的对比如下:
求解目标不同:回溯法一般用于找出解空间树中满足条件的所有解,而分支限界法的求解目标往往只是找到一满足条件的解,或者是在所有解中找出最优解。搜索方式不同:回溯法采用深度优先的方式搜索空间树,而分支限界法则以广度优先或以最小耗费(最大效益)优先的方式搜索空间树。