我如何将一系列数字分成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.序列是这样的,它的顺序不应该改变。
背景:这些数字代表电话簿中以某个字母开头的条目数。我正试图以最好的方式拆分电话簿。
答案 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)
如果你想要的东西不是最优的,但又容易,快速和足够好(鉴于分布不是疯狂的倾斜),我建议你做这样的事情:
您将拥有&lt; = K的N-1个分区,并且将具有&gt; = K(K = Sum / N)的分区。它比实际分区问题容易,并且不正确,但考虑到你的上下文,这似乎是可以接受的,特别是因为通常后面的值(与W X Y Z等字母相匹配)的值会更小。
答案 2 :(得分:0)
这被称为相同大小的K-Means问题。通常它指的是2-d变体,你可以更简单 - 只有一个维度的情况。
该算法的基本思想如下:
初始化:
- 计算所需的群集大小,n / k。
- 初始化意味着,最好用k-means ++
- 通过到最近的星团的距离减去距离最远群集的距离(=最好的最佳效果) 分配)
- 将点分配给其首选群集,直到此群集已满,然后使用剩余对象,而不占用整个群集 帐户不再这个初始化不是最佳的 - 随意 为本教程做出了改进! - 特别是对于 最后一个集群但它将作为初始化方法。
迭代:
- 计算当前群集意味着
- 对于每个对象,计算到群集的距离意味着
- 根据当前分配的增量和最佳备用分配对元素进行排序。
- 按优先级对每个元素:
- 对于每个其他群集,按元素增益,除非已移动:
- 如果有一个元素想要离开另一个集群并且这个交换产生和改进,那么交换这两个元素
- 如果可以在不违反尺寸限制的情况下移动元素,请将其移动
- 如果元素未更改,请添加到传出转移列表。
- 如果没有进行更多转移(或达到最大迭代阈值),则终止
醇>
答案 3 :(得分:0)
首先,您应该确定要最小化的确切值。
我们将S
表示数字的总和,s1
,s2
,s3
和s4
表示某些解决方案中四个部分的总和
我们可以定义一个相当模糊的术语“尽可能相等”的许多确切表示。也就是说,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-X
到S/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]
定义范围。