matlab 转换为正整数_python(二):正整数分两堆,各堆之和的差最小问题的求解

1、问题描述:

输入一组任意个数的正整数,将正整数分为两堆,使得各堆之和的差最小,如:

输入:[1, 2, 6, 9]

输出:

class1: [1, 2, 6]; class2: [9]; delta: 0

这个问题也可以多解,如:

输入:[1, 2, 6, 9, 5, 19, 14, 1, 8]

输出:

class1: [9, 14, 1, 8]; class2: [1, 2, 6, 5, 19]; delta: 1

class1: [2, 6, 5, 19]; class2: [1, 9, 14, 1, 8]; delta: 1

class1: [1, 2, 9, 5, 14, 1]; class2: [6, 19, 8]; delta: 1

class1: [1, 2, 6, 9,14]; class2: [5, 19, 1, 8]; delta: 1

2、求解思路:

(2.1)问题的转换

设输入序列n的各元素之和为sum,“两堆之和的差最小”,可以转换为“选择一些数字组成其中的一堆,其和在满足sum(1)<=floor(sum/2)条件下,使得sum(1)最大”,显然此时选择出来的这一堆数之和sum(1)和剩下的数之和sum(2)的差就是最小的。(换句话说,就是让两堆数之和尽量接近sum/2);

(2.2)动态规划求解

前述转换的描述便是一个“0-1 背包问题”,可以利用动态规划求解:

(2.2.1)定义一个价值矩阵value,第i行讨论的是第i个元素,第j列讨论的是背包容量为j,value[i, j]就是背包容量为j时,考虑完第i个元素是否添加后的价值(最大化的对象,这里即为和sum(1));

(2.2.2)value的递推公式:value[i, j] = max(not_take, take) = max(value[i-1, j], value[i-1, j-n(i)] + n[i]),其中n(i)为第i个元素的价值,这也是一个独立的子问题的解。

**理解**)假设前面(i-1)个元素的最大化问题都已经解决了,那么当容量为j时考虑第i个元素后的价值计算,只需要看两种情况:

(a)不选择i,此时的价值(sum(1))就和(i-1)个元素在j容量约束时的最大价值是一样的;

(b)选择i,那么此时的价值(sum(1))就是(i-1)个元素在(j-n[i])容量约束时的最大价值,加上i元素的价值n[i];

只需要在(a)和(b)里选择最大的价值,在value[i, j]这个节点上就做到了最大化,因而有(2.2.2)式;

(2.2.3)边界条件:

背包容量为0时,value[:, 0] = 0;

考虑第0个元素时,value[0, :] = 0;

递推时,如果j<n(i),让take取很小的值(如-1,一定是不取的情况,这意味着容量j还没有元素i的价值大)

(2.2.4) value[:,-1]这一列的最大值,即为sum(1)的最大值

(2.3)取数编码取数

(2.2)解决了选出来的堆的和的最大值是多少的问题,但是还需要知道具体取了哪些数,这里如下处理:

(2.3.1)假设用二进制为序列n进行编码,总共有len(n)位,假如选择了第i个元素n[i],则第i位为1,否则为0,比如:n包含了5个正整数,选择了其中的第2、5个数,则生成一个编码01001,利用这个编码即可从序列n中取数了。

(2.3.2)取数编码is_take也有递推公式(思路上和value的递推是一致的):

is_take[i, j] = is_take[i, j-1],如果不取第i个元素

is_take[i, j] = 2^(num-i) + is_take[i-1, j-n[i]],如果取第i个元素

(2.3.3)最后利用value[: -1]最大值时的取数编码is_take,就可以在输入序列n中取出所选择的值了。

3、python代码实现

div_2_class.py​github.com

输入可以是一个正整数的list,也可以是一个正整数的np.array。test例子的结果如下:

8a1c258938aedc3fe3d4d430e582abcf.png

fa9f9e328d04e3b0f159a49deaa10757.png

其中,class1和class2的非0表示该组选择了该位上的数字,0表示没有选择到该位上的数字。当然,结果还没有做到完全去重,如第一个例子,两个结果实际上是同一种分组,未来会继续改进这一点。

4、参考资料

主要是学习了动态规划的思想,如下链接讲得还是比较简洁易懂的:

最通俗易懂的背包问题

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值