数组分割问题-详细版
(2012-07-06 00:22:41)题记:这道题和《编程之美》一书中2.18节的数组分割区别不大,但本人觉得《编程之美》这一节讲的不够透彻,不好理解(或许本人愚钝),故给出自己的思路,同时也给出打印其中一种方案的方法(这一点《编程之美》并没有提到)。
两个序列大小均为n,序列元素的值为任一整数,无序;
要求通过交换两个序列的元素,使序列a元素之和与序列b的元素之和的差最小(可能存在很多种组合,要求找出其中一种即可)。
如序列:1
- F[][][]←
0 -
- for
i ← 1 to 2*N -
-
nLimit ← min(i,N) -
-
do for j ← 1 to nLimit -
-
do for k ← 1 to Sum/2 -
-
F[i][j][k] ← F[i-1][j][k] -
-
if (k >= A[i] && F[i][j][k] < F[i-1][j-1][k-A[i]]+A[i]) -
-
then F[i][j][k] ← F[i-1][j-1][k-A[i]]+A[i] -
- return
F[2N][N][Sum/2]
- F[][][]←
0 -
- Path[][][]←
0 -
- for
i ← 1 to 2*N -
-
nLimit ← min(i,N) -
-
do for j ← 1 to nLimit -
-
do for k ← 1 to Sum/2 -
-
F[i][j][k] ← F[i-1][j][k] -
-
if (k >= A[i] && F[i][j][k] < F[i-1][j-1][k-A[i]]+A[i]) -
-
then F[i][j][k] ← F[i-1][j-1][k-A[i]]+A[i] -
-
Path[i][j][k] ← 1 -
- return
F[2N][N][Sum/2] and Path[][][]
根据求得的Path[][][]我们可以从F[2N][N][Sum/2]往F[0][0][0]逆着推导来打印轨迹对应的元素。伪代码如下:
- i
← 2N -
- j
← N -
- k
← Sum/2 -
- while
(i > 0 && j > 0 && k > 0) -
-
do if(Path[i][j][k] = 1) -
-
then Print A[i] -
-
j ← j-1 -
-
k ← k-A[i] -
-
i ← i-1
上面的伪代码的意思是,每当找到一个Path[][][]=1,就将其对应的A[i]输出,因为已经确定一个所以j应该自减1,而k代表总和,所以也应该减去A[i]。至于为什么不管Path[][][]是否为1都需要i自减1,这一点可以参照本人博文《背包问题——“01背包”详解及实现(包含背包中具体物品的求解)》中的路径求法相关内容。
下面开始优化空间复制度为O(N*Sum/2)
- F[][]←
0 -
- for
i ← 1 to 2*N -
-
nLimit ← min(i,N) -
-
do for j ← nLimit to 1 -
-
do for k ← A[i] to Sum/2 -
-
if (F[j][k] < F[j-1][k-A[i]]+A[i]) -
-
then F[j][k] ← F[j-1][k-A[i]]+A[i] -
-
-
- return
F[N][Sum/2] and Path[][][]
上面的伪代码基本上和《编程之美》2.18节最后所给的代码基本一致了,但是里面并不含Path,如果要打印其中一种方案,那么仍需要2N*N*Sum/2的空间来存放轨迹。即
- F[][]←
0 -
- Path[][][]←
0 -
- for
i ← 1 to 2*N -
-
nLimit ← min(i,N) -
-
do for j ← nLimit to 1 -
-
do for k ← A[i] to Sum/2 -
-
if (F[j][k] < F[j-1][k-A[i]]+A[i]) -
-
then F[j][k] ← F[j-1][k-A[i]]+A[i] -
-
Path[i][j][k] ← 1 -
- return
F[N][Sum/2] and Path[][][]
打印路径的伪代码与之前的一模一样,这里不再重写。
下面给出《编程之美》2.18节所讲的“数组分割”中给出的数据进行本文思想的C++代码实现
数组1
- #include
<iostream> - #include
<cstring> - #include
"CreateArray.h" //该头文件是动态开辟及销毁二维三维数组的,读者自己实现 - using
namespace std;
//算法时间复杂度为O(N2Sum),空间复杂度为O(N2Sum)
- int
AdjustArray(int array[], int nLen, int nToBeClosed) - {
-
int*** F = NULL; -
int*** Path = NULL; -
CreateThreeDimArray(F,nLen+1,nLen/2+1,nToBeClosed+1); //创建三维数组,存放每一个状态 -
CreateThreeDimArray(Path,nLen+1,nLen/2+1,nToBeClosed+1); //创建三维数组,存放轨迹 -
for(int i = 1; i <= nLen; i++) -
{ -
int nLimit = min(i,nLen/2); -
for(int j = 1; j <= nLimit; j++) -
{ -
for(int k = 1; k <= nToBeClosed; k++) -
{ -
F[i][j][k] = F[i-1][j][k]; -
if(k >= array[i-1]) -
{ -
if(F[i][j][k] < F[i-1][j-1][k-array[i-1]]+array[i-1]) -
{ -
F[i][j][k] = F[i-1][j-1][k-array[i-1]]+array[i-1]; -
Path[i][j][k] = 1; -
} -
} -
} -
} -
} -
-
//打印调整后的其中一个数组 -
int i = nLen, j = nLen/2, k = nToBeClosed; -
while(i > 0 && j > 0 && k > 0) -
{ -
if(Path[i][j][k] == 1) -
{ -
cout << array[i-1] << "\t"; -
k -= array[i-1]; -
j--; -
} -
i--; -
} -
cout << endl; -
-
int nRet = F[nLen][nLen/2][nToBeClosed]; -
DestroyThreeDimArray(Path,nLen+1,nLen/2+1); //销毁轨迹表 -
DestroyThreeDimArray(F,nLen,nLen/2+1); //销毁状态表 -
return nRet; - }
//这里参数array为整个合并后的数组序列,nLen为合并后的数组长,nToBeClosed是之前所提的Sum/2
//算法时间复杂度为O(N2Sum),空间复杂度不含Path为O(NSum/2),含Path为O(N2Sum)
- int
Fun2(int array[], int nLen, int nToBeClosed) - {
-
int** F = NULL; -
int*** Path = NULL; -
CreateTwoDimArray(F,nLen/2+1,nToBeClosed+1); //创建二维状态表 -
CreateThreeDimArray(Path,nLen+1,nLen/2+1,nToBeClosed+1);//创建三维轨迹表 -
-
for(int i = 1; i <= nLen; i++) -
{ -
int nLimit = min(i,nLen/2); -
for(int j = nLimit; j >= 1; j--) -
{ -
for(int k = array[i-1]; k <= nToBeClosed; k++) -
{ -
if(F[j][k] < F[j-1][k-array[i-1]]+array[i-1]) -
{ -
F[j][k] = F[j-1][k-array[i-1]]+array[i-1]; -
Path[i][j][k] = 1; -
} -
} -
} -
} -
-
//打印调整后的其中一个数组 -
int i = nLen, j = nLen/2, k = nToBeClosed; -
while(i > 0 && j > 0 && k > 0) -
{ -
if(Path[i][j][k] == 1) -
{ -
cout << array[i-1] << "\t"; -
k -= array[i-1]; -
j--; -
} -
i--; -
} -
cout << endl; -
-
int nRet = F[nLen/2][nToBeClosed]; -
DestroyTwoDimArray(F,nLen/2+1); //销毁二维状态表 -
DestroyThreeDimArray(Path,nLen+1,nLen/2+1); //销毁三维轨迹表 -
return nRet; - }
测试代码
- int
main() - {
-
int array[] = {1,5,7,8,9,6,3,11,20,17}; -
int nSum = 0; -
for(int i = 0; i < sizeof(array)/sizeof(int); i++) -
nSum += array[i]; -
int nToBeClosed = nSum/2; -
-
cout << Fun(array,sizeof(array)/sizeof(int),nToBeClosed) << endl; -
cout << Fun2(array,sizeof(array)/sizeof(int),nToBeClosed) << endl; -
return 0; - }