堆沙子问题:
设有n堆沙子排成一排,其编号为1,2,3,…,n(n≤100)。每堆沙子有一定的数量,如下表
13 7 8 16 21 4 18
现在要将n堆沙子归并成一堆。归并的过程为每次只能将相邻的两堆沙子堆成一堆,这样经过n-1次归并之后最后成为一堆,如上面7堆沙子,可以有多种方法归并成一堆,归并的代价是这样定义的,将两堆沙子归并为一堆时,两堆沙子数量的和称为归并2堆沙子的代价。由此可见,不同归并过程得到的总的归并代价是不一样的。
问题:n堆沙子的数量给出之后,找出一种合理的归并方法,使总的归并代价为最小。
分析思路:
堆沙子问题只能是相邻的两堆沙子,因为这个条件,让堆沙子的过程有规律可循,问题就可以转化为如果得到两堆沙子的过程,依次往下,用f(n,m)来表示最初沙子从第n到第m合并后的值,g(n,m)表示从第n堆到第m堆的总和。我们做个简单的推演。
f(1,2)=13+7=20 f(2,3)=7+8=15
f(1,3)=f(1,2)+28=f(1,2)+g(1,3)=48
f(1,3)=f(2,3)+28=f(2,3)+g(1,3)=43
当三个合并成一个的时候就有了两种选择,先合并1,2然后3,先合并2,3然后再合并1。
f(1,4)=f(1,2)+f(3,4)+g(1,4)
f(1,4)=f(1,3)+g(1,4)
f(1,4)=f(2,4)+g(1,4)
f(n,m)是由两部分组成,一部分是两个沙堆的代价和,另一部分就是g(n,m),g(n,m)在具体的的n,m下就是定值,如果想让总代价最小,就需要两个沙堆的代价和最小。
合并情况其实也是可以找到规律的
f(n,m)=f(n,m-1)+g(n,m)
f(n,m)=f(n+1,m)+g(n,m)
f(n,m)=f(n,n+x)+f(n+x+1,m)+g(n,m)
其实合并只有这两种情况,合并好的加没有合并过的,两个合并过的相加。假如f(n,m)=f(n,n+x)+f(n+x+1,m)+g(n,m) 是f(n,m)的最小值,那么我们只要保证f(n,n+x),f(n+x+1,m)都是最小值就可以。然后依次往下找组成的最小值。但是想找最小值就必须从底层开始计算,一层一层的计算。因为不知道高层的最小情况,所以必须保证从高层计算的所有结果。但是比所有结果都算出来找最小,这种算法计算的量还是比较小的。他需要从顶向下分析,从下向上计算。
能用动态规划的情况一般都是顶层结果依赖底层结果,他们直接的是一种包含关系,但是分治法分开的小组计算的结果没有任何影响。这也是两种算法的区别。
代码实现:
public static void sand() {
int[] weight = { 0, 13, 7, 8,16,21,4,18};
//用一个二维数组来存储中间需要的结果
int[][] sumData = new int[weight.length][weight.length];
//len用来控制层数计算
int len = 1;
while (len < weight.length-1) {
for (int i = 1; i < (weight.length - len); i++) {
//计算最小值的过程
int j = i + len;
int min = sumData[i + 1][j];
int k = i + 1;
while (k < j) {
int score = sumData[i][k] + sumData[k + 1][j];
if (min > score)
min = score;
k++;
}
sumData[i][j] = min + sum(weight, i, j);
}
len++;
}
System.out.println(sumData[1][weight.length-1]);
}