最大除数使得两个数除以它的数值相同?

时间:2016-05-02 17:29:07

标签: algorithm

我有一个算法,可以解释为将数字行分成相同数量的块。为简单起见,我会坚持使用[0,1),它将被分割为:

0|----|----|----|----|1

我需要做的是获取一系列数字[j,k]并找到最大数量的块,N,最多为某个最大值M,这将划分数字行所以[j,k]仍然都属于同一个“bin”。这比听起来更棘手,因为范围可以像这样跨越一个箱子:

    j|-|k
0|----|----|----|----|1

因此,在完全包含该范围之前,您可能必须达到相当低的数字。更重要的是,随着垃圾箱数量的增加,范围可能会移入和移出单个垃圾箱,因此存在局部最小值。

显而易见的答案是从M个箱子开始,减少数量,直到范围落入单个箱子。但是,我想知道是否有比列举所有可能的划分更快的方法,因为我的最大数量可以合理地大(8000万左右)。

有更好的算法吗?

3 个答案:

答案 0 :(得分:3)

在这里,我想提供另一种与btilly不同的启发式方法。

任务是找到整数mn,使m / n <= j < k <= (m + 1) / n n尽可能大(但仍在M下)。< / p>

直观地,分数m / n最好接近j。这导致了使用continued fractions的想法。

我建议的算法非常简单:

  1. 使用减号计算j的所有连续分数(以便分数始终从上方查找j),直到分母超过M;
  2. 对于每个此类分数m / n,找到i >= 0k <= (m * i + 1) / (n * i)这样的最大整数n * i <= M,并用{{1}替换分数m / n };
  3. 在2中的所有分数中,找到分母最大的分数。
  4. 该算法在(m * i) / (n * i)j中不对称。因此,有一个类似的k - 版本,通常不应该给出相同的答案,以便您可以从两个结果中选择较大的一个。

    示例:我将采用btilly的示例:kj = 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等于trueNheurN是商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/nj接近但小于k <= (m+1)/nj }。

所有有希望的候选人都将出现在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)使用的正确功效。

这种聪明的诀窍是将你的遍历视为:

  1. 从2&#34开始;等于&#34;级分。
  2. 取中位数。如果超过i那么我就完成了。否则我会从那个方向找出方向。
  3. M中尝试2的幂,直到我知道i范围为(a+i*c)/(b+i*d)的范围。{/ li>
  4. 进行二进制搜索以找到我可以使用的最后一个(a+i*c+c)/(b+i*d+d)
  5. 0/11/1是我的两个新等分。回到第一步。
  6. 初始相等分数当然是O(log(M))M = 3

    这将始终在j=0.6操作中找到合适的答案。不幸的是,这个相当不错的答案并不总是正确的。考虑k=0.651/21/3的情况。在这种情况下,启发式搜索将停留在M,而实际的最佳答案为1/2

    它失败的另一种方式是它只找到减少的答案。在上面的示例中,如果1/4为4,那么当它实际为this.props.location.query.color时,它仍然认为最佳答案为color。通过测试最终答案的多个是否有效,可以很容易地解决这个问题。 (这一步将改善你的答案一个固定但相当大的时间。)