我有一个C ++中大约有数百个唯一字符串的列表,我需要检查此列表中是否存在值,但最好是快速闪电。
我当前正在使用带有std :: strings的hash_set(因为我无法使用const char *),如下所示:
stdext::hash_set<const std::string> _items;
_items.insert("LONG_NAME_A_WITH_SOMETHING");
_items.insert("LONG_NAME_A_WITH_SOMETHING_ELSE");
_items.insert("SHORTER_NAME");
_items.insert("SHORTER_NAME_SPECIAL");
stdext::hash_set<const std::string>::const_iterator it = _items.find( "SHORTER_NAME" ) );
if( it != _items.end() ) {
std::cout << "item exists" << std::endl;
}
在没有自己构建完整哈希表的情况下,是否有其他人对更快的搜索方法有好主意?
列表是一个固定的字符串列表,不会改变。它包含受特定错误影响的元素名称列表,并且在使用较新版本打开时应该即时修复。
我在使用Aho-Corasick之前已经构建了哈希表,但我并不是真的愿意增加过多的复杂性。
我对答案的数量感到惊讶。我最后测试了几种方法来表现他们的表现,最后结合了kirkus和Rob K.的答案。我之前尝试过二分搜索,但我想我实现了一个小错误(它有多难......)。
令人震惊的结果......我以为我有一个使用hash_set的快速实现......好吧,最后我没有。这是一些统计数据(以及最终的代码):
随机查找5个现有密钥和1个不存在密钥,50.000次
我原来的算法平均 18,62 秒 lineair搜索平均 2,49 秒 二进制搜索平均 0,92 秒 使用由gperf生成的完美哈希表的搜索平均 0,51 秒。
这是我现在使用的代码:
bool searchWithBinaryLookup(const std::string& strKey) {
static const char arrItems[][NUM_ITEMS] = { /* list of items */ };
/* Binary lookup */
int low, mid, high;
low = 0;
high = NUM_ITEMS;
while( low < high ) {
mid = (low + high) / 2;
if(arrAffectedSymbols[mid] > strKey) {
high = mid - 1;
}
else if(arrAffectedSymbols[mid] < strKey) {
low = mid + 1;
}
else {
return true;
}
}
return false;
}
注意:这是Microsoft VC ++,所以我没有使用SGI的std :: hash_set。
我今天早上做了一些测试,使用gperf作为VardhanDotNet建议,这确实快得多。
答案 0 :(得分:10)
如果您的字符串列表在编译时是固定的,请使用gperf http://www.gnu.org/software/gperf/ 引用: gperf是一个完美的哈希函数生成器。对于给定的字符串列表,它以C或C ++代码的形式生成散列函数和散列表,用于根据输入字符串查找值。哈希函数是完美的,这意味着哈希表没有冲突,哈希表查找只需要一个字符串比较。
gperf的输出不受gpl或lgpl,afaik的控制。
答案 1 :(得分:6)
如果标准容器都不符合您的需求,您可以尝试使用PATRICIA Trie。
最坏情况查找受到您正在查找的字符串长度的限制。此外,字符串共享公共前缀,因此它在内存中非常容易。所以如果你有很多相对较短的字符串,这可能是有益的。
注意:PATRICIA =检索以字母数字编码的信息的实用算法
答案 2 :(得分:4)
std :: vector有什么问题?加载它,排序(v.begin(),v.end())一次,然后使用lower_bound()查看字符串是否在向量中。在排序的随机访问迭代器上,lower_bound保证为O(log2 N)。如果值是固定的,我无法理解哈希的必要性。向量占用内存的空间少于散列,并且分配的次数更少。
答案 3 :(得分:3)
如果是固定列表,请对列表进行排序并进行二分查找?我无法想象,在现代CPU上只有一百个左右的字符串,你真的会看到算法之间有任何明显的差异,除非你的应用程序什么都不做,只是在100%的时间内搜索列表。
答案 4 :(得分:2)
我怀疑你是否想出更好的哈希表;如果名单不时变化,你可能会得到最好的方式。
最快的方法是构建一个有限状态机来扫描输入。我不确定最好的现代工具是什么(自从我在实践中做了这样的事情已经十多年了),但Lex / Flex是标准的Unix构造函数。
FSM有一个状态表和一个接受状态列表。它从开始状态开始,并对输入进行逐字符扫描。每个州都有一个输入每个可能的输入字符。该条目可以是进入另一个状态,也可以是中止,因为该字符串不在列表中。如果FSM到达输入字符串的末尾而没有中止,它会检查它所处的最终状态,这是一个接受状态(在这种情况下你匹配了字符串)或者它不是(在这种情况下你是避难所) “T)。
任何关于编译器的书都应该有更多细节,或者你无疑可以在网上找到更多信息。
答案 5 :(得分:1)
如果用一组字符串来检查数百个数字,就像你说的那样,这是在进行I / O(加载一个文件,我认为它通常来自磁盘),那么我会说:profile在寻找更具异国情调/复杂的解决方案之前,你已经得到了什么。
当然,你的“文档”可能包含数亿个这些字符串,在这种情况下我猜它真的需要时间......没有更多细节,很难肯定地说。
我所说的归结为“在优化之前考虑用例和典型场景”,我想这只是关于邪恶根源的旧事物的专门化...... :) p>
答案 6 :(得分:1)
100个独特的字符串?如果没有频繁调用,并且列表没有动态更改,我可能会使用带有线性搜索的直接const char数组。除非你经常搜索,否则小的东西不值得额外的代码。像这样:
const char _items[][MAX_ITEM_LEN] = { ... };
int i = 0;
for (; strcmp( a, _items[i] ) < 0 && i < NUM_ITEMS; ++i );
bool found = i < NUM_ITEMS && strcmp( a, _items[i] ) == 0;
对于一个小的列表,我认为你的实施和维护成本更复杂可能会超过运行时间成本,而你真的不会比这更便宜的空间成本。为了获得更快的速度,你可以做一个第一个字符的哈希表 - &gt; list index设置i的初始值;
对于这个小的列表,你可能不会更快。
答案 7 :(得分:0)
我不知道MS使用哪种散列函数进行叮咬,但也许你可以想出一些更简单(=更快)的东西,它适用于你的特殊情况。容器应该允许您使用自定义散列类。
如果这是容器的实施问题,您也可以尝试使用std::tr1::unordered_set提高效果。
答案 8 :(得分:0)
哈希表是一个很好的解决方案,通过使用预先存在的实现,您可能会获得良好的性能。我认为另一种选择叫做“索引”。
对方便的位置保持一些指示。例如如果它使用字母进行排序,保持一个指向所有开始aa,ab,ac ... ba,bc,bd的指针...这是几百个指针,但意味着你可以跳到列表的一部分,这是在继续之前非常接近结果。例如如果一个条目是“afunctionname”那么你可以在af和ag的指针之间进行二进制搜索,比搜索整个批次要快得多......如果你总共有一百万条记录你可能只需要二进制搜索一个列表几千人。
我重新发明了这个特定的轮子,但是可能已经有很多实现,这将为您节省实施的麻烦,并且可能比我在此处粘贴的任何代码更快。 :)
答案 9 :(得分:0)
您正在使用二进制搜索,即O(log(n))。您应该查看插值搜索,这不是一个好的“最坏情况”,但它的平均情况更好:O(log(log(n))。
答案 10 :(得分:0)
我切割&amp;粘贴上面的二进制搜索代码...原始二进制搜索代码存在问题,例如:在100个项目的列表中找不到第2个项目。
该行:
high = mid - 1;
应该是:
high = mid;