我知道几种基本的字符串匹配算法,例如KMP或Boyer-Moore,但是所有这些算法都在搜索之前就对模式进行了分析,但是,如果有一个字符,就没有太多要分析的了。那么,有什么比天真搜索更好的算法来比较文本的每个字符呢?
我知道几种基本的字符串匹配算法,例如KMP或Boyer-Moore,但是所有这些算法都在搜索之前就对模式进行了分析,但是,如果有一个字符,就没有太多要分析的了。那么,有什么比天真搜索更好的算法来比较文本的每个字符呢?
Answers:
可以理解,最坏的情况是O(N)
,存在一些非常好的微优化。
天真的方法对每个字符执行字符比较和文本结尾比较。
使用前哨(即目标字符在文本末尾的副本)可将比较次数减少到每个字符1次。
在微博级别,有:
#define haszero(v) ( ((v) - 0x01010101UL) & ~(v) & 0x80808080UL )
#define hasvalue(x, n) ( haszero((x) ^ (~0UL / 255 * (n))) )
知道单词(x
)中的任何字节是否具有特定值(n
)。
v - 0x01010101UL
只要对应的in字节v
为零或大于,子表达式就对任何字节中的高位进行求值0x80
。
该子表达式~v & 0x80808080UL
计算以字节为单位设置的高位,其中的字节v
未设置其高位(因此该字节小于0x80
)。
通过对这两个子表达式(haszero
)进行“与”运算,结果是设置了高位,其中的字节v
为零,这是因为由于0x80
第二个子表达式(4月27日, 1987年,艾伦·迈克罗夫特(Alan Mycroft)。
现在,我们可以对一个要测试的值(x
)与一个用我们感兴趣的字节值填充的单词()进行异或n
。由于将值与自身进行XOR运算会导致字节为零,否则为非零,因此我们可以将结果传递给haszero
。
这通常在典型的strchr
实现中使用。
(Stephen M Bennet于2009年12月13日提出了这一建议。有关更多详细信息,请参见著名的Bit Twiddling Hacks)。
聚苯乙烯
对于
1111
旁边的的任何组合,此代码均无效0
hack通过了蛮力测试(请耐心等待):
#include <iostream>
#include <limits>
bool haszero(std::uint32_t v)
{
return (v - std::uint32_t(0x01010101)) & ~v & std::uint32_t(0x80808080);
}
bool hasvalue(std::uint32_t x, unsigned char n)
{
return haszero(x ^ (~std::uint32_t(0) / 255 * n));
}
bool hasvalue_slow(std::uint32_t x, unsigned char n)
{
for (unsigned i(0); i < 32; i += 8)
if (((x >> i) & 0xFF) == n)
return true;
return false;
}
int main()
{
const std::uint64_t stop(std::numeric_limits<std::uint32_t>::max());
for (unsigned c(0); c < 256; ++c)
{
std::cout << "Testing " << c << std::endl;
for (std::uint64_t w(0); w != stop; ++w)
{
if (w && w % 100000000 == 0)
std::cout << w * 100 / stop << "%\r" << std::flush;
const bool h(hasvalue(w, c));
const bool hs(hasvalue_slow(w, c));
if (h != hs)
std::cerr << "hasvalue(" << w << ',' << c << ") is " << h << '\n';
}
}
return 0;
}
对于答案的大量支持,使假设一个字符=一个字节,这已不再是标准
谢谢你的发言。
答案只不过是一篇关于多字节/可变宽度编码的文章:-)(公平地说,这不是我的专业领域,我不确定这是OP所寻找的东西)。
无论如何,在我看来,上述想法/技巧在某种程度上可以适用于MBE(尤其是自同步编码):
strchr
/的调用strstr
(例如GNUlib coreutils mbschr)0x01010101UL
一行一行地写~0UL / 255
。它给人的印象是它们必须是不同的值,否则,为什么要用两种不同的方式编写它?
#define
s会扩展为,因此它需要多条(8?)指令( (((x) ^ (0x01010101UL * (n)))) - 0x01010101UL) & ~((x) ^ (0x01010101UL * (n)))) & 0x80808080UL )
。单字节比较不会更快吗?
如果您需要多次搜索同一字符串中的字符,那么一种可行的方法是将字符串分成多个较小的部分(可能是递归的),并对每个部分使用Bloom过滤器。
因为Bloom筛选器可以肯定地告诉您字符是否不在该筛选器“表示”的字符串部分中,所以在搜索字符时可以跳过某些部分。
例如:对于以下字符串,可以将其分为4个部分(每个11个字符长),并为每个部分填充一个Bloom过滤器(可能长4个字节),并包含该部分的字符:
The quick brown fox jumps over the lazy dog
| | | |
您可以加快字符搜索的速度:例如,a
对于bloom过滤器使用好的哈希函数,它们会告诉您-非常有可能-您不必在第一部分,第二部分或第三部分中都进行搜索。因此,您不必检查33个字符,而只需要检查16个字节(对于4个Bloom过滤器)。仍然O(n)
是一个常数(分数)因子(为了使其有效,您需要选择更大的部分,以最小化为搜索字符计算哈希函数的开销)。
使用类似树的递归方法应该可以使您接近O(log n)
:
The quick brown fox jumps over the lazy dog
| | | | | | | |---|-X-| | (1 Byte)
| | | |---X---|---- (2 Byte)
| |-----X------ (3 Byte)
-------------------------------|-----X------ (4 Byte)
---------------------X---------------------| (5 Byte)
在此配置中,需要检查(再次,假设我们很幸运并且没有从其中一个过滤器中得到误报)进行检查
5 + 2*4 + 3 + 2*2 + 2*1 bytes
进入最后一部分(其中需要检查3个字符,直到找到为止a
)。
使用良好的(如上所述更好的)细分方案,您应该会获得很好的结果。(注意:如示例所示,树的根处的Bloom过滤器应大于叶子附近的过滤器,以获得较低的误报率)
如果要多次搜索字符串(典型的“搜索”问题),则解决方案可以为O(1)。解决方案是建立索引。
例如:
映射,其中键是字符,值是字符串中该字符的索引列表。
这样,单个地图查找就可以提供答案。