C ++ - 用于大量可搜索数据的高效容器?

时间:2010-04-28 14:00:40

标签: c++ string search performance

我正在为大学项目实施基于文本的Scrabble版本。

我的词典非常庞大,重约400,000字(std::string)。

如果我选择vector<string>(O(n)),那么在效率方面寻找一个有效的单词会很麻烦。有什么好的选择吗?请记住,我已入读大一新生。没什么太复杂了!

谢谢你的时间!

旧金山

9 个答案:

答案 0 :(得分:23)

如果您想要使用标准库中的某些内容,可以使用std::set并将该单词作为键。这会给你对数搜索时间。

由于您的字典可能是静态的(即创建一次而未修改),您也可以使用std::vector,使用std::sort对其进行排序,然后在排序后的矢量上使用std::binary_search找个单词。这也会给出对数搜索时间,并且可能比set更节省空间。

如果你想实现自己的数据结构,trie将是一个不错的选择。

答案 1 :(得分:9)

std :: set对此很自然,因为在使用向量时几乎需要0才能完成。即便如此,我会教你一些你通常不会学习的东西,直到你成为一名专业人士。不要过早优化。我在一台计算机上打赌,40K字符串向量中的线性字典查找需要0.001秒。

在一个集合中,它是O(log n),可能需要.00001秒。

任何不在STL中的东西都是浪费时间。不要花费10美元的工作来解决10美分的问题。

答案 2 :(得分:6)

trieradix tree会为您提供搜索时间和插入时间,这些时间和插入时间与您要搜索的字符串的长度成线性关系。

(请注意,您搜索的字符串长度的线性是您可以使用任何搜索算法的最佳线性,因为比较或散列字符串在字符串的长度上是线性的 - 因此是运行时间的组成部分字符串长度中的线性通常不在二进制搜索,二叉树或线性搜索的运行时间之外。)

如果您的图书馆中没有这些解决方案,这些解决方案可能有点过分。

答案 3 :(得分:6)

我做了一些分析并获得了以下结果(MSVS 2008,/ O2,发布版本,单独启动.exe)。

编辑 - 现在我意识到我实际上已经完成了第一次测试,因为我没有拆分建筑和搜索测试。虽然它没有改变“赢家”,但我做了一些新的测试。所以,这是我们拆分时的结果。

首先,如果几乎没有糟糕的搜索请求(400万次搜索好搜索)

[ RUN      ] Containers.DictionaryPrepare
[       OK ] Containers.DictionaryPrepare (234 ms)
[ RUN      ] Containers.VectorPrepare
[       OK ] Containers.VectorPrepare (704 ms)
[ RUN      ] Containers.SetPrepare
[       OK ] Containers.SetPrepare (593 ms)
[ RUN      ] Containers.MultisetPrepare
[       OK ] Containers.MultisetPrepare (578 ms)
[ RUN      ] Containers.UnorderedSetPrepare
[       OK ] Containers.UnorderedSetPrepare (266 ms)
[ RUN      ] Containers.UnorderedMultisetPrepare
[       OK ] Containers.UnorderedMultisetPrepare (375 ms)
[ RUN      ] Containers.VectorSearch
[       OK ] Containers.VectorSearch (4484 ms)
[ RUN      ] Containers.SetSearch
[       OK ] Containers.SetSearch (5469 ms)
[ RUN      ] Containers.MultisetSearch
[       OK ] Containers.MultisetSearch (5485 ms)
[ RUN      ] Containers.UnorderedSet
[       OK ] Containers.UnorderedSet (1078 ms)
[ RUN      ] Containers.UnorderedMultiset
[       OK ] Containers.UnorderedMultiset (1250 ms)
[----------] 11 tests from Containers (20516 ms total)

此分析表明您应该使用“普通”容器变体而不是“multi”,并且应该选择unordered_set。它非常适合构建时间和搜索操作时间。

