将数组拆分为平衡和的P子阵列的算法

时间:2013-01-02 10:50:14

标签: arrays algorithm parallel-processing load-balancing

我有一个很长的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)更糟糕。

提前谢谢。

10 个答案:

答案 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)

全局变量Poptimumoptimum_diff表示包含 A P 值的问题实例,和 sigma ,以及最佳解决方案及其措施:

class MinimalSumOfSubArraySumsProblem:
    def __init__(self, a, p):
        self.a = a
        self.p = p
        self.sigma = sum(a)/p

接下来,我们指定非常直接的rejectaccept函数:

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

其余两个函数firstnext稍微复杂一些:

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值则返回nextNone只是将最右边的整数递增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 如果 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-leftSumrightP = 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 16 3 3 3 4 3 4 4 4 3 3 1以及P = 4-1处理sum = 60-12sum = 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)

我最近需要这个并按照以下步骤进行操作;

  1. 创建一个给定子数组计数的长度的初始子数组数组。子数组也应该有一个sum属性。即[[sum:0],[sum:0]...[sum:0]]
  2. 对主数组进行降序排序。
  3. 搜索具有最小总和的子数组,并从主数组中插入一个项目,并通过插入项目的值递增子数组sum属性。
  4. 重复第3项,直至到达主阵列的末尾。
  5. 返回initial数组。
  6. 这是JS中的代码。

    &#13;
    &#13;
    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;
    &#13;
    &#13;

答案 9 :(得分:-1)

您可以使用Max Flow算法。