【问题标题】:Quickly determine if number divides any element in a set快速确定数字是否除以集合中的任何元素
【发布时间】:2012-05-08 10:13:59
【问题描述】:

有没有一种算法可以快速确定一个数字是否是给定数字集的一个因子?

例如,12[24,33,52] 的一个因子,而 5 不是。

有没有比线性搜索更好的方法O(n)?该集合将包含几百万个元素。我不需要找到号码,只需一个 truefalse 结果。

【问题讨论】:

  • 听起来您正在寻找整数分解。 AFAIK,这不是在量子计算之外存在多项式时间解决方案的问题。
  • 这个集合是有序的吗?集合元素的范围是否受到任何限制?最佳算法始终取决于对数据的良好了解,如果您了解更多,请告诉我们。
  • 集合可以排序。元素的范围在 0 - 10^12 之间。
  • 集合和数字仅在运行时可用,还是其中之一或两者都可以在运行前处理?
  • “一个数字”是指要查找这样的数字还是要测试一个特定的数字?前者目前没有 Tony The Lion 建议的多项式算法。在后一种情况下,最佳算法取决于您是只测试一次、针对常数集测试多个潜在因素还是针对多个集合测试一个潜在因素。

标签: algorithm math language-agnostic


【解决方案1】:

如果根据一个常量列表检查大量数字,一种可能的加速处理的方法是首先将列表中的数字分解为它们的素因子。然后将列表成员放入字典中,并将主要因素作为键。然后当一个数字(潜在因子)出现时,您首先将其分解为其素因子,然后使用构造的字典检查该数字是否是可能是给定数字的倍数的数字的因子。

