【问题标题】:Sparse O(1) array with indices being consecutive products索引为连续乘积的稀疏 O(1) 数组
【发布时间】:2011-06-09 12:21:26
【问题描述】:

我想预先计算一些一元函数f的值数组。

我知道我只需要f(x) 的值,其中x 的形式为a*b,其中ab 都是0..N 范围内的整数。

明显的时间优化选择只是创建一个大小为N*N 的数组,并且只预先计算我稍后将要阅读的元素。对于f(a*b),我只需检查并设置tab[a*b]。这是可能的最快方法 - 但是,这将占用大量空间,因为该数组中有很多索引(以 N+1 开头)永远不会被触及。

另一种解决方案是制作一个简单的树形图......但这会通过引入大量分支而大大减慢查找本身的速度非常。没有。

我想知道 - 是否有任何解决方案可以使这样的数组不那么稀疏和更小,但在查找中仍然快速无分支 O(1)?

编辑

我可以听到很多关于哈希映射的 cmets... 我将继续对一个行为的行为进行基准测试 (我预计由于分支,性能会比正常查找显着下降;低于树,但仍然如此。 ..让我们看看我是否正确!).

我想强调:我最喜欢分析解决方案,它会使用一些聪明的方法 (?) 来利用仅采用“类产品”索引这一事实.我觉得这个事实可能会被利用来获得比普通的通用哈希映射函数更好的结果,但我自己没有想法。

编辑

按照您的建议,我从 gcc 4.5 尝试了 std::unordered_map。这比简单的数组查找慢了一点,但确实比基于树的std::map 快得多 - 最终我对这个解决方案感到满意。我现在明白了为什么不能做我最初打算做的事情;感谢您的解释!

我只是不确定哈希映射是否真的节省了任何内存! :) 正如@Keith Randall 所描述的,我无法使内存占用低于N*N/4 和三角矩阵@Sjoerd 描述的方法给了我N*N/2。我认为,如果元素大小很小(取决于容器开销),哈希映射完全有可能使用超过 N*N/2 空间 - 这将使最快的方法也是最有效的内存!我会试着检查一下。

我希望我能接受 2 个答案...

【问题讨论】:

  • 你能把f改成f(a, b)吗?
  • 当然......即使是包装器也可以做到这一点。结果仅取决于产品。
  • 嗯,不清楚您在通话时是否真的知道ab,或者您是否只知道x
  • 如果您找到一种快速跳过未使用条目的方法,它可以变成一种快速判断数字是否为素数的方法。由于后者被认为是一个难题,我怀疑你会为你的问题找到一个好的分析解决方案。

标签: c++ algorithm sparse-array


【解决方案1】:

为什么不简单地散列 A 和 B 组合并将结果放入地图中?懒惰地做,这样你就能得到你想要的?

public Result f(Type1 a, Type2 b) {
    TypePair key = new TypePair(a, b);
    Result res = map.get(key);
    if (res == null) {
        res = reallyCalculate(a, b);
        map.put(key, res);
    }
    return res;
}

基本记忆。

【讨论】:

  • 这看起来很像 Not C++。
  • 由于(a,b)的分布,会发生非常多的碰撞。每次碰撞都是另一个分支。更不用说冲突列表的内存开销了。对于小 N,一个简单的 N*N 数组将需要更少的内存并且速度更快。也许实际的 N 会在那个范围内?
  • 取决于散列函数和底层数组的大小。如果数组足够大并且哈希值很好,那么使用 n x n 数组没有任何好处——尤其是当它是稀疏的时候。
  • @Andrei 你依赖“如果 [...] 哈希是好的”。这是一个很大的假设。
  • 好吧,程序员可以控制哈希函数,所以这取决于他们。
【解决方案2】:

Hash tables 在查找速度和内存开销之间提供了良好的平衡。 C++ 标准库不提供哈希表,尽管它有时可作为非标准扩展使用。例如,请参阅SGI hash_map

Poco C++ 库也有一个 HashTable 和 HashMap 类,参见documentation

【讨论】:

    【解决方案3】:

    首先将其视为一个二维数组:tab[a][b]。这仍然需要 N*N 大小。

    将使用每个条目,但会有重复:f(a,b) = f(b,a)。所以只需要一个三角矩阵(代价是 a>b vs a

    if (a < b) return tab[b*(b+1) + a]; // assuming 0 <= a < b < N
    else return tab[a*(a+1) + b];       // assuming 0 <= b <= a < N
    

    或者

    if (a < b) return tab[b*(b-1) + a]; // assuming 1 <= a < b <= N
    else return tab[a*(a-1) + b];       // assuming 1 <= b <= a <= N
    

    编辑:三角矩阵使用的内存是 (N+1)*N/2,大约是方阵大小的一半。不过还是二次方的:(

    EDIT2:请注意, er 仍然是矩阵中的重复项:例如f(3, 2) = f(6, 1)。我认为如果不引入大量分支和循环就无法消除这种情况,但这只是一种直觉。

    【讨论】:

    • 这假设 f(a,b) 确实等于 f(b,a)
    • @Will 由于问题指出在计算中仅使用 a 和 b 的乘积,我假设是这种情况。
    【解决方案4】:

    这里似乎没有太多可以利用的结构。如果你问是否有办法安排表格,这样你就可以避免存储不可能发生的条目(因为它们的素数大于 N),你不能节省太多。有一个theory of smooth numbers,它指出 N^2 附近的 N-光滑数的密度是 ~2^-2。因此,绝对最佳情况下,您最多可以将(最大)存储需求减少 4 倍。

    如果您希望大多数参数永远不会发生,我认为您最好利用对称性,然后使用哈希表。

    【讨论】:

      猜你喜欢
      • 2019-07-04
      • 2017-07-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-05-06
      • 1970-01-01
      • 1970-01-01
      • 2019-05-09
      相关资源
      最近更新 更多