如何找到向量的两个子集和之间的最大和最小严格正差?

时间:2019-07-15 20:13:44

标签: r optimization

我搜索了此内容,但找不到任何看起来匹配的内容;如果有人能帮忙或指出正确的方向,那就太好了。

假设您有一个数值向量h = c(-1,-2,3,5,6,9)(我正在使用R)。

您对该向量的3个元素进行2个不同的选择。取每个选择的总和,然后取两个总和之间的差。
例如。一个选择可以是c(-1,3,5)->总和为7;另一个c(-2,3,9)->总和为10;差异是10-7 = 3。

您想知道:

  • 最大(正)差异
  • 可能的最小正值差异

对于像这样的小向量,您可以蛮力破解,得到两个和之间的所有可能差值,然后查找所需的值。

h = c(-1,-2,3,5,6,9)
N <- length(h)
n <- 3

vs <- expand.grid(rep(list(0:1),2*N))

vs["np1"] <- rowSums(vs[,1:N])
vs["np2"] <- rowSums(vs[,(N+1):(2*N)])

vs <- vs[(vs$np1 == n) & (vs$np2 == n),]

vs["hSum1"] <- apply(vs,1,function(x) sum(h*x[1:N]))
vs["hSum2"] <- apply(vs,1,function(x) sum(h*x[(N+1):(2*N)]))

vs["hdiff"] <- vs$hSum2-vs$hSum1

max(vs$hdiff)
#[1] 20
min(vs[vs$hdiff > 0,"hdiff"])
#[1] 1

显然,对于较大的向量来说这是不可能的。

我发现可以通过简单地对h进行排序(升序)并取其最后3个元素和前3个元素之间的差异来找到最大的差异:

sum(sort(h)[(N-n+1):(N)]-sort(h)[1:n])
#[1] 20

但是,我想不出最小的阳性差异的解决方案。
我尝试了线性编程,但是我找不到找到将严格的不等式强加于差值上的技巧,这是避免将0作为解决方案所必需的。
从这个意义上讲,this post在概念上似乎很接近,但是我无法理解它,因为我看不到方法的来源,并且在不理解它的情况下应用它时,没用。

有什么想法吗?

谢谢!


编辑-可能的LP解决方案,以找到最小的正差

通过迭代。
Dmin为所寻求的差异。首先,找到Dmin的上限,作为h唯一元素之间的最小成对差异:

h <- c(2.1,1,-0.5,0,1.7,2.3)
N <- length(h)
n <- 3

min.hdiff <- min(diff(sort(unique(h))))

UB <- min.hdiff
UB
#[1] 0.2

下限可以设置为0:

LB <- 0

在这种情况下,很明显UB不是Dmin;可以用蛮力检查Dmin是否为0.1。

为了通过LP重现该问题,我首先定义一个查找 max (不是 min positive 差异的函数。在LB和UB之间

obj <- c(h,-h)

constr.n1 <- c(rep(1,N),rep(0,N))
constr.n2 <- c(rep(0,N),rep(1,N))
dir.n <- "=="
rhs.n <- n

constr.D <- c(h,-h)
dir.D1 <- ">="
rhs.D1 <- LB
dir.D2 <- "<="
rhs.D2 <- UB

mat <- rbind(constr.n1,constr.n2,constr.D,constr.D)
dir <- c(dir.n,dir.n,dir.D1,dir.D2)
rhs <- c(rhs.n,rhs.n,rhs.D1,rhs.D2)
N.rhs <- length(rhs)

require(Rsymphony)

DS.feas <- function(LB,UB) {
  rhs[c(N.rhs-1,N.rhs)] <- c(LB,UB)
  LP.sol <- Rsymphony_solve_LP(obj,mat,dir,rhs,types="B",max=T)
  if ((LP.sol$status == 0) & (LP.sol$objval > 0)) {return(list(1,LP.sol$solution,LP.sol$objval))} else {return(list(0,NULL,LP.sol$objval))}
}

然后我检查初始[LB,UB]是否可行:

LB.feas <- DS.feas(LB,UB)

LB.feas
#[[1]]
#[1] 1
#
#[[2]]
# [1] 0 1 0 1 0 1 1 1 0 1 0 0
#
#[[3]]
#[1] 0.2

由于当前的[LB,UB]是可行的,因此在下一次迭代中,我将LBUBMB)之间的中点作为新的假定UB进行测试。 :

MB = (LB+UB)/2
MB
#[1] 0.1
MB.feas = DS.feas(LB,MB)
MB.feas
#[[1]]
#[1] 1
#
#[[2]]
# [1] 0 1 0 1 1 0 1 1 1 0 0 0
#
#[[3]]
#[1] 0.1

可行。因此,我将UB设置为MB并测试了新的较低(但仍为正)的中点:

UB = MB
MB = (LB+UB)/2
MB
[1] 0.05
MB.feas = DS.feas(LB,MB)
#MB.feas
#[[1]]
#[1] 0
#
#[[2]]
#NULL
#
#[[3]]
#[1] 0

不可行。因此Dmin必须在当前MBUB之间。我将LB设置为MB并运行下一个迭代。

依次类推,直到实现收敛为止。

我在各种载体上进行了测试。
似乎可行;仅N = 20就已经变得非常慢。

如果有人可以建议如何做得更好...

2 个答案:

答案 0 :(得分:1)

我不知道r,但是我敢肯定您可以提出与此Python解决方案相同的问题:

from pulp import *

vals = [-1,-2,3,5,6,9]
r = range(0, len(vals))

#group1[i] = 1 if we use vals[i] in first group
group1 = LpVariable.dicts('group1', r, cat='Binary')
group2 = LpVariable.dicts('group2', r, cat='Binary')

#repeats[i] = 1 if we allow to use vals[i] in both groups
repeats =  LpVariable.dicts('repeats', r, cat='Binary')

prob = LpProblem('test', LpMinimize)

sum1 = lpSum([group1[i] * vals[i] for i in r])
sum2 = lpSum([group2[i] * vals[i] for i in r])

#objective
prob += sum1 - sum2

#make group1 be highest sum to prevent negative solutions
prob += sum1 >= sum2

#make each group have 3 items
prob += lpSum([group1[i] for i in r]) == 3
prob += lpSum([group2[i] for i in r]) == 3


#dont allow choosing same number for both groups if we are not allowing repeat
for i in r:
    prob += group1[i] + group2[i] <= repeats[i] + 1

#only allow up to 2 repeats (thus preventing same solution repeating 3)
prob += lpSum([repeats[i] for i in r]) <= 2

prob.solve()

print([vals[x] for x in r if group1[x].value()], [vals[x] for x in r if group2[x].value()])

编辑:只需重新阅读您的问题,似乎您不想允许解决方案0。在这种情况下,它甚至更容易,可以删除与重复项相关的所有内容,只需将第一个限制更改为:< / p>

#avoid solution 0
prob += sum1 - sum2 >= 1 

答案 1 :(得分:0)

最小的差异实际上是分区问题https://en.wikipedia.org/wiki/Partition_problem

最大的差异是一个子集中的所有负数,另一个子集中的正数