以下是另一个案例的结果(猜测,这不是关于你的应用程序,只是为了它),当不良搜索量等于好搜索量(并且等于200万)。获胜者保持不变。

另请注意,对于静态词典vector执行效果更好(虽然需要更多时间进行初始化),而不是set,但是,如果必须添加元素,则会很糟糕。< / em>的

[ RUN      ] Containers.DictionaryPrepare
[       OK ] Containers.DictionaryPrepare (235 ms)
[ RUN      ] Containers.VectorPrepare
[       OK ] Containers.VectorPrepare (718 ms)
[ RUN      ] Containers.SetPrepare
[       OK ] Containers.SetPrepare (578 ms)
[ RUN      ] Containers.MultisetPrepare
[       OK ] Containers.MultisetPrepare (579 ms)
[ RUN      ] Containers.UnorderedSetPrepare
[       OK ] Containers.UnorderedSetPrepare (265 ms)
[ RUN      ] Containers.UnorderedMultisetPrepare
[       OK ] Containers.UnorderedMultisetPrepare (375 ms)
[ RUN      ] Containers.VectorSearch
[       OK ] Containers.VectorSearch (3375 ms)
[ RUN      ] Containers.SetSearch
[       OK ] Containers.SetSearch (3656 ms)
[ RUN      ] Containers.MultisetSearch
[       OK ] Containers.MultisetSearch (3766 ms)
[ RUN      ] Containers.UnorderedSet
[       OK ] Containers.UnorderedSet (875 ms)
[ RUN      ] Containers.UnorderedMultiset
[       OK ] Containers.UnorderedMultiset (1016 ms)
[----------] 11 tests from Containers (15438 ms total)

测试代码:

TEST(Containers, DictionaryPrepare) {
   EXPECT_FALSE(strings_initialized);
   for (size_t i = 0; i < TOTAL_ELEMENTS; ++i) {
      strings.push_back(generate_string());
   }
}

TEST(Containers, VectorPrepare) {
   for (size_t i = 0; i < TOTAL_ELEMENTS; ++i) {
      vec.push_back(strings[i]);
   }
   sort(vec.begin(), vec.end());
}

TEST(Containers, SetPrepare) {
   for (size_t i = 0; i < TOTAL_ELEMENTS; ++i) {
      set.insert(strings[i]);
   }
}

TEST(Containers, MultisetPrepare) {
   for (size_t i = 0; i < TOTAL_ELEMENTS; ++i) {
      multiset.insert(strings[i]);
   }
}

TEST(Containers, UnorderedSetPrepare) {
   for (size_t i = 0; i < TOTAL_ELEMENTS; ++i) {
      uo_set.insert(strings[i]);
   }
}

TEST(Containers, UnorderedMultisetPrepare) {
   for (size_t i = 0; i < TOTAL_ELEMENTS; ++i) {
      uo_multiset.insert(strings[i]);
   }
}

TEST(Containers, VectorSearch) {
   for (size_t i = 0; i < TOTAL_SEARCHES; ++i) {
      std::binary_search(vec.begin(), vec.end(), strings[rand() % TOTAL_ELEMENTS]);
   }
   for (size_t i = 0; i < TOTAL_BAD_SEARCHES; ++i) {
      std::binary_search(vec.begin(), vec.end(), NONEXISTENT_ELEMENT);
   }
}

TEST(Containers, SetSearch) {
   for (size_t i = 0; i < TOTAL_SEARCHES; ++i) {
      set.find(strings[rand() % TOTAL_ELEMENTS]);
   }
   for (size_t i = 0; i < TOTAL_BAD_SEARCHES; ++i) {
      set.find(NONEXISTENT_ELEMENT);
   }
}

TEST(Containers, MultisetSearch) {
   for (size_t i = 0; i < TOTAL_SEARCHES; ++i) {
      multiset.find(strings[rand() % TOTAL_ELEMENTS]);
   }
   for (size_t i = 0; i < TOTAL_BAD_SEARCHES; ++i) {
      multiset.find(NONEXISTENT_ELEMENT);
   }
}

