用于在内存中存储字符串数组的数据结构

Posted

技术标签:

【中文标题】用于在内存中存储字符串数组的数据结构【英文标题】:data structure for storing array of strings in a memory 【发布时间】:2011-04-05 12:47:24 【问题描述】:

我正在考虑在内存中存储大量字符串的数据结构。字符串将插入程序的开头,并且在程序运行时不会添加或删除。关键点是搜索过程应该尽可能快。节省内存并不重要。我倾向于使用标准库中的标准结构 hash_set,它允许以大约恒定的时间搜索结构中的元素。但不能保证这段时间会很短。有人会提出更好的标准决定吗?

非常感谢!

【问题讨论】:

你想要一个数组还是一个关联容器? “Array”和“Hash_Set”执行非常不同的功能。 search elements in the structure with about constant time. But it's not guaranteed that this time will be short。我认为您不了解恒定时间的含义。除非我们更多地了解字符串是什么、它们有多大、它们是如何存储的、它们是如何被访问的,否则恒定时间是最好的选择 But it's not guaranteed that this time will be short。除非您正在构建一个 RT 系统,否则它可能会足够短。 @Falmarri:准确地说,恒定时间是无限的。它保证的唯一属性是,无论 N 有多大,执行都需要相同的时间。在实践中,K 通常很小。 @Merlyn:是的。但这就是为什么我添加了该段的其余部分。不了解细节就无法变得更好 【参考方案1】:

试试Prefix Tree

在搜索元素方面,Trie 比二叉搜索树更好。与哈希表相比,您可以看到this question

【讨论】:

snicker 尝试前缀树 snicker :) 我也想到了这一点,但后来意识到它所需要的所有指针追逐都是非常低效的。此外,它使查找在 O(n) 中运行,其中 n 是字符串的长度。 Judy 数组也很令人兴奋,虽然我从来没有理由使用过。 如果 n 是字符串的长度,那么计算哈希值也是 O(n)。除此之外,不包括特殊情况,一个简单的哈希表可能比一个简单的 trie 更快。诚然,它没有相同的功能,例如前缀匹配,有序枚举。 正如@andras 指出的,哈希表在字符串长度上也是 O(n)。仅当计算散列函数所需的时间是恒定的或低到可以忽略时,散列查找才为 O(1)。【参考方案2】:

如果查找时间真的是唯一重要的事情,那么在启动时,一旦你有了所有的字符串,你就可以在它们上计算一个perfect hash,并将其用作哈希表的哈希函数。

问题在于如何执行散列 - 任何类型的基于字节码的计算都可能比使用固定散列和处理冲突要慢。但是,如果您只关心查找速度,那么您可以要求您的进程具有加载和执行代码所需的权限。编写完美哈希的代码,通过编译器运行它,加载它。在运行时测试这些字符串是否真的比您最知名的数据不可知结构(可能是 Trie、哈希表、Judy 数组或 splay 树,具体取决于实现细节和您的典型访问模式),如果不是回退到那个。设置慢,查找快。

速度几乎从来不是真正的关键点。

【讨论】:

【参考方案3】:

有例如google-sparsehash。 它包括一个密集的哈希集/映射(重新)实现,其性能可能比标准库哈希集/映射更好。 见performance。确保您使用的是良好的散列函数。 (我的主观投票:murmur2。)

字符串将插入到 程序的开始,不会 在程序运行时添加或删除。

如果字符串是不可变的 - 所以插入/删除是“不频繁的”,可以这么说 - 另一个选择是构建一个 Directed Acyclic Word Graph 或 Compact Directed Acyclic Word Graph 可能*比一个哈希表,并且有更好的最坏情况保证。

**适用标准免责声明:取决于用例、实现、数据集、月相等。由于未考虑的因素(例如缓存和内存延迟、某些机器指令的时间复杂度等)。*

【讨论】:

【参考方案4】:

