交换a,b中的元素,使序列a元素的和与序列b元素的和之间的差最小

题目:有两个序列a,b,大小都为n,序列元素的值任意整数,无序;要求:通过交换a,b中的元素,使[序列a元素的和]与[序列b元素的和]之间的差最小。
      例如:  
               var   a=[100 ,99 ,98 ,1 ,2 ,3];       var  b=[1, 2, 3, 4, 5, 40];

 

思路一:
   当前数组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>
#include <cmath>
using namespace std;

int GetSum(int *a, int len)
{
    int sum = 0;
    for(int i = 0; i < len; i++)
        sum += a[i];
    return sum;
}

int GetMinSub(int *a, int *b, int len)
{
    if(a == NULL || b == NULL || len <= 0)
        return -1;
    bool flag = true;
    while(flag)
    {
        flag = false;
        int suma = GetSum(a, len);
        int sumb = GetSum(b, len);
        int sub = suma - sumb;
        for(int i = 0; i < len; i++)
        {
            for(int j = 0; j < len; j++)
            {
                int subij = a[i] - b[j];
                if(subij * sub > 0 && abs(subij) < abs(sub))
                {
                    int t = a[i];
                    a[i] = b[j];
                    b[j] = t;
                    flag = true;
                    break;
                }
            }
            if(flag)
                break;

        }                    
    }
    
    return GetSum(a, len) - GetSum(b, len);
    
}



int main(void)
{
    int a[] = {1, 4, 5, 800};
    int b[] = {2, 3, 6, 700};
    int len = sizeof(a) / sizeof(a[0]);
    int minSub = GetMinSub(a, b, len);
    for(int i = 0; i < len; ++i)
        cout<<a[i]<<" ";
    cout<<endl;
    
    for(int i = 0; i < len; ++i)
        cout<<b[i]<<" ";
    cout<<endl;
    cout<<minSub<<endl;
    return 0;
}

结果:

image

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

 

思路二:

    1.将两序列合并为一个序列,并排序,为序列Source
         2.拿出最大元素Big,次大的元素Small
         3.在余下的序列S[:-2]进行平分,得到序列max,min
         4.将Small加到max序列,将Big加大min序列,重新计算新序列和,和大的为max,小的为min。
a={1,2,3,4,5} b={6,7,8,9,10}
s={1,2,3,4,5,6,7,8,9,10}
a={1,3,6,7,10}
b={2,4,5,8,9}

 

思路三:

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

  1. 求解思路:使用动态规划的思路
  2. *   外阶段:在前i个数中进行选择,i=1,2...2*n。
  3. *   内阶段:从这i个数中任意选出j个数,j=1,2...i。
  4. *   状态:这j个数的和为s,s=1,2...sum/2。
  5. *   决策:决定这j个数的和有两种决策,一个是这j个数中包含第i个数,另一个是不包含第i个数。
  6. *   dp[k][s]表示从前k个数中取任意个数,且这些数之和为s的取法是否存在。
  7. 在程序中我们给出S(k)的所有可能取值v和arr[k],去寻找v-arr[k]是否在S(k-1)={Vi}中,由于S(k)的可能取值的集合的大小与k无关,所以这样设计的动态规划算法其第k步的时间复杂度与k无关。
  8. 代码如下:
  9. /*
    *copyright@nciaebupt 转载请注明出处
    *问题:有两个序列a,b,大小都为n,序列元素的值任意整数,无序;
    *要求:通过交换a,b中的元素,使[序列a元素的和]与[序列b元素的和]之间的差最小。
    *比如 a=[100 ,99 ,98 ,1 ,2 ,3]; b=[1, 2, 3, 4, 5, 40];结果为48
    *求解思路:使用动态规划的思路
    *   外阶段:在前i个数中进行选择,i=1,2...2*n。
    *   内阶段:从这i个数中任意选出j个数,j=1,2...i。
    *   状态:这j个数的和为s,s=1,2...sum/2。
    *   决策:决定这j个数的和有两种决策,一个是这j个数中包含第i个数,另一个是不包含第i个数。
    *   dp[k][s]表示从前k个数中取任意个数,且这些数之和为s的取法是否存在。
    *在程序中我们给出S(k)的所有可能取值v和arr[k],去寻找v-arr[k]是否在S(k-1)={Vi}中,
    *由于S(k)的可能取值的集合的大小与k无关,
    *所以这样设计的动态规划算法其第k步的时间复杂度与k无关
    */
    #include <cstdio>
    #include <cstdlib>
    #include <cstring>
    #include <iostream>
    using namespace std;
    #define MAXN 101
    #define MAXSUM 100000
    bool dp[MAXN][MAXSUM];
    
    int c_sum(int *c,int len)
    {
        int sum = 0;
        for(int i = 0;i < len;++i)
        {
            sum = sum + c[i];
        }
        return sum;
    }
    
    int min(int a,int b)
    {
        if(a < b)
            return a;
        else
            return b;
    }
    
    void exchangeAB(int * c,int len)
    {
        int sum = c_sum(c,2*len);
        memset(dp,0,sizeof(dp));
        dp[0][0] = true;
        //外阶段i表示第i个数,内阶段j表示选取数的个数
        for(int i = 1;i <= 2*len;++i)//外阶段i
        {
            for(int j = min(i,len);j >=1;--j)//内阶段j
            {
                for(int s = 1;s <= sum/2;++s)//S(k)的所有可能取值s
                {
                    if((s >= c[i]) && dp[j-1][s-c[i]])//j个数中是否包含第i个数
                    {
                        dp[j][s]=true;
                        //cout<<s<<endl;
                    }
                }
            }
        }
        int s = sum/2;
        for(;s>=1 && !dp[len][s];s--)
            ;
        cout<<sum - 2*s<<endl;
    
    }
    
    int main(int args,char ** argv)
    {
        //int a[] = {100 ,99 ,98 ,1 ,2 ,3};
        //int b[] = {1, 2, 3, 4, 5, 40};
        int a[] = {-3,9,10,65};
        int b[] = {5,6,13,55};
        int len = sizeof(a)/sizeof(int);
        //将数组a与b中的值放在数组c中
        int c[MAXN];
        int pos = 0;
        for(int i = 0;i <= len;++i)
        {
            c[i] = a[i];
            pos = i;
        }
        for(int j = 0;j <= len;++j)
        {
            c[pos+j] = b[j];
        }
    
        for(int i = 0;i < 2*len;++i)
        {
            cout<<c[i]<<" ";
        }
        cout<<endl;
        exchangeAB(c,len);
        return 0;
    }
posted @ 2014-03-20 10:07  mickole  阅读(617)  评论(0)    收藏  举报