计算机算法应用(1)——选择金额最接近票据(0-1背包问题)

应用场景

最近遇到了一个场景,具体为在计算出金额水位差额以后通过质押若干张电票(票据可以在多张票据集合中选择,且票据只能全部质押不能部分质押)的方式弥补水位,为了把控风险,要尽量使得水位差额被完全填补,但是在水位差额被填补以后又需要使得质押票据的金额尽量接近于水位差额,在满足这样的条件下,要使得质押的票据张数尽量少,这样的目的是为了在遇到这样的情况之时提升客户体验。

问题分析

看到这个问题,最直观的感觉是这是一个优化问题,通过查找资料和零星的学习算法的回忆(是啊,已经忘记的差不多了-。-),意识到这是一个可以利用动态规划算法解决的0-1背包问题,但是具体的解决方法还是需要进一步思考。
狗头

0-1背包问题

首先,我们回顾一下什么是0-1背包问题,给定 n n n 种物品和一背包,物品 i i i的重量是 w i {w_i} wi,其价值为 v i {v_i} vi,背包的容量为 C C C。目标是选择装入背包的物品,使得装入背包中物品的总价值最大,一个物品只能选择装或不装,而不能只装一部分,所以是0-1背包问题。0-1背包是一个特殊的整数规划问题:
max ⁡ ∑ i = 1 n v i x i s . t . { ∑ i = 1 n w i x i ≤ C x i ∈ { 0 , 1 } , 1 ≤ i ≤ n \begin{array}{c} \max \sum\limits_{i = 1}^n {{v_i}{x_i}} \\ s.t.\left\{ \begin{array}{l} \sum\limits_{i = 1}^n {{w_i}{x_i}} \le C\\ {x_i} \in \left\{ {0,1} \right\},1 \le i \le n \end{array} \right. \end{array} maxi=1nvixis.t.i=1nwixiCxi{0,1},1in

数学模型

我们将以上场景建模,假设有 n n n张票据,第 i i i张票据的金额为 a i {a_i} ai,那么上述问题也可以建模为一个整数规划问题,假设计算出来的水位金额的差额为 M M M,那么上述问题就是一个特殊的整数规划问题:
min ⁡ ( ∑ i = 1 n a i x i − M ) s . t . { ∑ i = 1 n a i x i − M ≥ 0 x i ∈ { 0 , 1 } , 1 ≤ i ≤ n \begin{array}{c} \min \left( {\sum\limits_{i = 1}^n {{a_i}{x_i}} - M} \right)\\ s.t.\left\{ \begin{array}{l} \sum\limits_{i = 1}^n {{a_i}{x_i}} - M \ge 0\\ {x_i} \in \left\{ {0,1} \right\},1 \le i \le n \end{array} \right. \end{array} min(i=1naixiM)s.t.i=1naixiM0xi{0,1},1in
看这个公式似乎和0-1背包问题还有一些出入,下面我们令所有待选择票据金额之和为 S ( a ) S(a) S(a),令 f ( x ) = ( ∑ i = 1 n a i x i − M ) f(x)=\left( {\sum\limits_{i = 1}^n {{a_i}{x_i}} - M} \right) f(x)=(i=1naixiM)
S ( a ) − f ( x ) = ∑ i = 1 n a i − ( ∑ i = 1 n a i x i − M ) = ∑ i = 1 n a i ( 1 − x i ) + M \begin{array}{c} S(a) - f(x) = \sum\limits_{i = 1}^n {{a_i}} - \left( {\sum\limits_{i = 1}^n {{a_i}{x_i}} - M} \right)\\ = \sum\limits_{i = 1}^n {{a_i}\left( {1 - {x_i}} \right)} + M \end{array} S(a)f(x)=i=1nai(i=1naixiM)=i=1nai(1xi)+M
y i = ( 1 − x i ) ∈ ( 0 , 1 ) {y_i} = \left( {1 - {x_i}} \right) \in (0,1) yi=(1xi)(0,1),那么上述公式变为:
S ( a ) − f ( x ) = ∑ i = 1 n a i y i + M S(a) - f(x) = \sum\limits_{i = 1}^n {{a_i}{y_i}} + M S(a)f(x)=i=1naiyi+M

由于当票据列表确定的时候,票据金额之和为一个常量,那么上述的整数规划问题变为:
max ⁡ ( ∑ i = 1 n a i y i + M ) s . t . { f ( x ) ≥ 0 y i ∈ { 0 , 1 } , 1 ≤ i ≤ n \begin{array}{c} \max \left( {\sum\limits_{i = 1}^n {{a_i}{y_i}} + M} \right)\\ s.t.\left\{ \begin{array}{l} f(x) \ge 0\\ {y_i} \in \left\{ {0,1} \right\},1 \le i \le n \end{array} \right. \end{array} max(i=1naiyi+M)s.t.{f(x)0yi{0,1},1in
并且由于:
f ( x ) = S ( a ) − ∑ i = 1 n a i x i − M f(x) = S(a) - \sum\limits_{i = 1}^n {{a_i}{x_i}} - M f(x)=S(a)i=1naixiM
所以上述问题变为:
max ⁡ ∑ i = 1 n a i y i s . t . { ∑ i = 1 n a i y i ≤ S ( a ) − M y i ∈ { 0 , 1 } , 1 ≤ i ≤ n \begin{array}{c} \max \sum\limits_{i = 1}^n {{a_i}{y_i}} \\ s.t.\left\{ \begin{array}{l} \sum\limits_{i = 1}^n {{a_i}{y_i}} \le S(a) - M\\ {y_i} \in \left\{ {0,1} \right\},1 \le i \le n \end{array} \right. \end{array} maxi=1naiyis.t.i=1naiyiS(a)Myi{0,1},1in
所以这是一个特殊的0-1背包问题,在这个问题中每个物品的重量和价值都相等为 a i {a_i} ai(票据金额),背包容量为 S ( a ) − M {S(a) - M} S(a)M,当票据列表和水位金额差额确定时,此项为一常量。

问题求解

使用动态规划算法可以求解上述问题,问题的最优解 y i {y_i} yi为所有不能挑选质押的票据,那么最优解的补集则是我们需要挑选质押的票据。注意到上述公式仅仅可以使得挑选的票据金额之和大于水位差额并且最接近水位差额,可是并没有控制在满足上述条件后挑选的票据张数最少。变成了这样的一个优化问题:
max ⁡ ∑ i = 1 n x i s . t . { x i ∈ { 0 , 1 } , 1 ≤ i ≤ n \begin{array}{c} \max \sum\limits_{i = 1}^n {{x_i}} \\ s.t.\left\{ {{x_i} \in \left\{ {0,1} \right\},1 \le i \le n} \right. \end{array} maxi=1nxis.t.{xi{0,1},1in
这个问题我们可以这样思考,当我把物品挑选进入背包的时候,如果希望放入背包的物品数量尽量多,那么没有放入背包的问题是否就会尽量少了?那么我们的目标变成了希望放入背包的物品数量尽量多,那么在求解过程中,如果我们优先把物品价值低的物品放入背包,那么这样得到的最优值对应最优解就可以满足上述优化问题,因为如果低价值的物品优先构成了最优解,后续高价值的物品即使可以构成最优解也可以选择不放入背包了,这和我们算法实现的时候的“偏好”有关。所以我在求解上述问题的时候,对票据的票面金额由大到小进行了排序,并且在求解出了最优值之后,又使用回溯法计算出了最优解。

代码实现

请参考动态规划算法(1)——支持连续值的0-1背包问题(Knapback)

参考文献

USTC计算机算法课程

你可能感兴趣的:(算法,算法,动态规划)