【问题标题】:Efficient storage of prime numbers素数的有效存储
【发布时间】:2010-11-05 04:03:06
【问题描述】:

对于一个库,我需要将第一个质数存储到一个限制 L。这个集合必须有 O(1) 查找时间(检查一个数字是否是质数)并且它必须很容易,给定一个数字,用于查找下一个素数(假设它小于 L)。

鉴于 L 是固定的,生成列表的 Eratostene 筛子就可以了。现在,我使用压缩布尔数组来存储列表,其中仅包含 3 到 L(含)之间奇数的条目。这需要 (L-2)/2 位内存。我希望能够在不使用更多内存的情况下静态增加 L。

是否存在使用较少内存且具有相似属性的数据结构?或者至少有恒定的查找时间? (然后可以枚举奇数,直到我们得到一个素数)

(我写这个的语言是Factor,但是这个问题在任何具有内置或易于编程的打包位数组的语言中都是一样的)

【问题讨论】:

  • 什么是典型的“L”?这适用于内存紧张的嵌入式设备吗?它可能会影响建议。鉴于有 50,847,534 个质数低于 10 亿,您可能会花费更多时间打包/解包,然后是 4 字节整数的直接数组。
  • 而且我不希望需要超过我今天拥有的 ~320kB 的内存。
  • 所以你想将5,000,000个整数的信息存储到320,000个字节...
  • DanielDaranas:这就是今天正在做的事情。鉴于只存储奇数素性(并获取一位信息,对或错),我将大约 2,500,000 个奇数的信息存储在不到 313,000 个字节中。为什么会让你感到惊讶?
  • 我认为我们可以在这么小的空间内压缩这么多东西,这很酷。

标签: math data-structures primes factorization


【解决方案1】:

对于非常特殊的素数情况,我称之为“位串素数”(只有 1 和 0 的以 10 为底的十进制数),我已经找到了无损存储它们的方法,甚至无需使用标准压缩算法.

一个极端情况:我已经在这 3 个英文字母中无损编码了一个 3312 位素数:

  • Mcb

