有两个序列a,b,大小都为n,序列元素的值任意整数,无序;要求:通过交换a,b中的元素,使[序列a元素的和]与[序列b元素的和]之间的差最小。

本文介绍了两种优化算法,旨在通过交换两个序列中的元素,最小化序列元素和之间的差值。首先,阐述了一种初步但有缺陷的方法,随后引入动态规划算法,提供了一个全局最优解决方案。通过实例演示了算法的应用和效果,强调了动态规划在复杂问题上的优势。

第一种解法:

  1. /* 
  2. *copyright@nciaebupt 转载请注明出处 
  3. *问题:有两个序列a,b,大小都为n,序列元素的值任意整数,无序; 
  4. *要求:通过交换a,b中的元素,使[序列a元素的和]与[序列b元素的和]之间的差最小。 
  5. *比如 a=[100 ,99 ,98 ,1 ,2 ,3]; b=[1, 2, 3, 4, 5, 40];结果为48 
  6. *求解思路: 
  7. *当前数组a和数组b的和之差为 
  8. *A = sum(a) - sum(b) 
  9. *a的第i个元素和b的第j个元素交换后,a和b的和之差为 
  10. *A' = sum(a) - a[i] + b[j] - (sum(b) - b[j] + a[i]) 
  11. *    = sum(a) - sum(b) - 2 (a[i] - b[j]) 
  12. *    = A - 2 (a[i] - b[j]) 
  13. *设x = a[i] - b[j] 
  14. *所以 A' = A-2x 
  15. *假设A > 0, 
  16. *当x 在 (0,A)之间时,做这样的交换才能使得交换后的a和b的和之差变小, 
  17. *x越接近A/2效果越好, 
  18. *如果找不到在(0,A)之间的x,则当前的a和b就是答案。 
  19. *所以算法大概如下: 
  20. *在a和b中寻找使得x在(0,A)之间并且最接近A/2的i和j,交换相应的i和j元素, 
  21. *重新计算A后,重复前面的步骤直至找不到(0,A)之间的x为止。 
  22. */  
  23. #include <cstdio>  
  24. #include <cstdlib>  
  25. #include <iostream>  
  26. using namespace std;  
  27.   
  28. int sum(int a[],int len)  
  29. {  
  30.     int res = 0;  
  31.     for(int i=0;i<len;++i)  
  32.     {  
  33.         res = res + a[i];  
  34.     }  
  35.     return res;  
  36. }  
  37.   
  38. void swap(int * a,int * b)  
  39. {  
  40.     int tmp = *a;  
  41.     *a = *b;  
  42.     *b = tmp;  
  43. }  
  44.   
  45. bool isXinRangeA(int x,int A)  
  46. {  
  47.     if((x < A && x > 0) ||(x > A && x < 0))  
  48.         return true;  
  49.     return false;  
  50. }  
  51.   
  52. void exchangeAB(int *a, int *b,int len)  
  53. {  
  54.     int A = sum(a,len) - sum(b,len);  
  55.     double min = a[0]-b[0]-A/2.0;  
  56.     int ii = 0;  
  57.     int jj = 0;  
  58.     int flag = 0;  
  59.     if(A == 0)  
  60.         return ;  
  61.     for(int i = 0;i < len;++i)  
  62.     {  
  63.         for(int j = 0;j < len;++j)  
  64.         {  
  65.             int x = a[i] - b[j];  
  66.             if( x == 0)  
  67.                 continue;  
  68.             if(isXinRangeA(x,A))  
  69.             {  
  70.                 double tmp = x - A/2.0;  
  71.                 if(tmp < min)  
  72.                 {  
  73.                     min = tmp;  
  74.                     flag = 1;  
  75.                     ii = i;  
  76.                     jj = j;  
  77.                     cout<<"***"<<endl;  
  78.                 }  
  79.             }  
  80.         }  
  81.     }  
  82.     if(flag == 1)  
  83.     {  
  84.         swap(&a[ii],&b[jj]);  
  85.         exchangeAB(a,b,len);  
  86.     }  
  87.   
  88. }  
  89.   
  90. int main(int args,char ** argv)  
  91. {  
  92.     //int a[] = {100 ,99 ,98 ,1 ,2 ,3};  
  93.     //int b[] = {1, 2, 3, 4, 5, 40};  
  94.     int a[] = {-3,9,10,65};  
  95.     int b[] = {5,6,13,55};  
  96.     int len = sizeof(a)/sizeof(int);  
  97.     exchangeAB(a,b,len);  
  98.     //打印数组A  
  99.     for(int i = 0;i < len;++i)  
  100.     {  
  101.         cout<<a[i]<<" ";  
  102.     }  
  103.     cout<<endl;  
  104.     //打印数组B  
  105.     for(int j = 0;j < len;++j)  
  106.     {  
  107.         cout<<b[j]<<" ";  
  108.     }  
  109.     cout<<endl;  
  110.     //打印数组A的和与数组B的和的差值  
  111.     cout<<abs(sum(a,len)-sum(b,len))<<endl;  
  112.   
  113.     system("pause");  
  114.     return 0;  
  115. }  

