跟着编程之美学算法——数组分割

对于这个问题,首先按照《编程之美》中的分析对这个问题进行一定的简化。从2n个数中找n个元素,有三种可能:大于Sum/2,小于Sum/2以及等于Sum/2。而大于Sum/2与小于等于Sum/2没区别,故可以只考虑小于等于Sum/2的情况。

动态规划第一步,分析子问题:

这里我们用一个三维数组F[][][]表示子问题,F[i][j][k]表示前i个元素中选取j个元素,使得其和不超过k且最接近k。这个子问题可以根据第i个元素是否选择来进行分析:

如果我们想回溯找到一组合理的分割方式,那么在子问题的求解过程中,就要记录有效的路径,这样我们再用一个三维数组path[][][]来记录。

伪代码如下所示:

 1 F[][][] = 0
 2 path[][][] = 1
 3 
 4 for i = 1 to 2*N
 5     nLimit = min(i, N)
 6     for j = 1 to nLimit
 7         for k = 1 to sum/2
 8             F[i][j][k] = F[i-1][j][k]
 9             if k > A[i] && F[i-1][j-1][k-A[i]] + A[i] > F[i][j][k]
10                 F[i][j][k] = F[i-1][j-1][k-A[i]] + A[i]
11                 path[i][j][k] = 1
12 
13 return F[2N][N][sum/2], path[][][]
View Code

按照这样思路,该算法的时间复杂度为O(n^2*sum),空间复杂度也为O(n^2*sum)。

一下的分析,是针对上面的算法,进一步优化。优化的方向为降低空间复杂度。

优化的思路借鉴了0-1背包问题的思路,最外层循环是对元素的顺序遍历,这一步可以省略,为了保证每次计算时,问题F[i][j][k]的第i-1步为上次的状态,那么我们选择倒叙遍历变量j,这样可以保证变量i-1状态为上一步的状态。

这样改进之后的伪代码是:

 1 F[][] = 0
 2 path[][][] = 0
 3 
 4 for i = 1 to 2*N
 5     nLimit = max(i, N)
 6     for j = nLimit to 1
 7         for k = 1 to sum/2
 8             if k > A[i] && F[j-1][k-A[i]] + A[i] > F[j][k]
 9                 F[j][k] = F[j-1][k-A[i]] + A[i]
10                 path[i][j][k] = 1
11                 
12 return F[N][sum/2], path[][][]
View Code

改进后,不保存路径的空间复杂度为O(n*sum),时间复杂度不变。

下面是根据记录好的path[][][]回溯可行路径的伪码:

 1 F[][] = 0
 2 path[][][] = 0
 3 
 4 for i = 1 to 2*N
 5     nLimit = min(i, N)
 6     for j = nLimit to 1
 7         for k = 1 to sum/2
 8             if k > A[i] && F[j-1][k-A[i]] + A[i] > F[j][k]
 9                 F[j][k] = F[j-1][k-A[i]] + A[i]
10                 path[i][j][k] = 1
11                 
12 return F[N][sum/2], path[][][]
View Code

 

最后是我对算法复杂度为O(n*sum)的C++实现:

 1 #include <iostream>
 2 
 3 using namespace std;
 4 
 5 int Min(int a, int b)
 6 {
 7     if(a > b)
 8         return b;
 9     else
10         return a;
11 }
12 
13 int Split_Array(int *array, int nLength)
14 {
15     int sum = 0;
16     for(int i = 0; i < nLength; i++)
17         sum += array[i];
18     cout<<"sum: "<<sum<<endl;
19     
20     int F[nLength/2+1][sum/2+1];
21     int path[nLength+1][nLength/2+1][sum/2+1];
22     for(int i = 0; i <= nLength/2; i++)
23         for(int j = 0; j <= sum/2; j++)
24             F[i][j] = 0;
25     for(int i = 0; i <= nLength; i++)
26         for(int j = 0; j <= nLength/2; j++)
27             for(int k = 0; k <= sum/2; k++)
28                 path[i][j][k] = 0;
29     
30     for(int i = 1; i <= nLength; i++)
31     {
32         int nLimit = Min(nLength/2, i);
33         for(int j = nLimit; j > 0; j--)
34         {
35             for(int k = array[i]; k <= sum/2; k++)
36             {
37                 if(F[j][k] < F[j-1][k-array[i]] + array[i])
38                 {
39                     F[j][k] = F[j-1][k-array[i]] + array[i];
40                     path[i][j][k] = 1;
41                 }
42             }
43         }
44     }
45     int result = F[nLength/2][sum/2];
46 
47     int i = nLength;
48     int j = nLength/2;
49     int k = sum/2;
50     while(i > 0 && j > 0 && k >0)
51     {
52         if(path[i][j][k] == 1)
53         {
54             cout<<array[i]<<" ";
55             k -= array[i];
56             j--;
57         }
58         i--;
59     }
60     cout<<endl;
61     return result;
62 }
63 
64 int main()
65 {
66     int array[10] = {1,5,7,8,9,6,3,11,20,17};
67     cout<<Split_Array(array, 10)<<endl;
68     return 0;
69 }
View Code

 

 

 

 

 

posted @ 2013-05-27 12:39  ITeed  阅读(737)  评论(0编辑  收藏  举报