子集合加总问题(Subset sum problem)

两年前在做一个ERP项目时,有个客户提出,根据订单给出的每个货品的数量(库里存的有每个货品最小包装的重量,体积和长宽高),输入包装箱的承重和体积和长宽高,然后我需要你们提供一个包装方案,要求结果是用最少的纸箱来包装这些货物并给出包装步骤,最好能用3D图表示。当时我就蒙了,这算法要是靠外包公司写,不知道写不写的出来,于是给客户扯了个类似的背包问题,然后告诉它这是NP完全问题,非常复杂,货物很多的时候你的服务器不一定能承受计算的压力。然后客户也蒙了,再然后这个需求就取消了。其实,这个到底是不是背包问题,是不是属于NP完全问题,我也不敢讲,没仔细的推导过。当时只是觉得场景类似,所以就举了个这样的例子给他。现在得回顾一下这个问题,看看靠自己得知识能不能把这个问题推导至背包问题。但是要说背包问题,还是先说子集合加总问题吧。


给一个整数集合,问是否存在某个非空子集,使得子集内中的数字和为0?例:给定集合{−7, −3, −2, 5, 8},答案是存在,因为子集{−3, −2, 5}的数字和是0。这个问题被证明是属于NP完全问题。如果把这个问题当作一个特例,那范围更广的问题就是 “给一个整数集合A,问是否存在某个非空子集s,使得子集内中的数字和为B” ,当我们把B设为0时,就是我们上面的那个问题了。而第二个问题,属于背包问题的一个特例。

子集特定合问题的最简单算法(把所有的子集全部列出来,然后对每一个子集做运算)时间复杂度是(2N),因为有2N个非空子集,然后对于每一个子集,我们最多会做N次加法运算。

有人搞出另外一个算法,这个算法的时间复杂度是(2N/2 )  (Horowitz and Sahni ,1972

对他们在1972年提出的这个算法的简单解释如下,

将任意给定元素的集合N分成平分成两个集合,每个集合有N/2个元素,然后计算出每个集合的子集的和加总结果,得出两个列表,对这两个列表用比较排序法,然后我们再把两个列表的元素相加,看是否有其中一对能得到要求的结果。这个加法运算是从第一个列表的头和第二个列表的尾开始。


通过动态规划,还能搞出一个伪多项式时间复杂度的算法,对于判定是否有子集和为0的算法的大概的解释是

序列为 x1, ..., xn

A为集合中正数的总和

B为集合中负数的总和

一个方法Q(i, s) 输出是否有子集(x1, ..., xi )的和等于s。

当s=0时,这个方法给出的结果时子集i的和是否等于0

那很明显,当s < A 或者 s > B 时 ,Q(i, s)输出否,也就是两个集合不符合这个条件的都可以不被计算和存储。

构造一个数组来保存符合1 ≤ i ≤ N 和 A ≤ s ≤ B Q(i,s )结果。

递归的把结果放到这个数组中,当i=1 , A ≤ s ≤ 

Q(1,s) := (x1 == s)

当i >1时

(,s) := i  − 1, sor (xi == sor i  − 1, s − xi)   for A ≤ s ≤ B

当我们每次往数组中插入值的时候,我们都已知Q的值,所以最终算法的时间复杂度是 − ) ). 也就是当计算所有的值的时间复杂度是(k),整体算法的复杂度是k+2) ,但是因为A,B值的大小问题,这个算法不符合时间复杂度理论的多项式算法。


Wiki上面还有个线性时间的类似问题的算法,有些改动,但是那个需要时间参透,自己都没太看明白的东西还是不要摆出来了。


小总结,研究这些问题,或者,学习这些问题的分析方法和分析思路是非常有好处的,做为一个什么都干的程序员,和客户谈需求的时候必须要掌握一些问题的类型,或者能快速的将客户的问题归纳到某个已被证明复杂度的问题上(往往客户提出的问题都是这类问题的某个特例),如此才可有理由的答应或者拒绝客户提出的需求。而且,在处理问题的时候,也能快速的想出比较合适的算法来实现某些功能。总之,要想提高代码的质量和水平,基础还是要扎实的学的。

你可能感兴趣的:(子集合加总问题(Subset sum problem))