【讨论】:

    【解决方案2】:

    区间树怎么样? http://www.geeksforgeeks.org/interval-tree/

    它可能不是 O(1),但它真的很快。就像 O(log(p(n))) 一样,其中 p(n) 是直到数字 n 的素数数。这样,您需要的内存将仅与素数的数量成正比,从而大大降低了内存成本。

    例如,假设您在 p1 处找到一个素数,然后在 p2 处找到下一个素数, 插入区间 (p1,p2) 等,当您搜索该范围内的任何数字时,它将返回此区间,您可以返回 p2,这将是您的答案。

    【讨论】:

    • "Insert interval (p1,p2)" 你仍然有存储那些巨大的数字 p1 和 p2 的问题
    • 好吧,错过了关于 L 限制的评论。但即便如此,大约有 325 000 个素数低于 500 万,因此您的建议需要至少 2(间隔)* 325 000(素数之间的间隔) * 32 位(int 数据类型)= 20 800 000 位 = 650 kb,这已经是他能承受的字节数的两倍了。
    • @KavehHadjari 不,你不必使用 4 个字节来使用它。你可以尝试使用一些紧凑的布尔数组,它可能会使用 2 个字节和 5 位的东西,这可能会使它下降很多很多,但又不会让他的削减..
    • @KavehHadjari 但是是的,我猜你是对的,你还需要额外的内存来存储区间树中的指针......我会让答案保留下来,尽管可能对那些不太严格的人来说要求..因为如果 L 是几十亿等,我的回答会很好。
    • 你也许可以通过修改你的答案来实现它,如果你将增量存储到每个节点的间隔,那么你需要更少的位在树的下方(更小的增量需要进一步向下树),从而减少空间需求,如果你能以某种方式与由固定大小的数组表示的树结合使用,你可能会对它进行尝试。
    【解决方案3】:

    【讨论】:

      【解决方案4】:

      打包位图和轮子的替代方案 - 但在某些情况下同样有效 - 是存储连续素数之间的差异。如果您像往常一样忽略数字 2,那么所有差异都是偶数。存储差异/2,您可以使用字节大小的变量获得多达 2^40 个区域(就在 1999066711391 之前)。

      2^32 上的素数只需要 194 MB,而仅赔率打包位图则需要 256 MB。迭代 delta 存储的素数比轮式存储快得多,轮式存储包括称为仅赔率位图的模 2 轮。

      对于从 1999066711391 开始的范围,需要更大的单元格大小或可变长度存储。即使使用非常简单的方案,后者也可能非常有效(例如,不断添加直到添加了一个 LZ4 样式压缩中一样),因为超过 510/2 的间隙的频率极低。

      为了效率最好将范围划分为部分(页面)并以B-Tree的方式管理它们。

      对差异进行熵编码(霍夫曼或算术编码)将永久存储需求减少到不到一半,这接近于理论上的最优值,并且比使用最佳可用打包器压缩的列表或轮子更好。

      如果数据以未压缩的形式存储,那么它仍然比二进制或文本数字文件更紧凑,数量级或更多。有了 B-Tree 风格的索引,就可以很容易地根据需要将部分映射到内存中,并以惊人的速度对其进行迭代。

      【讨论】:

      • 这没有 O(1) 查找时间。
      【解决方案5】:

      目前您将 2 视为特殊情况,然后有一个数组,其中每个奇数都映射到数组中的一个元素(一些奇数是素数)。您可以通过将 2 和 3 视为特殊情况来改进这一点,认识到其余素数的形式为 6n+1 或 6n-1(即对于 p > 3、p mod 6 = 1 或5)。这可以进一步推广 - 参见Wikipedia。对于所有 p > 5 的素数,p mod 30 = 1、7、11、13、17、19、23 或 29。您可以继续这样做并以处理时间为代价减少所需的内存(尽管它仍然会是O(1),只是更慢的 O(1))。

      【讨论】:

        【解决方案6】:

        某种哈希表怎么样?

        您需要一个非常好的哈希函数(类似于n mod p,其中p 不是任何q 最低质数的倍数 - 选择足够高的q 以尽量减少冲突次数)。

        【讨论】:

          【解决方案7】:

          也许你正在寻找一个只包含素数的trie 数据结构。您可以使用整数数字,而不是使用字符作为索引。一个实现是Judy-Arrays。

          尽管如此,它们不满足您的 O(1) 要求,但它们对于相似的键(大多数数字都是如此)具有极高的内存效率,并且使用 O(m) (m=key-长度)最大。

          如果您在预先生成的树中查找素数,您可以遍历树直到找到它,或者您已经在前一个和后一个素数旁边的节点处。

          【讨论】:

            【解决方案8】:

            鉴于内存是如此便宜,我认为从速度的角度来看,您无法比现有方案做得更好。

            如果有更好的解决方案,那么我认为它会利用Prime Number Theorem 表明随着 L 变大,限制

            π(L) / (L / ln(L)) 接近 1。

            也许更好的解决方案是采用类似于skip list 的数据结构中的自适应打包解决方案。

            【讨论】:

              【解决方案9】:

              您可以显式检查更多素数以消除冗余。

              目前您只为两个执行此操作,通过明确检查是否可被两个整除,然后仅存储奇数是否为素数。

              对于 2 和 3,你得到 0 到 5 的余数,其中只有 1 和 5 不能被 2 或 3 整除,并且可以得到质数,所以你只有 1/3。

              对于 2、3 和 5,您会从 30 个数字中得到 8 个数字,这很适合存储在一个字节中。

              这在here有更详细的解释。

              【讨论】:

              【解决方案10】:

              如果您能确定哪些是Mersenne 或其他容易表示的素数,则可以通过使用带有适用数字标志的表示来节省一些位。

              另外,如何将数字存储为与前一个数字的差值?那么大小不应该增长得那么快(但查找会很慢)。结合上述方法,您可以存储梅森素数以及与上一个梅森素数的差。

              【讨论】:

                猜你喜欢
                • 2011-02-06
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2013-09-02
                • 1970-01-01
                • 2014-01-29
                相关资源
                最近更新 更多