【问题标题】:An efficient dictionary of strings高效的字符串字典
【发布时间】:2013-02-09 01:47:36
【问题描述】:

我有一个数据结构问题。我有一个字符串集合,这些字符串在进程的整个生命周期中都会增长。我希望能够在程序中以不同的持续时间传递对这些字符串的引用。我不想将重复项添加到集合中,因此当我传入一个时,我希望返回对现有条目的引用,因此:

const std::string& add_new_entry(const std::string&)
{
    // Check if string exists
    // Return reference if it does
    // Otherwise add to collection
    // Return reference to it
}

最天真的实现是一个字符串列表和每次调用std::find,但我不禁觉得这是非常次优的,特别是因为我要处理超过 50,000 个字符串。我创建了一个扩展数组容器,因此我可以在不强制调整大小和移动的情况下任意添加元素,并且我使用取消引用比较谓词按字母顺序排列的std::string*std::set 对它们进行索引:谁能做得更好? 15 个字符串比较似乎很多。

【问题讨论】:

  • “我情不自禁地觉得这太不理想了” - 有时你可能会对乍一看似乎效率低下的算法的速度感到非常惊讶。也许这不是你的情况,但我想发布一个关于避免过早优化的免责声明:)
  • 是的,您正在进行 (max) 15 次字符串比较,但您不会经常达到这个数字,其中许多只能比较一两个字符.
  • 为什么要使用数组,为什么不只使用std::set<std::string>
  • 也许 trie 是你想要的,但我不知道 C++ 中的标准实现。
  • 我同意 Dukeling 的观点,有一个额外的 Array 似乎没有必要,因为 std::set 不会使迭代器无效。 hash_set 也是如此(值得一试)。恕我直言,使用集合并不是过早的优化,它很聪明,因为 1. 它具有正确的语义。 2. 比较有效率。

标签: c++ sorting search stl containers


【解决方案1】:

要摆脱setO(log n) 性能,您可以使用unordered_set,它使用散列(并且是O(1))(或hash_set,基本上相同,但仅受某些编译器支持)。

鉴于您正在进行(最多)15 次字符串比较,您不会一直达到这个最大值,其中许多只能比较一两个字符,很有可能为 unordered_set 生成哈希(以及处理哈希冲突)比在set 中找到值需要更长的时间。

另外,为什么不去掉数组而只使用std::set<std::string> 呢?你仍然可以返回一个引用:

const string& add_new_entry(const string& str)
{
    set<string>::iterator iter = yourSet.find(str);
    if (iter == yourSet.end())
      return *yourSet.insert(str).first;
    return *iter;
}

Test.

【讨论】:

  • 注意:实际上,在完全平衡的二叉搜索树上,您执行log2(N) 比较的次数约为 50%;所以我不会说经常没有达到 15...
  • insert NOT 使迭代器无效是我记错的信息。如果 unordered_set 导致重新散列,则插入时的迭代器会无效,所以我会坚持使用 set。谢谢。顺便说一句,你为什么要删掉我的签名?
  • @hatcat 为什么使迭代器失效很重要?即使迭代器失效,对字符串的引用仍然有效。是的,我做到了。 "Thanks" is ... clutter and does not need to be in the post。还有this.
  • @hatcat It appears that set isn't invalidated on insert/erase。但是you're perfectly right,我不知道为什么会这样,但保持参照完整性并不难。
【解决方案2】:

优化总是可能的,有时也非常值得,但对于 50,000 个条目,我猜它可能没有必要。假设它实际上是必要的,你可以尝试一些事情。

首先,如果某些词条比其他词条更常用,您可以将它们存储在单独的流行词词典中,然后先搜索。要查看这是否值得,请针对每个字典条目存储一个计数器,每次访问条目时将其递增,并在较长的测试期间查看这些计数器。

另一个值得拥有的是一个固定大小的字典数组,比如 26^3 = 17576,其中条目的前三个字母用于选择要搜索的字典。对于三个字母或更少的单词,这会将您降低到 o(1),并大大减少您搜索剩余条目的时间。

【讨论】:

    【解决方案3】:

    我可能只使用std::set,可能会将其迭代器包装在一个小类中检查是否失效,因此您可以保留迭代器而不是指针。

    不要过早优化。您是否分析了该代码?您是否 100% 确定 是瓶颈?

    【讨论】:

    • 投反对票的人有勇气表态并说出原因吗?
    • OP 已经提到使用std::set,所以这可能更适合作为评论。如果你想说"wrapping its iterator in a small class checking for invalidation" 之类的话,你必须解释一下这是什么意思(或者可能只是我)。
    【解决方案4】:

    std::hash_set 我想是要走的路

    【讨论】:

      【解决方案5】:

      使用地图。您不必搜索您的数组/列表。

      【讨论】:

      • 他应该在地图的第二个值中存储什么?
      猜你喜欢
      • 2020-06-01
      • 1970-01-01
      • 2023-03-12
      • 1970-01-01
      • 2013-10-06
      • 1970-01-01
      • 1970-01-01
      • 2020-01-12
      • 2010-09-27
      相关资源
      最近更新 更多