编程之美读书笔记2.18—数组分割


解法1(参照每天一算法32):

       当前数组a和数组b的和之差为   A = sum(a) - sum(b)   a的第i个元素和b的第j个元素交换后,a和b的和之差为   A' = sum(a) - a[i] + b[j] - (sum(b)- b[j] + a[i])   = sum(a) - sum(b) - 2 (a[i] - b[j])    = A - 2 (a[i] - b[j])  

        设x= a[i] - b[j]    |A| - |A'| = |A| - |A-2x|    假设A> 0,    当x在(0,A)之间时,做这样的交换才能使得交换后的a和b的和之差变小,x越接近A/2效果越好,   如果找不到 在(0,A)之间的x,则当前的a和b就是答案。    

      所以算法大概如下: 在a和b中寻找使得x在(0,A)之间并且最接近A/2的i和j,交换相应的i和j元素,重新计算A后,重复前面的步骤直至找不到(0,A)之间的x为止。

#include <iostream>
using namespace std;
int sum(int a[],int n)
{
	int sum=0;
	for(int i=0;i<n;i++)
		sum+=a[i];
	return sum;
}

void adjust(int *a,int *b,int n)
{
	bool  ifCycle=true;
	while(ifCycle)
	{
		ifCycle=false;
		for(int i=0;i<n;i++)
		{
			for(int j=0;j<n;j++)
			{
				int subij=a[i]-b[j];
				int sub=sum(a,n)-sum(b,n);
				if(subij*sub>0 && abs(subij)<abs(sub))  
				{     
					ifCycle = true;  
					int item = a[i];  
					a[i] = b[j];  
					b[j] = item;  
				} 
			}  
		} 
	}
}

int main() 
{
	int  A[]={5,8,1,6,40,46};
	int n=sizeof(A)/sizeof(int);
	int *a=new int [n/2];
	int *b=new int [n/2];
	for (int i=0;i<n/2;i++)  //初试分配数组
	{
		a[i]=A[i];
		b[i]=A[n/2+i];
	}
		
	adjust(a,b,n/2);   

	for(int i=0;i<n/2;i++)
		cout<<a[i]<<"  ";
	cout<<endl;
	for(int i=0;i<n/2;i++)
		cout<<b[i]<<"  ";
	cout<<endl;
	system("pause");
	delete []a;
	delete []b;
	return 0;
}

  

解法2:

这个问题存储的是从前k个数中选取任意个数,且其和为s的取法是否存在flag[k][s]。之所以将选出的数之和放在下标中,而不是作为dp[k]的值,是因为那种做法不满足动态规划的前提——最优化原理,假设我们找到最优解有k个数p1p2...pk(选出的这k个数之和是最接近sum/2的),但最优解的前k-1个数p1p2...pk-1之和可能并不是最接近sum/2的,也就是说可能在访问到pk之前有另一组数q1q2....qk-1其和相比p1p2...pk-1之和会更接近sum/2,即最优解的子问题并不是最优的,所以不满足最优化原理。因此我们需要将dp[k]的值作为下标存储起来,将这个最优问题转化为判定问题,用带动态规划的思想的递推法来解。


外阶段:在前k1个数中进行选择,k1=1,2...2*n。
内阶段:从这k1个数中任意选出k2个数,k2=1,2...k1。

状态:这k2个数的和为s,s=1,2...sum/2。

决策:决定这k2个数的和有两种决策,一个是这k2个数中包含第k1个数,另一个是不包含第k1个数。
dp[k][s]表示从前k个数中取任意个数,且这些数之和为s的取法是否存在。


选出的物体数必须为n,这个条件限制了内阶段k2的取值范围,并且flag[k][s]的含义也发生变化。这里的dp[k][s]表示从前k个数中取k个数,且k不超过n,且这些数之和为s的取法是否存在。

#include <iostream>
using namespace std;

void func(int *A,int len) 
{
		int i,j,s=0;
	   
	    int n = len / 2;
	 
	   int sum = 0;
	   for (int i = 0; i <len ; i++) {
		   sum += A[i];
		  }
	   			
		 //  动态分配:flag[i][j]:任意i个数组元素之和是j,则flag[i][j]为true
		bool **flag;
		flag=(bool **)malloc(sizeof(bool*)*(len+1));/*创建t个行指针*/
        for (i=0;i<len+1;++i)/*分别创建数组的每一行*/
              flag[i]=(bool *)malloc(sizeof(bool)*(sum/2+1));
		for (i = 0; i < len; i++)
 			for (j = 0; j < sum / 2 + 1; j++)
 				flag[i][j] = false;
 		
		flag[0][0] = true;
		
		for (int k = 0; k < len; k++) {          // 外阶段k表示第k个数,
			for (i = k > n ? n : k; i >= 1; i--) {          //内阶段i表示选取数的个数  
			    for (s = 0; s <= sum / 2; s++) {                 // 状态s   
					if (s >= A[k] && flag[i - 1][s - A[k]])     // s >= A[k]条件是为了让flag数组的第二维不越界
					{        flag[i][s] = true;
				   }
				}
				
			}
		}
				
		for (i = sum / 2; i >= 0; i--) {      //最接近Sum/2的和
			if (flag[n][i]) {
				cout<<"sum is  "<< sum<<endl;
				cout<<"sum/2 is  " << sum / 2<<endl;
				cout<<"i is  " <<i<<endl;
				cout<<"minimum delta is " <<abs(2 * i - sum)<<endl;
                s=i;
				break;
			}
		}

		 for(i=0;i<len;i++)       //输出取得和的len/位数组
		 {
			 for(j=len-1;j>i;j--)
				{
					for(int k=len-1;k>j;k--)
			        {
			             	  if (flag[len/2][s-A[i]]  && flag[len/2-1][s-A[i]-A[j]]   && s-A[i]-A[j]-A[k]==0   )
			                {
				                 cout<<A[i]<<"\t"<<A[j]<<"\t"<<A[k]<<endl;
			                }
					}
			  } 
		 }
}

void  main ()
{
      int A[] = { 1, 2, 3, 5, 7, 8, 9 };
     int length = sizeof(A)/sizeof(int);
	   
	  func(A,length);
	

	  system("pause");
	
	  
}

	






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值