【问题标题】:What data structure to use? (hash map vs. trie vs. ?)使用什么数据结构? (哈希图与特里与?)
【发布时间】:2011-09-18 01:32:49
【问题描述】:

我有一个生成大约 600 万个唯一数组的 C 函数。这些数组每个总是有 17 个元素,每个元素都是从 0 到 16 的整数。我还有一个稍微修改过的函数版本,它也将生成大约 600 万个相同类型的唯一数组。我的问题是第二个产生的结果比第一个少大约 45,000 个,我想看看这些结果是什么。

所以我的方法是简单地存储第二个函数的所有结果(计算器告诉我这不应该超过 400 mb,这可以保存在内存中)然后查找第一个函数的结果,打印出来那些不存在的。

假设通用方法有意义(如果没有,请说明),我正在寻找的是一个合适的数据结构(最好在 C 中具有良好的实现),它可以容纳大约 600 万个独特的排列

[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]

(或对其进行一些转换),然后对它们执行快速的成员资格测试。正如标题所说,我确实怀疑哪些数据结构可以完成这项工作,但我不确定尝试或哈希图是最好的选择。

这是一种用于检测另一个算法中的缺陷的算法,而不是在生产中使用的东西。我有兴趣以一种可以编码并以人类术语相对快速地返回结果的方式来执行此操作,不一定要减少毫秒,因此存在可以完成大部分工作的易于理解的库绝对是一个加分。

【问题讨论】:

  • 所以基本上你需要做set(A) - set(B)set 实现和 compare 例程可以完成这项工作。
  • 既然它只是一个辅助工具,非生产,它必须用C编写吗?其他语言也包含数据结构。
  • 原来的算法是 C 的,所以如果我不必将数据发送出去就可以完成工作。
  • 很公平。排序数组是最好的。适合long long 中的每个值的优化可以加快速度,并且“对数组进行排序并通过两者进行线性传递”可能如果它提高缓存性能 - 缓存性能将较大的数组排序可能比将这么多二进制搜索放入较小的数组时的缓存性能要好。

标签: c data-structures hashmap trie


【解决方案1】:

最优性在某种程度上取决于排列的分布方式以及插入与搜索的比率。由于您关心最优性,而只是想要一种直接的方法来检验假设而不需要整夜等待结果,我的直觉是:

一个整数[0,16]可以表示为一个五位数字,因此其中的17个可以表示为一个85位(11字节)的二进制字符串。因此,您可以只使用可用于存储已排序/散列的字符串集的众多库之一,并对其进行成员资格测试,然后就可以完成了。它不会像调整过的 trie 那样快或缓存一致,但它足以在几秒钟内完成 66mb 的数据,并且您将在午餐前完成。

如果没有这样的库可以方便地使用并且您必须从头开始工作,我只需制作一个排序的字符串列表,然后通过二进制搜索进行成员资格测试。这相当于 O( n log n + m( n log n em> ) ) = O( 2×mn log n ) eg 二次时间为 m→n。如果这只是在生产期间作为离线作业运行一次或两次,那可能就足够了;如果您打算每天多次执行此操作,我会担心缓存局部性并使用 trie 或 B-tree。

【讨论】:

  • 我喜欢你关于表现的思路。由于每个数字只出现一次,如果我将每个数字作为其顺序存储在其余选项中,我可以将位串减少到 55 位,这将适合长整数。
  • @Alexandros Marinos:太棒了!如果您可以将整个数组放入单个寄存器中,那么从编译器的角度来看,它会成为自己的排序键,搜索会快得多。
  • 不需要二分查找:只需对两个列表进行排序并进行线性扫描,即可完成整个过程n log n;由于每个数组都可以编码为单个整数,因此使用实际数组而不是链表可能更有效;使用自定义排序算法或 C++ 标准库中的 std::sort 而不是 qsort(),以便可以内联比较...
  • @crashworks:实际上,将排列表示为整数的最佳方法是使用阶乘数系统。 en.wikipedia.org/wiki/Factoradic 这可以将排列调整为 49 位,但更重要的是,来回转换是纯数学,没有位移等。(我怕我已经被这个书呆子了)
  • @Cristoph:O(n log n) 也是二进制搜索,只要您使用数组而不是链表。链表(稍微过度概括但不多)是一种糟糕的数据结构。但是排序算法可能比一大堆基本上随机的二进制搜索提供更好的局部性。
