第一種解法:
- /*
- *copyright@nciaebupt 轉載請註明出處
- *問題:有兩個序列a,b,大小都爲n,序列元素的值任意整數,無序;
- *要求:通過交換a,b中的元素,使[序列a元素的和]與[序列b元素的和]之間的差最小。
- *比如 a=[100 ,99 ,98 ,1 ,2 ,3]; b=[1, 2, 3, 4, 5, 40];結果爲48
- *求解思路:
- *當前數組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-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 <cstdio>
- #include <cstdlib>
- #include <iostream>
- using namespace std;
- int sum(int a[],int len)
- {
- int res = 0;
- for(int i=0;i<len;++i)
- {
- res = res + a[i];
- }
- return res;
- }
- void swap(int * a,int * b)
- {
- int tmp = *a;
- *a = *b;
- *b = tmp;
- }
- bool isXinRangeA(int x,int A)
- {
- if((x < A && x > 0) ||(x > A && x < 0))
- return true;
- return false;
- }
- void exchangeAB(int *a, int *b,int len)
- {
- int A = sum(a,len) - sum(b,len);
- double min = a[0]-b[0]-A/2.0;
- int ii = 0;
- int jj = 0;
- int flag = 0;
- if(A == 0)
- return ;
- for(int i = 0;i < len;++i)
- {
- for(int j = 0;j < len;++j)
- {
- int x = a[i] - b[j];
- if( x == 0)
- continue;
- if(isXinRangeA(x,A))
- {
- double tmp = x - A/2.0;
- if(tmp < min)
- {
- min = tmp;
- flag = 1;
- ii = i;
- jj = j;
- cout<<"***"<<endl;
- }
- }
- }
- }
- if(flag == 1)
- {
- swap(&a[ii],&b[jj]);
- exchangeAB(a,b,len);
- }
- }
- 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);
- exchangeAB(a,b,len);
- //打印數組A
- for(int i = 0;i < len;++i)
- {
- cout<<a[i]<<" ";
- }
- cout<<endl;
- //打印數組B
- for(int j = 0;j < len;++j)
- {
- cout<<b[j]<<" ";
- }
- cout<<endl;
- //打印數組A的和與數組B的和的差值
- cout<<abs(sum(a,len)-sum(b,len))<<endl;
- system("pause");
- return 0;
- }
以上這種解法是有缺陷的,得到的結果並不一定是全局最優值,因爲:
一,題目要求的是差值最小的方案,所以交換一對數據是不能實現的。
二,最後交換一對數據無法使差值減小,但是存在同時交換兩對(還有更多對)數據可以減小差值的可能。
比如:如果兩個序列分別是[-3,9,10,65]和[5,6,13,55],按以上的算法這就是最優解了。可是顯然[-3,5,13,65]和[6,9,10,55]更好。
所以下面給出一種能找出全局最優值的解法,使用動態規劃的算法實現:
- /*
- *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);
- system("pause");
- return 0;
- }
注意:如果數組中有負數的話,上面的揹包策略就不能使用了(因爲第三重循環中的s是作爲數組的下標的,不能出現負數的),需要將數組中的所有數組都加上最小的那個負數的絕對值,將數組中的元素全部都增加一定的範圍,全部轉化爲正數,然後再使用上面的揹包策略就可以解決了。