【讨论】:

    【解决方案2】:

    我认为通常 O(n) 搜索是您最终会得到的结果。但是,根据一般数字的大小,您可以通过观察如果您正在搜索找到一个可被 D 整除的数字并且您有当前扫描的 x 和 x 不能被 D 整除,下一个可能的候选者显然在 floor([x + D] / D) * D。也就是说,如果 D = 12 并且列表为

    5 11 13 19 22 25 27
    

    如果您正在扫描 13,下一个可能的候选编号将是 24。现在,根据您输入的分布,您可以使用 二分搜索而不是线性搜索向前扫描,就像您现在一样现在搜索列表中不少于 24 的最小数,然后对列表进行排序。如果 D 很大,那么您可以通过这种方式节省大量比较。

    但是从纯计算复杂度的角度来看,排序然后搜索将是 O(n log n),而线性扫描是 O(n)。

    【讨论】:

      【解决方案3】:

      要针对一个常数集合测试许多潜在因素,您应该意识到,如果集合中的一个元素只是其他两个元素的倍数,则它是无关紧要的,可以删除。这种方法是一种称为Sieve of Eratosthenes 的古老算法的变体。在测试大量候选人时以启动时间换取运行时间:

      1. 选择集合中最小的>1
      2. 从集合中删除该数字的任何倍数,除了自身
      3. 重复 2 以获得下一个最小的数字,并进行一定次数的迭代。迭代次数将取决于与启动时间的权衡

      您现在只剩下一个小得多的集合来进行详尽的测试。为了提高效率,您要么需要一个允许 O(1) 删除的集合的数据结构,如链表,要么只需将“已删除”元素替换为零,然后将非零元素复制到新容器中。

      【讨论】:

        【解决方案4】:

        我不确定这个问题,所以让我再问一个问题:12 是 [6,33,52] 的因数吗?很明显,12 不能整除 6、33 或 52。但 12 的因数是 2*2*3,而 6、33 和 52 的因数是 2*2*2*3*3*11*13。 12 的所有因子都存在于集合 [6,33,52] 中,具有足够的多重性,因此您可以说 12 是 [6,33,52] 的因子。

        如果你说 12 不是 [6,33,52] 的因数,那么没有比测试每个数是否能被 12 整除更好的解决方案了;只需执行除法并检查余数。因此 6%12=6、33%12=9 和 52%12=4,所以 12 不是 [6.33.52] 的因数。但是如果你说12[6,33,52]的因数,那么判断一个数f是否是集合ns,只需将数字 ns 依次相乘,每次相乘后取余数模 f,如果余数为真则立即报告 true 0,如果到达数字列表的末尾 ns 且余数不为 0,则报告 false

        让我们举两个例子。首先,12 是 [6,33,52] 的因数吗?第一次(微不足道的)乘法结果为 6,余数为 6。现在 6*33=198,除以 12 余数为 6,我们继续。现在 6*52=312 和 312/12=26r0,所以余数为 0,结果为 true。其次,5 是 [24,33,52] 的因数吗?乘法链为 24%5=5、(5*33)%5=2、(2*52)%5=4,所以 5 不是 [24,33,52] 的因数。

        该算法的一个变体最近被用于attack RSA 密码系统;您可以阅读有关攻击的工作原理here

        【讨论】:

          【解决方案5】:

          由于要搜索的集合是固定的,因此花在组织搜索集合上的时间都是值得的。如果您可以在内存中获取该集合,那么我希望二叉树结构会适合。平均而言,在二叉树中搜索一个元素是一个O(log n) 操作。

          如果您有理由相信集合中的数字在[0..10^12] 范围内均匀分布,那么内存中排序集合的二分查找应该与二叉树查找一样执行。另一方面,如果集合(或集合的任何子集)中的中间元素预计不会接近集合(或子集)包含的范围内的中间值,那么我认为二叉树会更好(实际)性能。

          如果您无法将整个集合放在内存中,那么将其分解为适合内存的块并将这些块存储在磁盘上可能是可行的方法。您可以将集合的根分支和上分支存储在内存中,并使用它们在磁盘上建立索引。保存在内存中的树部分的深度应该由您自己决定,但如果您需要的不仅仅是根和 2 级分支,在磁盘上提供 8 个块,我会感到惊讶。

          当然,这只解决了你的部分问题,查找给定的数字是否在集合中;你真的想知道给定的数字是否是集合中任何数字的因数。正如我在 cmets 中所建议的那样,我认为任何基于对集合中的数字进行因式分解的方法都是没有希望的,因为它给出了超出多项式时间的预期运行时间。

          我会反过来处理这部分问题:生成给定数字的倍数并搜索它们中的每一个。如果你的集合有 10^7 元素,那么任何给定的数字 N 将在集合中有大约 (10^7)/N 倍数。如果给定数字是从[0..10^12] 范围内随机抽取的,则N 的平均值为0.5*10^12,这表明(与直觉相反)在大多数情况下您只需搜索N 本身。

          是的,我知道在许多情况下您必须搜索更多的值。

          这种方法相对容易并行化。

          【讨论】:

            【解决方案6】:

            需要一些预计算的快速解决方案:

            按照以下规则将您的集合组织成二叉树:

            • 集合的数字在叶子上。
            • 树的根包含r 可整除一组数的所有素数中的最小值。
            • 左子树对应r的倍数子集(除以r,这样r就不会无限重复)。
            • 右子树对应于不是r 倍数的数字子集。

            如果您想测试一个数字 N 是否除以集合中的某个元素,请计算其素数分解并遍历树直到到达叶子。如果叶子包含一个数字,则N 将其除,否则如果叶子为空,则N 不除集合中的任何元素。

            【讨论】:

              【解决方案7】:

              只需计算集合的乘积并将结果与​​测试因子相乘。 在你的例子中 {24,33,52} P=41184

              Tf 12: 41184 mod 12 = 0 真 Tf 5: 41184 mod 5 = 4 假

              如果计算乘积会溢出计算器的算术,则可以将集合分成块,但是通过存储字符串可能会产生巨大的数字。

              【讨论】:

                猜你喜欢
                • 2016-06-18
                • 1970-01-01
                • 1970-01-01
                • 2011-05-31
                • 2017-11-01
                • 2017-12-11
                • 1970-01-01
                • 1970-01-01
                • 2012-03-10
                相关资源
                最近更新 更多