具有合适数量的桶的 hash_set 将是理想的,或者具有字典顺序的字符串的向量,使用二进制搜索进行搜索,也会很棒。

【讨论】:

【参考方案5】:

用于快速字符串查找的两种标准数据结构是哈希表和tries,尤其是Patricia tries。一个好的哈希实现和一个好的 trie 实现应该提供相似的性能,只要哈希实现足够好以限制冲突的数量。由于您从不修改字符串集,您可以尝试构建一个perfect hash。如果性能比开发时间更重要,请尝试所有解决方案并对其进行基准测试。

一种可以在字符串表中保存查找的补充技术是使用atoms:每次读取一个知道要在表中查找的字符串时,立即查找它,并存储指向它的指针(或数据结构中的索引)而不是存储字符串。这样,测试两个字符串的相等性是一个简单的指针或整数相等(并且您还可以通过将每个字符串存储一次来节省内存)。

【讨论】:

你可以做一些花哨的事情来快速查找原子——例如首先按长度排序,然后按字典顺序存储它们。存储指向每个给定长度的第一个和最后一个字符串的指针。然后你可以先跳到右边的块,然后二分叉查找。【参考方案6】:

您最好的选择如下:

    构建您的结构:
      将所有字符串 (char*s) 插入到数组中。 按字典顺序对数组进行排序。
    查找
      对数组使用二分搜索。

这保持了缓存局部性,允许高效查找(将在大约 40 亿个字符串的空间中搜索 32 次比较),并且实现起来非常简单。没有必要对尝试着迷,因为它们很复杂,而且比它们看起来要慢(尤其是当你有长字符串时)。

随机附注:结合http://blogs.msdn.com/b/oldnewthing/archive/2005/05/19/420038.aspx,你将势不可挡!

【讨论】:

@Clark:嗯,有没有什么方法可以让你知道字符串的顺序后,就可以构建一个新的字符串池,让字符串靠得很近,这可能是“下一个”比较?因此,例如,有 128 个字符串,想象我可以在一页中放置大约 8 个字符串,数字 64、32、96、16、48、80、112 应该都在同一页上,然后数字 1-8 应该在另一页上, 9-15 在另一个上,依此类推。因此,保证每次查找只命中两页。显然它比这更复杂一些,因为字符串的长度不会都一样...... ... 当然,这可能方式的麻烦多于其价值,因为如果您进行大量查找,您最终会发现整个事情反正一直缓存。但是提问者确实说查找是唯一关键的事情,所以他手上大概有很多开发时间;-) @Clark:是的,只有 32 次比较,但您应该注意,这些是字符串比较,可能相对昂贵,而不是字符比较。 @Billy:我不这么认为。一方面,它访问缓存的次数要少得多,另一方面,它会提前退出,并且只会在成功时比较整个字符串。 “它对缓存的影响要少得多”——可能是比较幼稚的实现。与此解决方案相比,Patricia 树实际上可以命中更少的缓存行:log(n) 个节点(与您的 log(n) 个字符串相同),每个节点访问的数据更少(最小区分字符串片段)。不幸的是,字符串的二叉树和简单的二叉树共享一个属性,当您在字典上更接近目标时,很可能您会得到几个具有共同前缀的字符串,因此您会得到“缓慢”的失败比较。当您有 100k 个字符串以“***.com/questions”开头时,尝试看起来不错。【参考方案7】:

好吧,假设你真的想要一个 array 而不是你提到的 associative contaner,Raymond Chen's Blog 中提到的分配策略会很有效。 p>

【讨论】:

以上是关于用于在内存中存储字符串数组的数据结构的主要内容,如果未能解决你的问题,请参考以下文章

go——数组

4-数组指针与字符串1.4-动态内存分配

c++中字符串是如何分配内存的?

leetcode 数据结构 探索数组和字符串

将浮点值存储到无符号字符数组

mysql中怎么存储数组