以上这种解法是有缺陷的,得到的结果并不一定是全局最优值,因为:
一,题目要求的是差值最小的方案,所以交换一对数据是不能实现的。
二,最后交换一对数据无法使差值减小,但是存在同时交换两对(还有更多对)数据可以减小差值的可能。
比如:如果两个序列分别是[-3,9,10,65]和[5,6,13,55],按以上的算法这就是最优解了。可是显然[-3,5,13,65]和[6,9,10,55]更好。


所以下面给出一种能找出全局最优值的解法,使用动态规划的算法实现:

  1. /* 
  2. *copyright@nciaebupt 转载请注明出处 
  3. *问题:有两个序列a,b,大小都为n,序列元素的值任意整数,无序; 
  4. *要求:通过交换a,b中的元素,使[序列a元素的和]与[序列b元素的和]之间的差最小。 
  5. *比如 a=[100 ,99 ,98 ,1 ,2 ,3]; b=[1, 2, 3, 4, 5, 40];结果为48 
  6. *求解思路:使用动态规划的思路 
  7. *   外阶段:在前i个数中进行选择,i=1,2...2*n。 
  8. *   内阶段:从这i个数中任意选出j个数,j=1,2...i。 
  9. *   状态:这j个数的和为s,s=1,2...sum/2。 
  10. *   决策:决定这j个数的和有两种决策,一个是这j个数中包含第i个数,另一个是不包含第i个数。 
  11. *   dp[k][s]表示从前k个数中取任意个数,且这些数之和为s的取法是否存在。 
  12. *在程序中我们给出S(k)的所有可能取值v和arr[k],去寻找v-arr[k]是否在S(k-1)={Vi}中, 
  13. *由于S(k)的可能取值的集合的大小与k无关, 
  14. *所以这样设计的动态规划算法其第k步的时间复杂度与k无关 
  15. */  
  16. #include <cstdio>  
  17. #include <cstdlib>  
  18. #include <cstring>  
  19. #include <iostream>  
  20. using namespace std;  
  21. #define MAXN 101  
  22. #define MAXSUM 100000  
  23. bool dp[MAXN][MAXSUM];  
  24.   
  25. int c_sum(int *c,int len)  
  26. {  
  27.     int sum = 0;  
  28.     for(int i = 0;i < len;++i)  
  29.     {  
  30.         sum = sum + c[i];  
  31.     }  
  32.     return sum;  
  33. }  
  34.   
  35. int min(int a,int b)  
  36. {  
  37.     if(a < b)  
  38.         return a;  
  39.     else  
  40.         return b;  
  41. }  
  42.   
  43. void exchangeAB(int * c,int len)  
  44. {  
  45.     int sum = c_sum(c,2*len);  
  46.     memset(dp,0,sizeof(dp));  
  47.     dp[0][0] = true;  
  48.     //外阶段i表示第i个数,内阶段j表示选取数的个数  
  49.     for(int i = 1;i <= 2*len;++i)//外阶段i  
  50.     {  
  51.         for(int j = min(i,len);j >=1;--j)//内阶段j  
  52.         {  
  53.             for(int s = 1;s <= sum/2;++s)//S(k)的所有可能取值s  
  54.             {  
  55.                 if((s >= c[i]) && dp[j-1][s-c[i]])//j个数中是否包含第i个数  
  56.                 {  
  57.                     dp[j][s]=true;  
  58.                     //cout<<s<<endl;  
  59.                 }  
  60.             }  
  61.         }  
  62.     }  
  63.     int s = sum/2;  
  64.     for(;s>=1 && !dp[len][s];s--)  
  65.         ;  
  66.     cout<<sum - 2*s<<endl;  
  67.   
  68. }  
  69.   
  70. int main(int args,char ** argv)  
  71. {  
  72.     //int a[] = {100 ,99 ,98 ,1 ,2 ,3};  
  73.     //int b[] = {1, 2, 3, 4, 5, 40};  
  74.     int a[] = {-3,9,10,65};  
  75.     int b[] = {5,6,13,55};  
  76.     int len = sizeof(a)/sizeof(int);  
  77.     //将数组a与b中的值放在数组c中  
  78.     int c[MAXN];  
  79.     int pos = 0;  
  80.     for(int i = 0;i <= len;++i)  
  81.     {  
  82.         c[i] = a[i];  
  83.         pos = i;  
  84.     }  
  85.     for(int j = 0;j <= len;++j)  
  86.     {  
  87.         c[pos+j] = b[j];  
  88.     }  
  89.   
  90.     for(int i = 0;i < 2*len;++i)  
  91.     {  
  92.         cout<<c[i]<<" ";  
  93.     }  
  94.     cout<<endl;  
  95.     exchangeAB(c,len);  
  96.     system("pause");  
  97.     return 0;  
  98. }  

注意:如果数组中有负数的话,上面的背包策略就不能使用了(因为第三重循环中的s是作为数组的下标的,不能出现负数的),需要将数组中的所有数组都加上最小的那个负数的绝对值,将数组中的元素全部都增加一定的范围,全部转化为正数,然后再使用上面的背包策略就可以解决了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值