以编程方式将数字序列拆分为4个相对相等的块

时间:2015-07-03 11:12:06

标签: algorithm split language-agnostic

我如何将一系列数字分成4个相等(尽可能相等)的块?

如果我有这样的整数序列:

  

16,4,17,10,15,4,4,6,7,14,9,17,27,6,1,9,0,12,20,8,   0,3,4,0,3,4

我想将该序列分成4个块,其中每个块的总和尽可能接近序列总和的四分之一。序列的总值是220,所以我希望块大致等于55.序列是这样的,它的顺序不应该改变。

背景:这些数字代表电话簿中以某个字母开头的条目数。我正试图以最好的方式拆分电话簿。

4 个答案:

答案 0 :(得分:1)

如果你想要四个块,保留顺序,那么你有三个块边界要放置。我首先将边界均匀放置,然后迭代移动每个边界+/- 1以寻找改进。回溯或遗传算法都应该有效。有了一个尽可能短的列表,没有大量不同的可能性尝试,所以它应该合理地运行。

ETA:可能的伪代码:

place three boundaries in initial positions
calculate sizes of each chunk between boundaries
boundariesMoved <- true
WHILE (boundariesMoved) DO
  boundariesMoved <- false
  FOR EACH boundary
    check sizes of two adjacent chunks
    test moving boundary 1 step towards larger adjacent chunk
    IF move increased absolute difference between chunks THEN
      leave boundary in original position
    ELSE
      move boundary
      update sizes of affected chunks
      boundariesMoved <- true
    ENDIF
  ENDFOR
ENDWHILE

答案 1 :(得分:0)

如果你想要的东西不是最优的,但又容易,快速和足够好(鉴于分布不是疯狂的倾斜),我建议你做这样的事情:

  1. 计算总和
  2. 除以N(您想要多少个分片)
  3. 贪婪地为每个分区获取最大值,直到分区总和<= Sum / N.将所有剩余值放在最后一个分区中。
  4. 您将拥有&lt; = K的N-1个分区,并且将具有&gt; = K(K = Sum / N)的分区。它比实际分区问题容易,并且不正确,但考虑到你的上下文,这似乎是可以接受的,特别是因为通常后面的值(与W X Y Z等字母相匹配)的值会更小。

答案 2 :(得分:0)

这被称为相同大小的K-Means问题。通常它指的是2-d变体,你可以更简单 - 只有一个维度的情况。

  

该算法的基本思想如下:

     

初始化:

     
      
  • 计算所需的群集大小,n / k。
  •   
  • 初始化意味着,最好用k-means ++
  •   
  • 通过到最近的星团的距离减去距离最远群集的距离(=最好的最佳效果)   分配)
  •   
  • 将点分配给其首选群集,直到此群集已满,然后使用剩余对象,而不占用整个群集   帐户不再这个初始化不是最佳的 - 随意   为本教程做出了改进! - 特别是对于   最后一个集群但它将作为初始化方法。
  •   
     

迭代:

     
      
  1. 计算当前群集意味着
  2.   
  3. 对于每个对象,计算到群集的距离意味着
  4.   
  5. 根据当前分配的增量和最佳备用分配对元素进行排序。
  6.   
  7. 按优先级对每个元素:      
        
    1. 对于每个其他群集,按元素增益,除非已移动:
    2.   
    3. 如果有一个元素想要离开另一个集群并且这个交换产生和改进,那么交换这两个元素
    4.   
    5. 如果可以在不违反尺寸限制的情况下移动元素,请将其移动
    6.   
    7. 如果元素未更改,请添加到传出转移列表。
    8.   
  8.   
  9. 如果没有进行更多转移(或达到最大迭代阈值),则终止
  10.   

来源:http://elki.dbs.ifi.lmu.de/wiki/Tutorial/SameSizeKMeans

答案 3 :(得分:0)

首先,您应该确定要最小化的确切值。

我们将S表示数字的总和,s1s2s3s4表示某些解决方案中四个部分的总和

我们可以定义一个相当模糊的术语“尽可能相等”的许多确切表示。也就是说,max(s1,s2,s3,s4)-min(s1,s2,s3,s4)应该尽可能小吗?或max(|s1-S/4|, |s2-S/4|, |s3-S/4|, |s4-S/4|)应尽可能减少?或者说,|s1-S/4|+|s2-S/4|+|s3-S/4|+|s4-S/4|?等

我可以想到第二个指标的简单解决方案:max(|s1-S/4|, |s2-S/4|, |s3-S/4|, |s4-S/4|)最小化。

首先,让我们解决一个不同的问题。给定你的序列和一些值X,我们可以用max(|s1-S/4|, |s2-S/4|, |s3-S/4|, |s4-S/4|)<=X的方式对它进行分区吗?如果我们可以针对任意X解决此问题,则可以通过X上的二进制搜索来解决初始问题。

那么,我们如何检查是否存在max(|s1-S/4|, |s2-S/4|, |s3-S/4|, |s4-S/4|)<=X的分区?此要求等同于要求S/4-X<=s[i]<=S/4+X,因此对于每个块,我们知道允许的最小和最大总和。

现在从头开始计算当前总和并标记第一个块可以结束的位置 - 这将是从开始的总和从S/4-XS/4+X的位置。

现在找到第二个块可以结束的位置。这有点棘手。最简单的方法是从第一个块的每个找到的结束位置开始,并找到第二个块的相应可能的结束位置。但是存在更快的方法。首先,从第一个块的第一个可能的结束位置开始,并计算第二个块的相应结束位置。然后,移动到第一个块的第二个可能的结束位置。请注意,这只会为已找到位置右侧的第二个块添加一些新的结束位置,因此无需重复全部;如果保持“当前”第二个块所涵盖的累积跨度总和,则可以在O(N)中找到第二个块的所有可能位置。因此,您标记第二个块的所有可能的结束位置。

同样找到第三个块和第四个块的可能终点位置。如果数组的末尾在第四个块的可能的结束位置之中,则可以进行这样的划分,否则为否。分裂本身可以用简单的方式恢复,我不会描述它。

像这样编码:

func check(a,S,X) // a is given array
    // canEnd[i,j] is whether the i-th chunk can end just before position j :
    //  canEnd[i,j]==0 --- can not end
    //  canEnd[i,j]==1 --- can end
    //  cadEnd[i,j]==2 --- can end and this is the final possible position
    fill canEnd with zeroes
    canEnd[0,0] = 2
    l = 0  // left end of 'current' chunk
    r = 0  // right end of 'current' chunk (not inclusive)
    curs = 0 // sum of the 'current' chunk
    for i = 1..4
        while true
            last = -1
            while curs <= S/4+X
                if curS > S/4-X
                     canEnd[i,r] = 1
                     last = r
                s +=a[r] 
                r++
            // now processed all chunks that start at l
            if canEnd[i-1,l] == 2
                canEnd[i,last] = 2
                break
            do
                s -= a[l]
                l++
            until canEnd[i-1,l]>0

// main code
left = -1
right = S
while right - left > 1
    middle = (right + left) /2
    if can(middle)
        right = middle
    else left = middle
// the answer is right

(请注意,我没有测试代码,很可能它包含错误,仅供参考。)

对于max(s1,s2,s3,s4)-min(s1,s2,s3,s4)指标,可以应用类似的方法,但您必须先从0到S/4进行迭代才能尝试min(s1,s2,s3,s4)的每个可能值。对于min(s1,s2,s3,s4)的每个可能值,对最大可能值进行二进制搜索,并再次为每个s[i]定义范围。