Knuth-Morris-Pratt字符串搜索算法:不含DFA的版本

2021-01-22 19:48:36

unsigned search_ok(char * s,unsigned len){如果(len< 2)返回len; //找不到(unsigned i = 0; i

您会看到,输入字符串的每个字符有两个内存访问权限,因此,最坏情况下所有内存访问的总数可能为len * 2。

可以减少这个数字吗?是。在以下示例中,每个字符只有一个内存访问权限:

unsigned search_ok(char * s,unsigned len){如果(len< 2)返回len; //找不到bool seen_o = false; for(unsigned i = 0; i< len-1; i ++){char ch = s [i]; //如果(ch ==' o')seen_o = true;这是一次读取操作否则(seen_o& ch ==' k')返回i-1; //找到其他seen_o = false; // 重启 }; len返回// 未找到};

我们可以将该方法扩展到任何3个字符的字符串吗?让我们编写一个搜索鳗鱼的函数。字符串。(对于非英语使用者:这确实是一个词。有些鱼。)

我们将使用“已见”变量,反映了我们已经看到的所查找字符串的多少个字符:

unsigned search_eel(c​​har * s,unsigned len){如果(len< 3)返回len; //未找到unsigned seen = 0; for(unsigned i = 0; i

但是此实现存在一个错误:要找到它,我将使用出色的工具CBMC,该工具可以验证C函数是否等效于另一个函数(要了解有关验证的更多信息,请参见SAT / SMT示例)。

unsigned search_eel_brute(char * s,unsigned len){如果(len< 3)返回len; //找不到(unsigned i = 0; i< len-2; i ++){如果(s [i] ==' e'&&s s [i + 1] ==&# 39; e&& s s [i + 2] ==' l')返回i; }; len返回//未找到}; ...避免check(){unsigned len = LEN; char s [len]; __CPROVER_assert(search_eel_brute(s,len)== search_eel(s,len)," assert");};

%cbmc --trace --function check -DLEN = 4 kmp_eel2.c ... **结果:[check.assertion.1]断言:FAILURETrace for check.assertion.1:...状态21文件kmp_eel2.c行41功能检查线程0 --------------------------------------------- ------- s = {' e&#39 ;、' e&#39 ;、' e&#39 ;、' l' }({01100101、01100101、01100101、01101100})...

经过一番思考后,我们发现一个问题,如果第三个字符不是' e' ;,我们处于一长串' e'的中间字符。因此,如果看到== 2并且输入的字符不是' l,而是" e&#39 ;,则我们不应该提前' seen&# 39;变量:

...如果(seen == 0&&ch; ==' e')看到= 1;否则,如果(seen == 1&&ch; ==' e')see = 2;否则,如果(seen == 2&&ch; ==' l')返回i-2; //发现是否为(seen == 2&& ch ==' e')// //修复seen = 2; //修复else see = 0; // 重启...

#!/ bin / bashfor i in $(seq 0 15);做#echo $ i cbmc --trace --function check -DLEN = $ i kmp_eel2.cdone

" cocos" substring的问题要多得多。但是在CBMC的帮助下进行修复是一项很棒的编程工作。

unsigned search_cocos_brute(char * s,unsigned len){如果(len< 5)返回len; //找不到(unsigned i = 0; i< len-4; i ++){如果(s [i] ==' c'&& s [i + 1] ==&# 39; o& s s [i + 2] ==' c'& s s [i + 3] ==' o'& s [i + 4] ==' s')返回i; //找到}; len返回//找不到}}; unsigned search_cocos_naive(char * s,unsigned len){如果(len< 5)返回len; //未找到unsigned seen = 0; for(unsigned i = 0; i

%cbmc --trace --function check -DLEN = 6 kmp_cocos.c ... **结果:[check.assertion.1]断言:FAILURETrace for check.assertion.1:...陈述21个文件kmp_cocos.c行47功能检查线程0 --------------------------------------------- ------- s = {' c&#39 ;,' c&#39 ;,' o&#39 ;,' c&#39 ;,' o',' }({01100011、01100011、01101111、01100011、01101111、01110011})...

解决方法是:如果第一个' c'重复一遍,我们不应该推进看到的变量:

如果(seen == 0&&ch; ==' c')看到= 1;否则,如果(seen == 1&&ch; ==' o')看到= 2; else if(seen == 1&& ch!=' o'){//如果输入为' ccocos'如果(ch ==' c')看到= 1;否则看到= 0; }否则,如果(seen == 2&& ch ==' c')看到= 3;否则,如果(seen == 3& ch ==' o')看到= 4;否则(seen == 4&&ch; ==' s')返回i-4; //发现其他人看过= 0; // 重启

CBMC可以针对所有6个字符的字符串验证此功能,但是可以找到有问题的7个字符的字符串:

%cbmc --trace --function check -DLEN = 7 kmp_cocos.c ... **结果:[check.assertion.1]断言:FAILURETrace for check.assertion.1:...陈述21个文件kmp_cocos.c行79功能检查线程0 --------------------------------------------- ------- s = {' c&#39 ;、' o&#39 ;、' c&#39 ;、' o&#39 ;、' c',o',' s }({01100011,01101111,01100011,01101111,01100011,01101111,01110011})...

...否则,如果(seen == 4&&ch; ==' s')返回i-4; //发现是否为if(seen == 4&& ch!=' s'){//输入字符串为' cocoX'其中X不是//(ch =' X'的当前状态)//但' X'可能是' c'如果输入字符串是' cococos' if(ch ==' c'){//如果字符串是' cococos&#39 ;, //我们可以说我们已经看到了' coc'部分内容:see = 3; } else {//' X'不是&c&#39 ;,因此重置为= 0; }; } else see = 0; // 重启...

%cbmc --trace --function check -DLEN = 8 kmp_cocos.c ... **结果:[check.assertion.1]断言:FAILURETrace for check.assertion.1:...陈述21个文件kmp_cocos.c行96功能检查线程0 --------------------------------------------- ------- s = {' c&#39 ;、' o&#39 ;、' c&#39 ;、' c&#39 ;、' o',',o',' s }({01100011、01101111、01100011、01100011、01101111、01100011、01101111、01110011})...

它因" coccocos"失败。我们必须为重复的第二个&#39c'添加另一张支票字符。

...否则,如果(seen == 3&&ch; ==' o')看到= 4;否则if(seen == 3&& ch!=' o'){// if input =' coccocos'如果(ch ==' c')看到= 1;否则看到= 0; // reset} else if(seen == 4&&ch; ==' s')...

现在CBMC最多可以检查所有15个字符的字符串。整个固定功能为:

unsigned search_cocos_fixed(char * s,unsigned len){如果(len< 5)返回len; //未找到unsigned seen = 0; for(unsigned i = 0; i

它能够搜索' cocos'子字符串仅读取一次输入字符串的每个字符,并由CBMC正式验证。

像这样的代码很难测试(您可以使用所有15个字符的输入字符串来执行这些功能吗?),但是由于有了CBMC,我们可以确保它是正确的,或者至少等同于简单的# 39; bruteforce'版本。没有它,我无法设计出正确的版本。实际上第一个版本是用Python编写的,我将其重写为纯C语言,以便可以使用CBMC对其进行验证。