我有一个很长的N长度,让我们说:
2 4 6 7 6 3 3 3 4 3 4 4 4 3 3 1
我需要将此数组拆分为P个子数组(在此示例中,P=4
是合理的),这样每个子数组中的元素总和尽可能接近sigma,为:
sigma=(sum of all elements in original array)/P
在此示例中,sigma=15
。
为了清楚起见,一个可能的结果是:
2 4 6 7 6 3 3 3 4 3 4 4 4 3 3 1
(sums: 12,19,14,15)
我写了一个非常天真的算法,基于我如何手工划分,但我不知道如何强加条件,其总和是(14,14,14,14,19)是比(15,14,16,14,16)更糟糕。
提前谢谢。
答案 0 :(得分:3)
首先,让我们通过为每个可能的解决方案指定输入,输出和度量来形式化您的优化问题(我希望这符合您的利益):
给定一个正整数的数组 A 和一个正整数 P ,将数组 A 分成 P 非重叠子阵列,使得每个子阵列的总和与子阵列的完美总和之间的差异(总和( A )/ P )是最小的。
输入:数组 A 正整数; P 是一个正整数 输出: P 的 SA 非负整数,表示 A 的每个子数组的长度这些子阵列长度等于 A 的长度 测量:abs(sum( sa ) - sum( A )/ P )对于每个 sa ∈{ sa | sa =( A i ,..., A 我 + SA j ) i =(Σ SA j ), j 从0到 P -1}。
输入和输出定义了一组有效的解决方案。 度量定义了一种比较多个有效解决方案的度量。由于我们正在寻找与完美解决方案(最小化问题)差异最小的解决方案,因此度量也应该是最小的。
有了这些信息,很容易实现measure
函数(在Python中):
def measure(a, sa):
sigma = sum(a)/len(sa)
diff = 0
i = 0
for j in xrange(0, len(sa)):
diff += abs(sum(a[i:i+sa[j]])-sigma)
i += sa[j]
return diff
print measure([2,4,6,7,6,3,3,3,4,3,4,4,4,3,3,1], [3,4,4,5]) # prints 8
现在找到最佳解决方案有点困难。
我们可以使用Backtracking algorithm查找有效的解决方案,并使用 measure 函数对其进行评分。我们基本上尝试了总计长度( A )的 P 非负整数的所有可能组合,以表示所有可能的有效解。虽然这可以确保不会错过有效的解决方案,但它基本上是一种蛮力方法,其好处是我们可以省略一些不能比我们最好的解决方案更好的分支。例如。在上面的例子中,如果我们已经有一个 measure ≤38的解决方案,我们就不需要用[9,...]( measure > 38)来测试解决方案。
遵循维基百科的伪代码模式,我们的bt
函数如下所示:
def bt(c):
global P, optimum, optimum_diff
if reject(P,c):
return
if accept(P,c):
print "%r with %d" % (c, measure(P,c))
if measure(P,c) < optimum_diff:
optimum = c
optimum_diff = measure(P,c)
return
s = first(P,c)
while s is not None:
bt(list(s))
s = next(P,s)
全局变量P
,optimum
和optimum_diff
表示包含 A , P 值的问题实例,和 sigma ,以及最佳解决方案及其措施:
class MinimalSumOfSubArraySumsProblem:
def __init__(self, a, p):
self.a = a
self.p = p
self.sigma = sum(a)/p
接下来,我们指定非常直接的reject
和accept
函数:
def reject(P,c):
return optimum_diff < measure(P,c)
def accept(P,c):
return None not in c
这只是拒绝任何测量已超过我们最佳解决方案的候选人。我们接受任何有效的解决方案。
measure
功能也略有改变,因为c
现在可以包含None
值:
def measure(P, c):
diff = 0
i = 0
for j in xrange(0, P.p):
if c[j] is None:
break;
diff += abs(sum(P.a[i:i+c[j]])-P.sigma)
i += c[j]
return diff
其余两个函数first
和next
稍微复杂一些:
def first(P,c):
t = 0
is_complete = True
for i in xrange(0, len(c)):
if c[i] is None:
if i+1 < len(c):
c[i] = 0
else:
c[i] = len(P.a) - t
is_complete = False
break;
else:
t += c[i]
if is_complete:
return None
return c
def next(P,s):
t = 0
for i in xrange(0, len(s)):
t += s[i]
if i+1 >= len(s) or s[i+1] is None:
if t+1 > len(P.a):
return None
else:
s[i] += 1
return s
基本上,如果first
不是列表中的最后一个值,或者余数代表有效的解决方案,None
要么用0
替换列表中的下一个None
值。这里的优化)如果它是列表中的最后一个值,或者如果列表中没有None
值则返回next
。 None
只是将最右边的整数递增1,如果增量会超过总限制,则返回bt
。
现在您只需要创建一个问题实例,初始化全局变量并使用root调用P = MinimalSumOfSubArraySumsProblem([2,4,6,7,6,3,3,3,4,3,4,4,4,3,3,1], 4)
optimum = None
optimum_diff = float("inf")
bt([None]*P.p)
:
{{1}}
答案 1 :(得分:2)
如果我没有在这里弄错,还有一种方法是动态编程。
您可以将 P [ pos , n ]定义为累积到位置 pos 的最小可能“惩罚” em>如果 n 子阵列已创建。显然有一些位置'这样
P [pos',n-1] +惩罚(pos',pos)= P [pos,n]
你可以最小化过度'= 1..pos。
天真的实现将在O(N ^ 2 * M)中运行,其中N - 原始数组的大小和M - 分区数。
答案 2 :(得分:1)
下面的工作代码(我使用的是php语言)。此代码决定零件数量本身;
$main = array(2,4,6,1,6,3,2,3,4,3,4,1,4,7,3,1,2,1,3,4,1,7,2,4,1,2,3,1,1,1,1,4,5,7,8,9,8,0);
$pa=0;
for($i=0;$i < count($main); $i++){
$p[]= $main[$i];
if(abs(15 - array_sum($p)) < abs(15 - (array_sum($p)+$main[$i+1])))
{
$pa=$pa+1;
$pi[] = $i+1;
$pc = count($pi);
$ba = $pi[$pc-2] ;
$part[$pa] = array_slice( $main, $ba, count($p));
unset($p);
}
}
print_r($part);
for($s=1;$s<count($part);$s++){
echo '<br>';
echo array_sum($part[$s]);
}
代码将输出部分总和,如下所示
13
14
16
14
15
15
17
答案 3 :(得分:1)
@Gumbo的答案是明确且可行的,但是当length(A)大于400且P大于8时,会浪费大量时间。这是因为如他所说,该算法有点残酷。
实际上,一种非常快速的解决方案是使用动态编程。
给定一个正整数数组A和一个正整数P,将数组A分成P个不重叠的子数组,以使每个子数组的和与子数组的完美和之间的差(sum(A)/ P )是最小的。
度量: ,其中是子数组的元素之和,是P个子数组之和的平均值。
这可以确保总数的平衡,因为它使用了Standard Deviation的定义。
假设数组A具有N个元素; Q(i,j)表示将A的最后i个元素拆分为j个子数组时的最小测量值。 D(i,j)表示(sum(B)-sum(A)/P)^2
,当数组B由A的第i〜j个元素组成时(0<=i<=j<N
)。
该问题的最小度量是计算Q(N,P)。我们发现:
Q(N,P)=MIN{Q(N-1,P-1)+D(0,0); Q(N-2,P-1)+D(0,1); ...; Q(N-1,P-1)+D(0,N-P)}
因此,可以通过动态编程来解决。
Q(i,1) = D(N-i,N-1)
Q(i,j) = MIN{ Q(i-1,j-1)+D(N-i,N-i);
Q(i-2,j-1)+D(N-i,N-i+1);
...;
Q(j-1,j-1)+D(N-i,N-j)}
所以算法步骤是:
1. Cal j=1:
Q(1,1), Q(2,1)... Q(3,1)
2. Cal j=2:
Q(2,2) = MIN{Q(1,1)+D(N-2,N-2)};
Q(3,2) = MIN{Q(2,1)+D(N-3,N-3); Q(1,1)+D(N-3,N-2)}
Q(4,2) = MIN{Q(3,1)+D(N-4,N-4); Q(2,1)+D(N-4,N-3); Q(1,1)+D(N-4,N-2)}
... Cal j=...
P. Cal j=P:
Q(P,P), Q(P+1,P)...Q(N,P)
The final minimum Measure value is stored as Q(N,P)!
To trace each subarray's length, you can store the
MIN choice when calculate Q(i,j)=MIN{Q+D...}
的D(i,j)空间;
计算Q(N,P)的时间
与纯暴力破解算法相比,耗时。
答案 4 :(得分:0)
我想知道以下是否有效:
从左侧开始,只要sum > sigma
分支为两个,一个包括将其推过的值,另一个不包括。使用rightSum = totalSum-leftSum
和rightP = P-1
递归处理数据。
所以,在开始时,sum = 60
2 4 6 7 6 3 3 3 4 3 4 4 4 3 3 1
然后对于2 4 6 7
,sum = 19&gt;西格玛,所以分成:
2 4 6 7 6 3 3 3 4 3 4 4 4 3 3 1
2 4 6 7 6 3 3 3 4 3 4 4 4 3 3 1
然后我们分别用7 6 3 3 3 4 3 4 4 4 3 3 1
和6 3 3 3 4 3 4 4 4 3 3 1
以及P = 4-1
处理sum = 60-12
和sum = 60-19
。
我认为这导致O(P * n)。
当1或2值是最大的时候可能是一个问题,但是,对于任何值&gt; = sigma,我们可能只是把它放在它自己的分区中(预处理数组以找到它们可能是最好的)想法(并适当减少总和))。
如果它有效,它应该有希望最小化平方误差的误差(或接近那个),这似乎是所希望的度量。
答案 5 :(得分:0)
我提出了一种基于回溯的算法。选择的主函数从原始数组中随机选择一个元素,并将其添加到分区的数组中。对于每个添加将检查以获得比原始更好的解决方案。这将通过使用计算偏差的函数来实现,区分每个向页面添加新元素。无论如何,我认为在循环中添加一个原始变量是不错的,你无法达到所需的解决方案将迫使程序结束。通过所需的解决方案,我的意思是根据条件强加的条件添加所有元素。
sum=CalculateSum(vector)
Read P
sigma=sum/P
initialize P vectors, with names vector_partition[i], i=1..P
list_vector initialize a list what pointed this P vectors
initialize a diferences_vector with dimension of P
//that can easy visualize like a vector of vectors
//construct a non-recursive backtracking algorithm
function Deviation(vector) //function for calculate deviation of elements from a vector
{
dev=0
for i=0 to Size(vector)-1 do
dev+=|vector[i+1]-vector[i]|
return dev
}
iteration=0
//fix some maximum number of iteration for while loop
Read max_iteration
//as the number of iterations will be higher the more it will get
//a more accurate solution
while(!IsEmpty(vector))
{
for i=1 to Size(list_vector) do
{
if(IsEmpty(vector)) break from while loop
initial_deviation=Deviation(list_vector[i])
el=SelectElement(vector) //you can implement that function using a randomized
//choice of element
difference_vector[i]=|sigma-CalculateSum(list_vector[i])|
PutOnBackVector(vector_list[i], el)
if(initial_deviation>Deviation(difference_vector))
ExtractFromBackVectorAndPutOnSecondVector(list_vector, vector)
}
iteration++
//prevent to enter in some infinite loop
if (iteration>max_iteration) break from while loop
} 如果某些代码以计算出的偏差量增加,则可以通过首先添加来更改此值。 aditional_amount = 0 迭代= 0 而 { ... 如果(initial_deviation&GT;偏差(difference_vector)+ additional_amount) ExtractFromBackVectorAndPutOnSecondVector(list_vector,vector) 如果(迭代&GT; MAX_ITERATION) { 迭代= 0 aditional_amout + = 1 / some_constant } 迭代++ //如果是第一个版本,请删除第二个 }
答案 6 :(得分:0)
您的问题与minimum makespan scheduling problem非常相似或相同,具体取决于您定义目标的方式。如果您想最小化最大|sum_i - sigma|
,那正是这个问题。
正如维基百科文章中所提到的,p > 2
的问题是NP完全的。格雷厄姆的list scheduling algorithm最适合p <= 3
,并提供2 - 1/p
的近似比率。您可以查看维基百科文章,了解其他算法及其近似值。
本页面给出的所有算法要么针对不同的目标求解,不正确/次优,要么可以用来解决NP中的任何问题:)
答案 7 :(得分:0)
这与一维 bin打包问题的情况非常相似,请参阅http://www.cs.sunysb.edu/~algorith/files/bin-packing.shtml。在相关的书籍The Algorithm Design Manual中,Skienna建议采用先适合减少方法。即找出你的bin大小(mean = sum / N),然后将剩余的最大对象分配到第一个有空间的bin中。你要么已经到了必须开始过度填充垃圾箱的地步,要么如果你很幸运,你会得到一个完美的契合。正如Skiena所说:“首先适应减少具有直观的吸引力,因为我们首先包装笨重的物体,并希望小物体可以填满裂缝。”
正如之前的一张海报所说,问题看起来像是NP完全的,所以你不会在合理的时间内完美地解决它,你需要寻找启发式方法。
答案 8 :(得分:0)
我最近需要这个并按照以下步骤进行操作;
[[sum:0],[sum:0]...[sum:0]]
initial
数组。这是JS中的代码。
function groupTasks(tasks,groupCount){
var sum = tasks.reduce((p,c) => p+c),
initial = [...Array(groupCount)].map(sa => (sa = [], sa.sum = 0, sa));
return tasks.sort((a,b) => b-a)
.reduce((groups,task) => { var group = groups.reduce((p,c) => p.sum < c.sum ? p : c);
group.push(task);
group.sum += task;
return groups;
},initial);
}
var tasks = [...Array(50)].map(_ => ~~(Math.random()*10)+1), // create an array of 100 random elements among 1 to 10
result = groupTasks(tasks,7); // distribute them into 10 sub arrays with closest sums
console.log("input array:", JSON.stringify(tasks));
console.log(result.map(r=> [JSON.stringify(r),"sum: " + r.sum]));
&#13;
答案 9 :(得分:-1)
您可以使用Max Flow算法。