TEST(Containers, UnorderedSet) {
   for (size_t i = 0; i < TOTAL_SEARCHES; ++i) {
      uo_set.find(strings[rand() % TOTAL_ELEMENTS]);
   }
   for (size_t i = 0; i < TOTAL_BAD_SEARCHES; ++i) {
      uo_set.find(NONEXISTENT_ELEMENT);
   }
}

TEST(Containers, UnorderedMultiset) {
   for (size_t i = 0; i < TOTAL_SEARCHES; ++i) {
      uo_multiset.find(strings[rand() % TOTAL_ELEMENTS]);
   }
   for (size_t i = 0; i < TOTAL_BAD_SEARCHES; ++i) {
      uo_multiset.find(NONEXISTENT_ELEMENT);
   }
}

答案 4 :(得分:3)

您的数据结构的目的是什么?你想用它做什么事?

  • 检查列表中是否有“tempest”这样的完整单词?
  • 查找以“t”开头和结尾的所有七个字母的单词?
  • 查找以“t”开头和结尾的所有七个字母单词,可以用一组给定的字母制作吗?

如果你正在为一组人类玩家实施某种裁判,那么第一个问题就是你所需要的,也就是说,某个实体会根据官方字典检查提议的单词。其他人已经建议的std::mapstd::setstd::vector等基本数据结构本身就足以满足此目的。

第二个和第三个问题是你在写一个玩家时需要回答的问题。在这里,您可能需要考虑每个字母位置的26个集合,每个集合在给定位置保存给定字母的单词。您需要额外的代码来在需要时计算交叉点,并且可能会根据机架上可用的字母检查单词。

更新:在对原始问题的评论中,OP明确表示他只需检查词典中是否有单词。这是我上面提到的第一个问题,任何标准的高效数据结构都可以。

答案 5 :(得分:2)

如果对矢量进行了排序,您可以使用binary_search来测试字典中是否存在给定的单词。

答案 6 :(得分:2)

使用std::tr1::unordered_set,这样可以为您提供持续的时间查询。 (根据我的其他答案,字符串的长度是线性的。)

答案 7 :(得分:2)

我会回应Ken关于使用Trie的建议,并进一步建议你可以通过让它更像是一个有限状态机的公共前缀和后缀来大大缩小trie的大小。例如
“民族”,
“国家”,
“国有化”,
“国有化”,
“国有化”,
“denationalize”,
都可以共享共同的结构。你不得不担心可疑的话,比如
“nationalizationalizationalizing”
我在很久以前就用过拼写修正程序了。

答案 8 :(得分:1)

我建议使用单词的长度和首字母作为搜索中的前两项。让数据组织起来以支持该算法。

首先,让我们为所有长度相同的单词定义一个容器:

typedef std::vector<std::string> Word_Container;

应对此数据结构进行排序,以便可以使用二进制搜索。

接下来,将创建一个索引表。索引表的格式为&lt; 字长,指向字容器的指针&gt;:

typedef std::map<unsigned int, Word_Container *> Index_Table;

最后有一个索引表数组,使用单词的第一个字母作为索引:

Index_Table alpha_array[26]; // ASCII A - Z.

然后算法:
将指数计算为alpha_array
    index = word[0] - 'A';

使用索引获取关联的索引表:
    Index_Table& table = alpha_array[index];

使用单词的长度作为表查找的键,得到单词容器:
    Word_Container * p_word_container = table[word.length()];

搜索容器中的确切字词:

bool found = false;
if (p_word_container)
{
    found = std::binary_search(p_word_container->begin(), p_word_container->end(), word);
}

有一种更有效但更复杂的搜索词典的方法。上述算法具有快速“逃避”点的好处,其中该词不存在。这个概念可以扩展到数据库表。