我有一个包含大量短字符串(不超过100个字符)的数据集(100k),我需要快速查找其中包含某个子字符串的所有字符串。
这将用作搜索框,用户开始输入文本后,系统会立即提供“建议”(即包含用户键入的文本作为子字符串的字符串)。类似于StackOverflow中的“标签”框。
由于这将是交互式的,所以速度应该相当快。你推荐使用什么算法或数据结构来完成这项任务?
顺便说一下,我将使用Delphi 2007。
谢谢!
我有一个包含大量短字符串(不超过100个字符)的数据集(100k),我需要快速查找其中包含某个子字符串的所有字符串。
这将用作搜索框,用户开始输入文本后,系统会立即提供“建议”(即包含用户键入的文本作为子字符串的字符串)。类似于StackOverflow中的“标签”框。
由于这将是交互式的,所以速度应该相当快。你推荐使用什么算法或数据结构来完成这项任务?
顺便说一下,我将使用Delphi 2007。
谢谢!
我写了一个长篇介绍,进行了大量的复杂度计算和Xzibit笑话(树中的树,因此您可以在查找时查找),但后来意识到这比我想象的要容易。浏览器经常这样做,而且每次加载页面时都不会预先计算大表。
http://en.wikipedia.org/wiki/Boyer%E2%80%93Moore_string_search_algorithm
它的意思是将你的100k字符串合并成一个长字符串。然后取出你的查询子串,并迭代你的大字符串,寻找匹配项。但你不是按字符跳转(这意味着你要查看100k*100次),而是按子串长度跳转,所以你的子串越长,速度越快。
下面是一个很好的例子:http://userweb.cs.utexas.edu/users/moore/best-ideas/string-searching/fstrpos-example.html
他们正在搜索字符串EXAMPLE。
这就是浏览器和文本编辑器所做的事情,它们实际上并不会为每个加载的页面构建巨大的前缀表。
你可能想要使用的数据结构是Trie,特别是后缀Trie。阅读这篇文章,了解它们是如何工作的以及如何为您的问题编写一个。
虽然使用更好的数据结构可以加快速度,但在某些情况下暴力搜索可能已经足够。我们来做一个快速测试:
[编辑:修改代码以搜索子字符串,并再次编辑以缩短要搜索的子字符串与要搜索的字符串之间的比较长度。]
#include <algorithm>
#include <iostream>
#include <vector>
#include <string>
#include <time.h>
std::string rand_string(int min=20, int max=100) {
size_t length = rand()% (max-min) + min;
std::string ret;
for (size_t i=0; i<length; i++)
ret.push_back(rand() % ('z' - 'a') + 'a');
return ret;
}
class substr {
std::string seek;
public:
substr(std::string x) : seek(x) {}
bool operator()(std::string const &y) { return y.find(seek) != std::string::npos; }
};
int main() {
std::vector<std::string> values;
for (int i=0; i<100000; i++)
values.push_back(rand_string());
std::string seek = rand_string(5, 10);
const int reps = 10;
clock_t start = clock();
std::vector<std::string>::iterator pos;
for (int i=0; i<reps; i++)
pos = std::find_if(values.begin(), values.end(), substr(seek));
clock_t stop = clock();
std::cout << "Search took: " << double(stop-start)/CLOCKS_PER_SEC/reps << " seconds\n";
if (pos == values.end())
std::cout << "Value wasn't found\n";
else
std::cout << "Value was found\n";
return 0;
}
在我的机器上(约4年前的机器——按现有标准几乎不算快),每次搜索运行大约10毫秒左右。这足够快,对于交互用户来说几乎是瞬间出现的——即使有10倍的字符串,也仍然很好。
O(m * logn)
,其中m
是搜索词的长度,n
是数据集的大小(在这种情况下为100kb)。t
中查找所有子字符串s
的出现。
Fernando的问题可以通过将初始字符串集合使用某个分隔符连接成一个字符串来简化。例如,如果初始集合是["text1", "text2", "some text"]
,则结果字符串t
将为"text1|text2|some text"
。"text"
,而只需在大字符串t
中查找所有出现次数即可。
我还建议参考Oren的答案,他提出了另一种现实可行的方法。