问题阐述:
1.有两个序列a,b,大小都为n,序列元素的值时任意型整数,无序:要求交换a,b中的元素,是序列a元素的和与序列b元素的和之间的差最小。
问题分析:
1.经过很长时间的思考,并通过浏览各路大神的分析,本小白最终锁定此问题是背包问题。
2.将两个列表合成一个列表命名为c,所有元素求和得到sum,最终得到的两个列表为deal_a 和deal_b
搞成背包问题,看看有几个约束
1.差值最小,我们可以预测到,最终得到的两个数组一定在sum/2 的周围,
可以假设deal_a<=sum/2 和deal_b>=sum/2。
我们只需要找到deal_a, 那么从c中将deal_a的元素剔出来剩下的就是deal_b
2.由1的分析得出,只需要找出deal_a即可,有一个约束条件sum/2,即deal_a中的元素之和<=sum/2
3.从题目可以看出,还有一个约束条件,即deal_a的元素个数应该等于n
4.归纳成背包问题,有一个背包,可以装n块宝石,最大装sum/2千克,可以获得的最大价值是多少?(最大价值也是deal_a元素的和)
5.deal_a中所有元素的和既是你的目的,同时也限制着你的选择,所以可以从两方面考虑,并不冲突。
最终:
我们把该问题搞成了一个拥有二重约束的背包问题:具体二重约束的背包问题算法请阅读dd大神的《背包九讲》
由于最后要把最终数组输出,需将其回溯出来,所以不考虑降维(当然也是由于菜鸡很菜,能力有限)
代码注意事项:
1.容量sum/2,sum是对c中的元素求和,可以推测,有负数的时候,sum/2小于c(共2n个)中任取n个较大值,容量可能会溢出,那就不行了,但是又不能随便加大容量,那样就起不到分割deal_a和deal_b的作用了,所以为了消除负数的影响,我们只需要将列表每个元素都增大即可。
2.deal_a中的值应该有n个,但是背包是找到最优的存放方法,某种情况下,例如n-1个数就可以达到sum/2,这显然与我们的要求不符,我们是要求deal_a中有n个值的,所以这里不仅仅在消除负数的影响的时候平移,一般时候也平移,平移多少呢,c中每个元素加上c中最大值即可,如果绝对值最大的是负数也没关系,我们平移的时候同时考虑这两者即可。(如此,一些基本的,偏僻的,只要是不是特别特别特殊的案例都可以测试通过)
3,2中的方法有些投机嫌疑,但菜鸡本菜,背包用得不好,希望大佬能多多指点。
#二重约束条件的背包问题
#测试数组,从各个博客中搞来的测试数组
#[5,5,9,10],[4,7,7,13]
#[-3,9,10,65],[5,6,13,55]
#[0,0,-3,5],[0,0,1,1]
#[100,99,98,1,2, 3],[1, 2, 3, 4,5,40]
#[1,5,5,7,14],[4,5,7,7,9]
###################################################################
import numpy as np
list_a = [5,5,9,10]
list_b = [4,7,7,13]
list_c=(list_a+list_b)#每个物体的容量和价值是一样的
N=len(list_c)
#先检查有没有负数,若有负数整体平移
negative=0 if min(list_c)>=0 else min(list_c)#先将负数求和,其实找到最小的负数即可,但本着避免“代码注意事项”中2的问题多加点更好
negative=-negative+100 if max(list_c)<100 else max(list_c)#设置平移值,负数之和取反在加上c中的最大值
list_c=[i+negative for i in list_c]#整体平移
M=sum(list_c)//2+1
Q=N//2+1
N=N+1
bag=np.zeros((Q,M,N),dtype=int )#N是第几个物品,M是当前容量,Q是物体个数N/2
#如果看不懂背包部分,可以自己写写此部分,毕竟每个人代码细节都不同
for i in range(1,N):
for j in range(1,M):
for k in range (1,Q):
if(j>=list_c[i-1]):
bag[k,j,i]=max(bag[k][j][i-1],bag[k-1][j-list_c[i-1]][i-1]+list_c[i-1])
else:
bag[k][j][i]=bag[k][j][i-1]
#结束后最值存储在bag[Q-1][M-1][N-1]中,后面是将背包里的物品回溯出来
deal,i,j,k=0,N-1,M-1,Q-1
while True:
if(bag[k,j,i]!=bag[k,j,i-1]):
list_a[deal]=list_c[i-1]
deal+=1;
j-=list_c[i-1];i-=1;k-=1
else:
i-=1
if j==0 or i==0 or k==0:
break
#懒得设置新的变量,有list_a,list_b存结果(反正也他俩也用不到了QAQ)
N=N-1
#将list_b搞出来,从list_c中剔除list_a,不能用差集,因为会删除重复元素,不知道有啥好方法了,用个循环
for i in range(N//2):
for j in range(N):
if(list_a[i]==list_c[j]):
list_c[j]=0
break
i=0
for j in range(N):
if(list_c[j]!=0):
list_b[i]=list_c[j]
i+=1
#移回来
list_a=[i-negative for i in list_a]
list_b=[i-negative for i in list_b]
print(list_a,list_b)