问题来源:《编程之美》2.18数组分割 详情见Page(202-204)
问题描述: 有一无序,个数为2n的正整数数组,要求将其拆分为元素个数为n的两个数组,并使两个子数组的和最接近。
个人解法:两个子数组和最接近,可以有多种转化方式。比较典型的有,其中一个子数组的和最接近原数组和的二分之一;两个子数组的和差值的绝对值最小等等。
一个比较直观的思路是首先将原数组排序,然后按照“首尾依次合并”的原则建立两个子数组,如原数组排序后为{1,2,3,4,5,6,7,8,9,10},拆分得到的子数组分别为{1,10,3,8,5}和{2,9,4,7,6}。很遗憾,这种方法并不能保证两个子数组和最接近。比如原数组排序后为{1,2,3,4,5,6,7,100},按照“首尾依次合并”的原则得到两个子数组为{1,100,3,6}和{2,7,4,5},但真正的最优解应当是{1,2,3,100}和{4,5,6,7}。很明显“首尾依次合并”的办法只能解决原数组是等差数列排布的特殊情况。虽然这种方法并不能解决问题,但可以将其作为一步预处理,尽可能简化后续的搜索过程。
设原数组各个元素之和为sum,预处理后的两个子数组各元素之和分别为left_sum和right_sum。如果left_sum和right_sum相等,则当前的划分方案即为最优解;若left_sum和right_sum一大一小,则可能需要进行调整,若当前方案不是最优解,则需要对两个数组中的部分元素进行对换。
简单起见,从单个元素的互换进行分析,多个元素互换可以看做单一元素互换的分步进行,最终得到的结果是一样的。元素对换的目标应当是使新得到的数组和更为接近。直观地讲就是,从和较大的数组中选取一个较大的元素,不妨设其为big,从和较小的数组中选取一个较小的数,不妨设其为small,其中big>small,将big和small互换,设互换前两数组之差的绝对值为target,元素互换后,新的数组和差值的绝对值应当减小,最理想的结果为0。若要达成此目标,则只有当0<big-small<target时才能进行互换。
证明过程非常简单(此处不妨设left_sum>right_sum):
|left_sum-right_sum|=left_sum-right_sum =target;
|(left_sum-big+small)-(right_sum-small+big)|
= |left_sum-right_sum-2(big-small)|
= | target -2(big-small)|
= targetnew
targetnew<target,则0<big-small<target
此处选取互换前和较大的数组进行分析。最优的互换结果应当是big-small=target/2,此时新得到的数组和完全相等。遍历当前和较大的数组中的所有元素,分别与和较小的数组中的元素做差(实际操作时只需要与部分元素做差即可,因为有big>small的限定),设其差值为delta,则| delt-tagert/2|的值越小,表明当前互换方案更优。选出最优的互换方案后,会得到两个和更接近的新数组。之后按照上述的方法继续迭代求解,直至遍历所有元素都无法找到满足0<big-small<target的方案时停止,此时即得到最优分配方案。
由于之前已经对数组进行过排序的预处理,根据0<big-small<target的条件,可以很快地在和较小的数组中定位搜索的开始和终止位置,从而优化算法的时间复杂度。
下面是基于VC6.0IDE的C++实现代码。在下非计算机专业出身,代码中多有不规范之处,恳请斧正,在此谢过。
#include<iostream>
using namespace std;
#define LENGTH 10
#define LEFT 0
#define RIGHT 1
#define INF 65535
void quick_sort(int* pt,int input_lenght);
struct my_struct
{
int my_array[LENGTH];
int my_attr[LENGTH];
};
void main(void)
{
int test[LENGTH]={1,5,7,8,9,6,3,11,20,17};
quick_sort(test,LENGTH);
int left_sum=0;
int right_sum=0;
int delt=0;
int bigger=-1;//0 left bigger 1 right bigger
int tagert;
int current_dis;
int best_dis=INF;
int is_find=0;//1 找到
int change_big_index;
int change_small_index;
int change_count=0;
int i;
int j;
int little=0;
int large=LENGTH-1;
my_struct sorted_array;
for ( i=0;i<LENGTH;i++)
{
sorted_array.my_array[i]=test[i];
cout<<test[i]<<endl;
}
//数组分划
while((large-little+1)>=4)
{
sorted_array.my_attr[little]=LEFT;
sorted_array.my_attr[little+1]=RIGHT;
sorted_array.my_attr[large]=LEFT;
sorted_array.my_attr[large-1]=RIGHT;
little=little+2;
large=large-2;
}
if ((large-little+1)==2)
{
sorted_array.my_attr[little]=LEFT;
sorted_array.my_attr[large]=RIGHT;
}
cout<<“左侧数组”<<endl;
for (i=0;i<LENGTH;i++)
{
if (sorted_array.my_attr[i]==LEFT)
{
cout<<sorted_array.my_array[i]<<endl;
}
}
cout<<“右侧数组”<<endl;
for (i=0;i<LENGTH;i++)
{
if (sorted_array.my_attr[i]==RIGHT)
{
cout<<sorted_array.my_array[i]<<endl;
}
}
while(1)
{
//迭代操作开始
is_find=0;
left_sum=0;
right_sum=0;
for (i=0;i<LENGTH;i++)
{
if (sorted_array.my_attr[i]==LEFT)
{
left_sum=left_sum+sorted_array.my_array[i];
}
else
{
if (sorted_array.my_attr[i]==RIGHT)
{
right_sum=right_sum+sorted_array.my_array[i];
}
else
{
cout<<“出现既不属于左不属于右的元素”<<endl;
}
}
}
if (left_sum>right_sum)
{
tagert=left_sum-right_sum;
bigger=LEFT;
}
else
{
if (right_sum>left_sum)
{
tagert=right_sum-left_sum;
bigger=RIGHT;
}
else //相等
{
cout<<“出现相等情况的完美情况”<<endl;
break;
}
}
tagert=tagert/2;//存在相等怎么处理?
for (i=0;i<LENGTH;i++)
{
if (sorted_array.my_attr[i]==bigger)
{
for (j=i-1;j>=0;j–)
{
if (sorted_array.my_attr[j]!=bigger)
{
delt=sorted_array.my_array[i]-sorted_array.my_array[j];
current_dis=abs(delt-tagert);
if (current_dis>tagert)
{
break;
}
else
{
//更新
is_find=1;
if (current_dis<best_dis)
{
best_dis=current_dis;
change_big_index=i;
change_small_index=j;
}
}
}//结束单个元素的求差操作
}//一个元素遍历完毕
}//end if
}//end for
if (is_find==1) //找不到就可以结束了
{
if (bigger==LEFT)
{
sorted_array.my_attr[change_big_index]=RIGHT;
sorted_array.my_attr[change_small_index]=LEFT;
}
else
{
if (bigger==RIGHT)
{
sorted_array.my_attr[change_big_index]=LEFT;
sorted_array.my_attr[change_small_index]=RIGHT;
}
else//直接相等 不可能
{
cout<<“严重错误”<<endl;
}
}
change_count++;
cout<<“第”<<change_count<<“次调整位置”<<endl<<endl;
//输出看下
cout<<“左侧数组”<<endl;
for (i=0;i<LENGTH;i++)
{
if (sorted_array.my_attr[i]==LEFT)
{
cout<<sorted_array.my_array[i]<<endl;
}
}
cout<<“右侧数组”<<endl;
for (i=0;i<LENGTH;i++)
{
if (sorted_array.my_attr[i]==RIGHT)
{
cout<<sorted_array.my_array[i]<<endl;
}
}//输出完毕
cout<<endl<<endl<<endl;
} //至此所有基本操作完成
if (is_find==0)//没找到
{
break;
}
}//end while(1)
}//end main
void quick_sort(int* pt,int input_lenght)
{
int my_length=input_lenght;
int* my_array=pt;
int key;
int i;//small
int j;//big
char is_end=0;
if (my_length==1)
{
return ;
}
i=0;
j=my_length-1;
key=my_array[i];
while(1)
{
for (;j>i;j–)
{
if(my_array[j]<key)
{
my_array[i]=my_array[j];
my_array[j]=key;
break;
}
}
//判断一下 是否i和j相等
if (i==j)
{
break;
}
for (;i<j;i++)
{
if (my_array[i]>key)
{
my_array[j]=my_array[i];
my_array[i]=key;
break;
}
}
if (i==j)
{
break;
}
}
//计算新的首尾
if (i>0)
{
quick_sort(my_array,i);
}
if (j<(my_length-1))
{
quick_sort(my_array+j+1,my_length-i-1);
}
}