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.pygithub.com输入可以是一个正整数的list,也可以是一个正整数的np.array。test例子的结果如下:
![8a1c258938aedc3fe3d4d430e582abcf.png](https://img-blog.csdnimg.cn/img_convert/8a1c258938aedc3fe3d4d430e582abcf.png)
![fa9f9e328d04e3b0f159a49deaa10757.png](https://img-blog.csdnimg.cn/img_convert/fa9f9e328d04e3b0f159a49deaa10757.png)
其中,class1和class2的非0表示该组选择了该位上的数字,0表示没有选择到该位上的数字。当然,结果还没有做到完全去重,如第一个例子,两个结果实际上是同一种分组,未来会继续改进这一点。
4、参考资料
主要是学习了动态规划的思想,如下链接讲得还是比较简洁易懂的:
最通俗易懂的背包问题