【问题标题】:Programmatically Split A Sequence of Numbers Into 4 Relatively Equal Chunks以编程方式将一个数字序列拆分为 4 个相对相等的块
【发布时间】:2015-07-03 11:12:06
【问题描述】:

我将如何将一个数字序列分成 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 的块。序列就是这样,它的顺序不应该改变。

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

【问题讨论】:

  • 最简单(可能)的方法是递归回溯。尝试自己解决问题,如果遇到困难,请提出更具体的问题,而不是我如何不尝试解决问题。
  • 你想到了什么?请解释你的努力。
  • @Patrick 我假设 OP 在两个站点都问了同样的问题(这意味着他可能是外班人,只是复制了它)。
  • @Patrick 是的,就是我哈哈(没有意识到我有一个旧帐户登录 IE)。我知道有一种方法可以在数学上和编程上都做到这一点,并且只是在寻找其中一个。数字数学是寻找前者的最佳位置,而 Stack 是寻找后者的最佳位置。
  • @Patrick 过去 4 天我一直在尝试解决方案。这不是分区问题,因为 A)分区问题处理两个子集,B)我特别指出序列的顺序不应该改变(它与分区问题有关)

标签: algorithm split language-agnostic


【解决方案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

【讨论】:

  • 而不是均匀放置边界,您可以遍历列表并在前缀总和大于 i* n/4 时放置 ith 边界。在找到最佳解决方案之前,这应该会减少迭代次数。
【解决方案2】:

如果你想要的东西不是最优的,但简单、快速且足够好(考虑到分布没有疯狂倾斜),我建议你这样做:

  1. 计算总和
  2. 除以 N(你想要多少分片)
  3. 贪婪地为每个分区取最大值,直到分区总和

您将拥有 N-1 个 = K (K=Sum/N) 的分区。它比实际的分区问题更容易,而且不正确,但考虑到您的上下文,这似乎是可以接受的,尤其是因为通常后面的值(与 W X Y Z 等字母匹配)将具有较小的值。

【讨论】:

  • 他想要最优解。
  • 我不同意。他说他想要“最好的”解决方案,但还不清楚(例如,数学最优 VS 最佳时间性能启发式)。这就是为什么我指定这个解决方案没有给出数学上的最优值,但绝对容易实现和维护,而且速度很快 (O(n));
  • “我想将该序列分成 4 个块,其中每个块的总和尽可能接近序列总和的四分之一。”
  • 这意味着最小均值、中值还是总误差?如您所见,即使在这种解释中,也有很多模棱两可的地方。无论如何,这种方法在大多数情况下都会返回一些伪最优的东西。
【解决方案3】:

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

算法的基本思想如下:

初始化:

  • 计算所需的集群大小,n/k。
  • 初始化均值,最好使用 k-means++
  • 按到最近集群的距离减去到最远集群的距离对点进行排序(= 最佳优于最差的最大优势 作业)
  • 将点分配给他们的首选集群,直到该集群已满,然后重新处理剩余的对象,而不将整个集群放入 帐户不再此初始化不是最佳的 - 随意 为本教程做出改进! - 特别是对于 最后一个集群。但它会作为一种初始化方法。

迭代:

  1. 计算当前集群意味着
  2. 对于每个对象,计算到聚类均值的距离
  3. 根据当前分配的增量和可能的最佳替代分配对元素进行排序。
  4. 对于每个元素的优先级:
    1. 对于其他集群,按元素增益,除非已经移动:
    2. 如果有一个元素想要离开另一个集群并且此交换产生和改进,则交换两个元素
    3. 如果元素可以在不违反大小限制的情况下移动,请移动它
    4. 如果元素未更改,则添加到传出传输列表。
  5. 如果不再进行传输(或达到最大迭代阈值),则终止

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

【讨论】:

  • 可能需要开发一个特殊的数据 bean 容器来存储列表中的原始位置,因为它指定应该保留顺序。
【解决方案4】:

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

让我们表示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|)&lt;=X 吗?如果我们可以为任意X解决这个问题,那么最初的问题是通过对X进行二分查找来解决的。

那么,我们如何检查是否存在带有max(|s1-S/4|, |s2-S/4|, |s3-S/4|, |s4-S/4|)&lt;=X 的分区?这个要求等价于要求S/4-X&lt;=s[i]&lt;=S/4+X,所以对于每个块我们知道允许的最小和最大和。

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

现在找到第二个块可以结束的位置。这有点棘手。最简单的方法是从第一个块的每个找到的结束位置开始,并找到第二个块的相应可能结束位置。但是有一种更快的方法。首先,从第一个chunk的第一个可能的结束位置开始,计算第二个chunk对应的结束位置。然后,移动到第一个块的第二个可能的结束位置。请注意,这只会为第二个块添加一些新的结束位置,这些位置位于已找到位置的右侧,因此无需全部重复;如果您保留“当前”第二个块所覆盖的累积跨度总和,那么您可以在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] 定义了范围。

【讨论】:

    猜你喜欢
    • 2020-07-24
    • 2020-09-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-11-26
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多