我有一个算法,可以解释为将数字行分成相同数量的块。为简单起见,我会坚持使用[0,1),它将被分割为:
0|----|----|----|----|1
我需要做的是获取一系列数字[j,k]并找到最大数量的块,N,最多为某个最大值M,这将划分数字行所以[j,k]仍然都属于同一个“bin”。这比听起来更棘手,因为范围可以像这样跨越一个箱子:
j|-|k
0|----|----|----|----|1
因此,在完全包含该范围之前,您可能必须达到相当低的数字。更重要的是,随着垃圾箱数量的增加,范围可能会移入和移出单个垃圾箱,因此存在局部最小值。
显而易见的答案是从M个箱子开始,减少数量,直到范围落入单个箱子。但是,我想知道是否有比列举所有可能的划分更快的方法,因为我的最大数量可以合理地大(8000万左右)。
有更好的算法吗?
答案 0 :(得分:3)
在这里,我想提供另一种与btilly不同的启发式方法。
任务是找到整数m
和n
,使m / n <= j < k <= (m + 1) / n
n
尽可能大(但仍在M
下)。< / p>
直观地,分数m / n
最好接近j
。这导致了使用continued fractions的想法。
我建议的算法非常简单:
j
的所有连续分数(以便分数始终从上方查找j
),直到分母超过M
; m / n
,找到i >= 0
和k <= (m * i + 1) / (n * i)
这样的最大整数n * i <= M
,并用{{1}替换分数m / n
}; 该算法在(m * i) / (n * i)
和j
中不对称。因此,有一个类似的k
- 版本,通常不应该给出相同的答案,以便您可以从两个结果中选择较大的一个。
示例:我将采用btilly的示例:k
和j = 0.6
,但我将采用k = 0.65
。
我将首先完成M = 10
- 程序。为了计算j
的连续分数展开,我们计算:
j
由于 0.6
= 0 + 0.6
= 0 + 1 / (2 - 0.3333)
= 0 + 1 / (2 - 1 / (3 - 0))
是一个有理数,因此扩展会在很多步骤中终止。相应的分数是:
0.6
在步骤2中计算相应的0 = 0 / 1
0 + 1 / 2 = 1 / 2
0 + 1 / (2 - 1 / 3) = 3 / 5
值,我们用以下内容替换三个派系:
i
最大的分母是0 / 1 = 0 / 1
1 / 2 = 3 / 6
3 / 5 = 6 / 10
。
继续上面的示例,相应的6 / 10
- 过程如下:
k
因此相应的分数:
0.65
= 1 - 0.35
= 1 - 1 / (3 - 0.1429)
= 1 - 1 / (3 - 1 / (7 - 0))
通过第2步,我们得到:
1 = 1 / 1
1 - 1 / 3 = 2 / 3
1 - 1 / (3 - 1 / 7) = 13 / 20
最大的分母是1 / 1 = 2 / 2
2 / 3 = 6 / 9
13 / 20 = 0 / 0 (this is because 20 is already bigger than M = 10)
。
编辑:实验结果。
令我惊讶的是,算法比我想象的要好。
我做了以下实验,忽略了绑定6 / 9
(等效地,可以使M
足够大。)
在每一轮中,我会在M
的{{1}}中使用(j, k)
生成一对[0, 1)
均匀分布的随机数。如果差异j < k
小于k - j
,我会放弃这一对,使此轮无效。否则,我使用朴素算法计算真实结果1e-4
,并使用我的算法计算启发式结果trueN
,并将它们添加到统计数据中。这将持续1e6轮。
结果如下:
heurN
effective round = 999789
sum of trueN = 14013312
sum of heurN = 13907575
correct percentage = 99.2262 %
average quotient = 0.999415
是有效回合的百分比,correct percentage
等于trueN
,heurN
是商average quotient
的平均值所有有效轮次。
因此,该方法在99%以上的案例中给出了正确的答案。
我还使用较小的heurN / trueN
值进行了实验,结果相似。
答案 1 :(得分:2)
bin大小的最佳情况必须大于k-j
。
考虑数字线段[0..j]
和[k..1)
。如果我们可以使用相同的bin大小将两个部分段划分为多个部分,我们应该能够解决问题。
因此,如果我们考虑gcd((j-0)/(k-j), (1-k)/(k-j))
,(我们在除法后使用最大整数函数),我们应该能够得到一个好的估计值或最佳值。存在极端情况:如果(k-j) > j
或(k-j) > (1-k)
,则最佳值为1
本身。
所以一个非常好的估计应该是min(1, (k-j) * gcd((j-0)/(k-j), (1-k)/(k-j)))
答案 2 :(得分:2)
让我们稍微改变一下。
您希望找到m, n
尽可能大的n < M
m/n
)j
接近但小于k <= (m+1)/n
和j
}。
所有有希望的候选人都将出现在https://en.wikipedia.org/wiki/Stern%E2%80%93Brocot_tree。事实上,只要走过Stern-Brocot树就可以找到最后一个大理性的#34;在k
或更高的0.49999999
以下1/3, 2/5, 3/7, 4/9, ...
下方设置您的限制。
有一个并发症。通常树快速收敛。但有时Stern-Brocot树的序列很长,间隙很小。例如,到a/b < c/d
的序列将包括(a+c)/(b+d)
我们总是在(a+i*c)/(b+i*d)
时落入这些序列,然后我们取中间i
然后我们走向一边,所以M
。如果您非常聪明,那么您可以直接进行二元搜索,以获得(a+i*c)/(b+i*d)
使用的正确功效。
这种聪明的诀窍是将你的遍历视为:
i
那么我就完成了。否则我会从那个方向找出方向。M
中尝试2的幂,直到我知道i
范围为(a+i*c)/(b+i*d)
的范围。{/ li>
(a+i*c+c)/(b+i*d+d)
。0/1
和1/1
是我的两个新等分。回到第一步。初始相等分数当然是O(log(M))
和M = 3
。
这将始终在j=0.6
操作中找到合适的答案。不幸的是,这个相当不错的答案并不总是正确的。考虑k=0.65
,1/2
和1/3
的情况。在这种情况下,启发式搜索将停留在M
,而实际的最佳答案为1/2
。
它失败的另一种方式是它只找到减少的答案。在上面的示例中,如果1/4
为4,那么当它实际为this.props.location.query.color
时,它仍然认为最佳答案为color
。通过测试最终答案的多个是否有效,可以很容易地解决这个问题。 (这一步将改善你的答案一个固定但相当大的时间。)