检查给定字符串是不是等效于给定字符串集中的至少一个字符串的有效方法
Posted
技术标签:
【中文标题】检查给定字符串是不是等效于给定字符串集中的至少一个字符串的有效方法【英文标题】:Efficient way to check if a given string is equivalent to at least one string in the given set of strings检查给定字符串是否等效于给定字符串集中的至少一个字符串的有效方法 【发布时间】:2014-01-14 12:08:33 【问题描述】:给定一组字符串,比如"String1", "String2",..., "StringN"
,在C++ 中确定(返回true
或false
)给定string s
是否匹配上述集合中的任何字符串的最有效方法是什么?
Boost.Regex 可以用于这个任务吗?
【问题讨论】:
对集合进行排序并运行二分搜索。 你想这样做一次还是多次? 如果您正在寻找简单(精确)匹配,正则表达式会增加不必要的开销。 我需要将某个数组中的每个字符串元素与集合进行比较 - 所以.. 很多次 many有不同的度量,你的意思是多少?要检查的集合中有多少个字符串? 【参考方案1】:std::unordered_set
将提供最有效的查找(摊销常数时间)。
#include <unordered_set>
#include <string>
#include <cassert>
int main()
std::unordered_set<std::string> s = "Hello", "Goodbye", "Good morning";
assert(s.find("Goodbye") != s.end());
assert(s.find("Good afternoon") == s.end());
return 0;
【讨论】:
【参考方案2】:您可以将所有字符串放在std::set 中,然后检查该字符串是否在集合中。
【讨论】:
【参考方案3】:在您经常需要执行这些检查的某些情况下,可以从Interning 获得很大的性能改进。
实习仍然需要我们有一些字符串查找数据结构,比如树或哈希表。但是,我们很少执行这些繁重的查找:具体而言,我们仅在某些原始文本输入从环境到达我们的软件系统时才执行它们。
那时,我们把输入的文本当作一个字符串,intern它:在现有的字符串集合中查找它,然后将它转换为一个原子。原子是数据的小单位,通常是单个字的数量,例如机器指针。如果字符串不存在,interning 函数会为我们提供一个新的、唯一的原子。否则,它会为我们提供与之前为该字符串提供的相同原子。
一旦我们将输入字符串嵌入到原子中,我们总是使用原子来代替它们。所以我们不是比较两个字符串是否相同,而是比较两个原子是否相同,这是一种“快得令人眼花缭乱”的单字比较操作。 (当我们需要以可读的方式打印原子时,我们仍然使用字符串:再次在我们的系统和外部世界之间的边界处)。
Interning 来自 Lisp:在 Lisp 中,符号是原子。在原始源代码中,它们是文本的,因此当代码被读入内存时,符号名称会被内部生成原子,这些原子基本上是指向符号对象的指针。
实习出现在其他地方,例如 X Window 系统(XInternAtom
函数):
Atom XInternAtom(Display *display, char *atom_name, Bool only_if_exists)
在Microsoft Windows API 中未使用术语“实习生”,但该函数返回称为ATOM
的内容:Lisp 术语。实习的不是简单的字符串,而是“类”结构:
ATOM WINAPI RegisterClass(const WNDCLASS *lpWndClass);
在这两个系统中,这些原子是系统范围的(在 X 的情况下是服务器范围的),并且可以在它们所代表的对象的位置进行相等性比较。在 Windows 中,如果您有两个相等的 ATOM
值,则它们是同一个类。
GoF 书中的Flyweight Design Pattern 本质上是对实习的再发明,扩展到字符串以外的结构(如上述 Win32 API 中的WNDCLASS
);所以如果你想把这个想法“推销”给你的老板,你可以从这个角度来看待它。
【讨论】:
【参考方案4】:另一种方法是构造一棵 N 叉树来存储所有字符串。
节点看起来像:
struct Node
Node* children[256]; // Or reduce to correct accepted char
bool isAWord; // true when the letters from the root forms a word
所以,对于"String1"
、"String10"
、"String2"
、"StringN"
,树是:
Root
|
[S]
|
[t]
|
[r]
|
[i]
|
[n]
|
[g]
/ | \
[1*] [2*] [N*]
|
[0*]
一旦你有了你的树,查看它以查看字符串是否匹配。 搜索复杂度:要搜索的字符串的大小。
【讨论】:
通常组合comman spans更有效,这样你就可以拥有"String"
,然后指向1,2,N
。这增加了局部性,但更难编写。
@GlennTeitelbaum:这是Dietmar Kühl 的回答提出的Trie 结构。
我相信从技术上讲,您的解决方案也是Trie
,主要是因为它使用平等而不是<
或>
来决定正确的孩子【参考方案5】:
作为替代方案,您可以定义一个有序的字符数组或字符串数组,并使用标准算法 std::binary_search、std::lower_bound、std::upper_bound 或 std::equal_range 来检查目标字符串是否存在于大批。
我将使用此处已显示的示例。
#include <algorithm>
#include <iterator>
#include <string>
#include <iostream>
#include <iomanip>
int main()
const char * s[] = "Good morning", "Goodbye", "Hello" ;
std::cout << std::boolalpha
<< std::binary_search( std::begin( s ), std::end( s ), std::string( "Goodbye" ) )
<< std::endl;
std::cout << std::boolalpha
<< std::binary_search( std::begin( s ), std::end( s ), std::string( "Good afternoon" ) )
<< std::endl;
return 0;
【讨论】:
【参考方案6】:假设字符串不是完全随机的并且我有共同的前缀,使用Trie 可能更有效:最初构建数据结构可能比创建其他容器更昂贵,但如果有很多查询针对这可以得到回报的字符串集。主要缺点是标准C++库中没有trie实现。
【讨论】:
以上是关于检查给定字符串是不是等效于给定字符串集中的至少一个字符串的有效方法的主要内容,如果未能解决你的问题,请参考以下文章