【解决方案2】:

我认为您需要权衡在 C 中执行此操作的价值,以避免交流。

我会将 C 中的每个数组逐行打印为空格分隔的整数。然后从文件中加载它以创建一组像这样的字节数组(F# 代码):

let load file =
  System.IO.File.ReadAllLines file
  |> Array.Parallel.map (fun s -> s.Split[|' '|] |> Array.map (int >> byte))
  |> set

然后像这样计算两个文件之间的集差:

load "file1.txt" - load "file2.txt"

这可能需要几分钟才能运行。

【讨论】:

    【解决方案3】:

    保持简单:

    • 将每个排列表示为一个 17 字节的数组
    • 将整个较小的集合存储为上述数组 (17*6M
    • 按字典顺序对其进行排序,因此qsort 的比较器只需调用memcmp(left, right, 17)
    • 对于较大集合中的每个元素,使用二分法在排序后的数组中查找它(使用与以前相同的比较器,这次使用 bsearch)。

    最后两个步骤中的每一个都将执行大约 6M * log(6M) 的比较,大约为 138M。这可能比编写代码所需的时间还少。这并不长,因为一切都那么简单:-)

    【讨论】:

      【解决方案4】:

      取决于您的情况下哪一个会获得更好的内存性能。还有你使用什么哈希函数,你如何解决冲突等等。看看Hash Array Mapped Trie (HAMT)

      【讨论】:

      • 我还没有散列函数。我已经编辑了问题以表明数组是唯一生成的,所以不应该有冲突(如果我理解正确的话)。
      • 散列的性能取决于散列函数,以及它们将数组映射到的散列位置,这将决定所需内存的范围。 trie 会占用大量内存。我认为 HAMT 可能会拯救你,或者其他类型的内存优化尝试。
      【解决方案5】:

      @Steve Jessop 您可以在线性时间内完成最后一步,通过删除我们正在搜索的数组中不需要的值来进行更智能的搜索:

      假设n是A的大小,m是B的大小,

      int counter_A = 0;
      int counter_B = 0;
      int counter_C = 0;
      while(counter_A != n){
          int temp = A[counter_A];
          counter_A++;
          //Removes all elements at the beginning of B since they are inferior than all
          //elements in A because they are inferior than the minimum of A
          for(;counter_B < m && B[counter_B] < temp;counter_B++);
          if((counter_B < m && B[counter_B] > temp) || counter_B == m){
              C[counter_C] = temp;
              counter_C++;
          }
      }
      

      这应该在 O(n+m) 时间内执行,因为算法的每一步都至少执行一次计数器的递增。

      【讨论】:

        【解决方案6】:

        a) 创建一个包含两个 64 位 int 的结构

        b) 由于每个结果有 17 个元素,因此将前 8 个元素相乘并将结果放在第一个 int 上,将其他 7 相乘并将结果放在第二个 int 上。

        c) 为你的结构创建一个操作符

        d) 创建一组结构并插入第一次运行的所有结果

        e) 遍历您的第二次运行结果并执行 set::find()

        class Result
        {
        public:
            Result(int arr[17]);              // Fill-in _n1 and _n2
        
            bool operator < (const Result& r) const  // Compare
            { 
                if (_n1 != r._n1)
                   return _n1 < r._n1;
                return _n2 < r._n2;
            }
        
        protected:
            int _n1;
            int _n2;
        };
        
        typedef std::set< Result > SetResult;
        SetResult setResult;
        

        埃德温

        【讨论】:

          猜你喜欢
          • 2015-03-05
          • 1970-01-01
          • 2016-10-31
          • 2010-11-10
          • 2011-07-30
          • 2012-10-09
          • 2015-10-06
          • 1970-01-01
          • 2011-05-27
          相关资源
          最近更新 更多