Naive Pattern Searching

http://www.geeksforgeeks.org/searching-for-patterns-set-1-naive-pattern-searching/#disqus_thread


KMP:
http://www.geeksforgeeks.org/searching-for-patterns-set-2-kmp-algorithm/


Searching for Patterns | Set 1 (Naive Pattern Searching)

Given a text txt[0..n-1] and a pattern pat[0..m-1], write a function search(char pat[], char txt[]) that prints all occurrences of pat[] in txt[]. You may assume that n > m

Examples:
1) Input:

  txt[] =  "THIS IS A TEST TEXT"
  pat[] = "TEST"

Output:

Pattern found at index 10

2) Input:

  txt[] =  "AABAACAADAABAAABAA"
  pat[] = "AABA"

Output:

   Pattern found at index 0
   Pattern found at index 9
   Pattern found at index 13

Pattern searching is an important problem in computer science. When we do search for a string in notepad/word file or browser or database, pattern searching algorithms are used to show the search results. 

Naive Pattern Searching:
Slide the pattern over text one by one and check for a match. If a match is found, then slides by 1 again to check for subsequent matches. 

  • C
  • Python
# Python program for Naive Pattern Searching
def search(pat, txt):
     M = len (pat)
     N = len (txt)
 
     # A loop to slide pat[] one by one
     for i in xrange (N - M + 1 ):
 
         # For current index i, check for pattern match
         for j in xrange (M):
             if txt[i + j] ! = pat[j]:
                 break
         if j = = M - 1 : # if pat[0...M-1] = txt[i, i+1, ...i+M-1]
             print "Pattern found at index " + str (i)
 
# Driver program to test the above function
txt = "AABAACAADAABAAABAA"
pat = "AABA"
search (pat, txt)
 
# This code is contributed by Bhavya Jain


Output:
Pattern found at index 0 
Pattern found at index 9 
Pattern found at index 13 

What is the best case?
The best case occurs when the first character of the pattern is not present in text at all.

txt[]  = "AABCCAADDEE"
pat[] = "FAA"

The number of comparisons in best case is O(n). 

What is the worst case ?
The worst case of Naive Pattern Searching occurs in following scenarios.
1) When all characters of the text and pattern are same. 

txt[] = "AAAAAAAAAAAAAAAAAA"
pat[] = "AAAAA" .

2) Worst case also occurs when only the last character is different.

txt[] = "AAAAAAAAAAAAAAAAAB"
pat[] = "AAAAB"

Number of comparisons in worst case is O(m*(n-m+1)). Although strings which have repeated characters are not likely to appear in English text, they may well occur in other applications (for example, in binary texts). The KMP matching algorithm improves the worst case to O(n). We will be covering KMP in the next post. Also, we will be writing more posts to cover all pattern searching algorithms and data structures. 

Please write comments if you find anything incorrect, or you want to share more information about the topic discussed above.


串的匹配:朴素匹配&kmp算法

   
id="iframeu1160954_0" src="http://pos.baidu.com/acom?rdid=1160954&dc=2&di=u1160954&dri=0&dis=0&dai=2&ps=210x167&dcb=BAIDU_EXP_UNION_define&dtm=BAIDU_DUP_SETJSONADSLOT&dvi=0.0&dci=-1&dpt=none&tsr=173&tpr=1450590626805&ti=%E4%B8%B2%E7%9A%84%E5%8C%B9%E9%85%8D%EF%BC%9A%E6%9C%B4%E7%B4%A0%E5%8C%B9%E9%85%8D%26kmp%E7%AE%97%E6%B3%95--%E8%88%9E%E7%9A%84%E6%96%87%E7%AB%A0--CFANZ%E7%A4%BE%E5%8C%BA--IT%E6%8A%80%E6%9C%AF%E5%88%86%E4%BA%AB%E7%BD%91%E7%AB%99&ari=1&dbv=0&drs=1&pcs=1232x672&pss=1232x221&cfv=19&cpl=5&chi=1&cce=true&cec=UTF-8&tlm=1450561826&ltu=http%3A%2F%2Fwww.cfanz.cn%2Findex.php%3Fc%3Darticle%26a%3Dread%26id%3D180612&ecd=1&psr=1280x800&par=1280x734&pis=-1x-1&ccd=24&cja=true&cmi=45&col=en-us&cdo=-1&tcn=1450590627&exps=110211&qn=0b9d2b637fc4fa71&tt=1450590626741.186.316.316&feid=110211" width="728" height="15" align="center,center" vspace="0" hspace="0" marginwidth="0" marginheight="0" scrolling="no" frameborder="0" allowtransparency="true" style="margin: 0px; padding: 0px; border-width: 0px; vertical-align: bottom;">
id="iframeu1229945_0" src="http://pos.baidu.com/acom?rdid=1229945&dc=2&di=u1229945&dri=0&dis=0&dai=3&ps=230x167&dcb=BAIDU_EXP_UNION_define&dtm=BAIDU_DUP_SETJSONADSLOT&dvi=0.0&dci=-1&dpt=none&tsr=196&tpr=1450590626805&ti=%E4%B8%B2%E7%9A%84%E5%8C%B9%E9%85%8D%EF%BC%9A%E6%9C%B4%E7%B4%A0%E5%8C%B9%E9%85%8D%26kmp%E7%AE%97%E6%B3%95--%E8%88%9E%E7%9A%84%E6%96%87%E7%AB%A0--CFANZ%E7%A4%BE%E5%8C%BA--IT%E6%8A%80%E6%9C%AF%E5%88%86%E4%BA%AB%E7%BD%91%E7%AB%99&ari=1&dbv=0&drs=1&pcs=1232x672&pss=1232x240&cfv=19&cpl=5&chi=1&cce=true&cec=UTF-8&tlm=1450561826&ltu=http%3A%2F%2Fwww.cfanz.cn%2Findex.php%3Fc%3Darticle%26a%3Dread%26id%3D180612&ecd=1&psr=1280x800&par=1280x734&pis=-1x-1&ccd=24&cja=true&cmi=45&col=en-us&cdo=-1&tcn=1450590627&exps=110211&qn=cd88cd42fc68fa82&tt=1450590626741.197.320.320&feid=110211" width="336" height="280" align="center,center" vspace="0" hspace="0" marginwidth="0" marginheight="0" scrolling="no" frameborder="0" allowtransparency="true" style="margin: 0px; padding: 0px; border-width: 0px; vertical-align: bottom;">

引言

字符串的模式匹配是一种常用的操作。模式匹配(pattern matching),简单讲就是在文本(text,或者说母串str)中寻找一给定的模式(pattern)。通常文本都很大,而模式则比较短小。典型的例子如文本编辑和DNA分析。在进行文本编辑时,文本通常是一段话或一篇文章,而模式则常常是一个单词。若是对某个指定单词进行替换操作,则要在整篇文章中进行匹配,效率要求肯定是很高的。

模式匹配的朴素算法

最简单也最容易想到的是朴素匹配。何为朴素匹配,简单讲就是把模式串跟母串从左向右或从右向左一点一点比较:先把模式串的第一个字符同母串的第一个字符比较,若相等则接着比较后面的对应字符;若不等,把模式串后移一个位置,再次从模式串的头部比较……

这如同枚举法:把母串中与模式串相同长度的子串挨个比较。则这种匹配方式显然无任何启发性或智能性。如下图:

kmp

以上步骤很容易看懂,它的代码也各种各样,下面是其中一种:

/* 朴素的模式匹配算法,匹配方向:从前往后 匹配成功,则返回匹配成功时主串的下标位置(只返回第一次匹配成功的位置) 否则,返回-1 母串或子串有一个为空也返回-1 */ int naiveStringMatching(const char* T, const char* P) { if (T && P) { int i, j, lenT, lenP; lenT = strlen(T); lenP = strlen(P); //模式串的长度比主串还长,显然无法匹配 if (lenP > lenT) return -1; i = 0; while (i <= lenT-lenP) { j = 0; if (T[i] == P[j]) { j++; //指针的写法是这样的:while(j < lenP && *(T + i + j) == *P(j))j++; while (j < lenP && T[i + j] == P[j]) j++; //顺利匹配到了模式串的结尾,则匹配成功 if (j == lenP) return i; } i++; } //如果程序运行到这里,仍然没有结束,说明没有匹配上 return -1; } return -1; }

考虑到有时需要使用c++中的string类型,这时它的代码是这样的:

int naiveStringMatching(const string T, const string P) { int i, j, lenT, lenP; lenT = T.length(); lenP = P.length(); //串空或模式串的长度比主串还长,显然无法匹配 if (lenT == 0 || lenP == 0 || lenP > lenT) return -1; i = 0; while (i <= lenT - lenP) { j = 0; if (T[i] == P[j]) { j++; while (j < lenP && T[i + j] == P[j]) j++; if (j == lenP) return i; } i++; } return -1; }

不要小看上面的代码,虽然效率不高,但仍有掌握的必要。代码和算法总是在不断优化的,而这一切的优化都是从简单的情形开始的。

朴素匹配的时间复杂度是很容易分析的。若是成功匹配,最好的情况下,第一次比较就匹配上了,此时只需strlen(P)次(模式串的长度次)比较。最坏的情况下,一直需要比较到母串最后一个长度与模式串相同的子串,共(strlen(T)-strlen(P)+1)*strlen(P)次比较。平均下是O(strlen(T)*strlen(P))。


KMP算法

KMP算法是一种用于字符串匹配的算法,这个算法的高效之处在于当在某个位置匹配不成功的时候可以根据之前的匹配结果从模式字符串的另一个合适的位置开始,而不必每次都从头开始匹配。该算法由Knuth、Morris和Pratt三人设计,故取名为KMP。

KMP的改进之处

回顾一下上文讲的的朴素匹配算法,每次失配的时候,都是 i++;j=0; 从画面上看,就是把模式串相对于主串向后移动一个位置,再次从模式串的首字符开始新的一轮比较。用数学的形式讲,这是 i++;j=0; 的几何意义

失配时,记主串的下标为i,此时模式串的下标是j。则可以肯定已有j个字符成功匹配。如下图:

Ti-j Ti-j+1 ...Ti-2 Ti-1 Ti

P0  P1   ...Pj-2 Pj-1 Pj         图(a)

在上图中,红色的字符是匹配上的。下标 0,1..j-1 不正好是j个吗?

KMP的做法是,充分利用已匹配的信息,失匹配时:i不变,j=next[j],其中next[j]<=j-1。即失配的时候,重新用模式串的next[j]位置的字符和主串的i位置进行匹配。故模式串相对主串移动的位置大小是j-next[j]>=1,在一般情况下都比朴素匹配的移动一个位置高效。这就是KMP的改进之处。

之所以敢把主串T[i]与模式串P[next[j]]直接匹配,也就是说跳过模式串的前next[j]个字符,那么下面的事实必须存在:

Ti-next[j]  Ti-next[j]+1 ...Ti-2    Ti-1   Ti

P0     P1     ...Pnext[j]-2 Pnext[j]-1 Pnext[j]           图(b)

这个事实就是:模式串下标从0到next[j]-1位置的字符已经匹配成功了

两个论断

(1)从图(a)中要明确一点:Ti-j...Ti-1和P0...Pj-1是一模一样的。所以把前者看成后者是没有问题的。

(2)从图(b)中可以得到,P0...Pnext[j]-1 是 P0...Pj-1 的前 next[j] 个连续字符子串,而 Ti-next[j]...Ti-1 也就是 Pj-next[j]...Pj-1(根据上一条结论)是 P0...Pj-1 的后next[j]个连续字符子串。并且它们是一模一样的。

前缀、后缀

这就引出了前缀、后缀的概念。举一个例子即可说明:

字符串 "abc"

它有前缀  "a"  "ab"  "abc"

它有后缀  "c"  "bc"  "abc"

看到这里,不用过多的解释,大家也可明白前后缀指的是什么呢。

next[j]的含义

结合前后缀的概念,可得出next[j]的的实际含义:next[j]就是模式串下标 0...j-1 这j个字符的最长相对应前缀和后缀的长度。

很显然,相对应是指可以匹配得上。至于为什么是最长?,基于两点考虑:

(i)直观上看,next[j]最大,说明剩下需要进行匹配验证的字符就最少嘛。

(ii)本质上,只能取最大,否则会遗漏可能的匹配。(这一点,需要仔细想想!)

需要指出,这里我们只能考虑非平凡的前后缀,否则,对于平凡的无意义。(平凡前后缀是指:空串和串本身。其它的都是非平凡的。)

还有一点我们得明白:next数组完全由模式串本身确定,与主串无关!

求解next数组的步骤
  1. 先求出以当前字符结尾的子串的最长相对应前缀和后缀的长度。
  2. 当前字符的next值,需要参考(参考就是取的意思)上一个字符的步骤1中的求解结果。至于第一个字符,由于没有“上一个字符”的说法,直接设置为-1,即可。
看一个具体的例子:
模式串 "ababca"
最长前后缀长度表
模式串ababca
最长前后缀长度001201
由上表得出next数组:
下标012345
next-100120


直观地看就是:把“最长前后缀长度表”右移一个位置,于是最右边的一个长度被丢弃掉了,最左边空出的填上-1。这样得到的就是next数组。

next数组的递推求解

当模式串的长度很小的时候,手动计算next数组也没什么问题。可手动计算终究不是办法,必须机器计算。实际上next数组可以递推求解,这也是一个理解上的难点。
(i)初始 next[0]=-1;
(ii)若next[j]为k,即有 P0...Pk-1(Pk...)=Pj-k...Pj-1(Pj...)(*式)('='的意义:对应位置相匹配)。分两种情况,求解next[j+1]:
  1. if(Pk==Pj),则 next[j+1]=k+1=next[j]+1; 道理显而易见:若Pk与Pj相等,则最长前后缀顺势增长一个,由*式可以看出。
  2. 若Pk与Pj不相等,则更新 k=next[k];if(Pk==Pjnext[j+1]=k+1;否则,重复此过程。
我们用手动计算得到的next数组,来进行下测试:
给定一主串 "cadabababcacadda",模式串 "ababca",next数组同上:next[]={-1,0,0,1,2,0}
kmp
kmp
代码
#include<iostream> #include<iomanip> using namespace <span id="8_nwp" style="width: auto; height: auto; float: none;"><a target=_blank id="8_nwl" href="http://cpro.baidu.com/cpro/ui/uijs.php?adclass=0&app_id=0&c=news&cf=1001&ch=0&di=128&fv=19&is_app=0&jk=e2465702409b2df2&k=std&k0=std&kdi0=0&luki=3&mcpm=0&n=10&p=baidu&q=92037085_cpr&rb=0&rs=1&seller_id=1&sid=f22d9b4025746e2&ssp2=1&stid=9&t=tpclicked3_hc&td=2060332&tu=u2060332&u=http%3A%2F%2Fwww%2Ecfanz%2Ecn%2Findex%2Ephp%3Fc%3Darticle%26a%3Dread%26id%3D180612&urlid=0" target="_blank" mpid="8" style="margin: 0px; padding: 0px; color: rgb(51, 51, 51); text-decoration: none; outline: none;"><span style="color: rgb(0, 0, 255); width: auto; height: auto;">std</span></a></span>; /* 根据模式串P,设置<span id="9_nwp" style="width: auto; height: auto; float: none;"><a target=_blank id="9_nwl" href="http://cpro.baidu.com/cpro/ui/uijs.php?adclass=0&app_id=0&c=news&cf=1001&ch=0&di=128&fv=19&is_app=0&jk=e2465702409b2df2&k=next&k0=next&kdi0=0&luki=7&mcpm=0&n=10&p=baidu&q=92037085_cpr&rb=0&rs=1&seller_id=1&sid=f22d9b4025746e2&ssp2=1&stid=9&t=tpclicked3_hc&td=2060332&tu=u2060332&u=http%3A%2F%2Fwww%2Ecfanz%2Ecn%2Findex%2Ephp%3Fc%3Darticle%26a%3Dread%26id%3D180612&urlid=0" target="_blank" mpid="9" style="margin: 0px; padding: 0px; color: rgb(51, 51, 51); text-decoration: none; outline: none;"><span style="color: rgb(0, 0, 255); width: auto; height: auto;">next</span></a></span>数组的值 */ void setNext(const char* P, int* next) {  int j, k, lenP; lenP = strlen(P); j = 1; next[0] = -1; while (j < lenP) { k = next[j-1]; //P[j]!=P[k] while ((k >= 0) && P[j-1]!=P[k]) k = next[k]; if (k < 0) next[j] = 0; else next[j] = k + 1; j++; } } /* 串的模式匹配:KMP<span id="10_nwp" style="width: auto; height: auto; float: none;"><a target=_blank id="10_nwl" href="http://cpro.baidu.com/cpro/ui/uijs.php?adclass=0&app_id=0&c=news&cf=1001&ch=0&di=128&fv=19&is_app=0&jk=e2465702409b2df2&k=%CB%E3%B7%A8&k0=%CB%E3%B7%A8&kdi0=0&luki=4&mcpm=0&n=10&p=baidu&q=92037085_cpr&rb=0&rs=1&seller_id=1&sid=f22d9b4025746e2&ssp2=1&stid=9&t=tpclicked3_hc&td=2060332&tu=u2060332&u=http%3A%2F%2Fwww%2Ecfanz%2Ecn%2Findex%2Ephp%3Fc%3Darticle%26a%3Dread%26id%3D180612&urlid=0" target="_blank" mpid="10" style="margin: 0px; padding: 0px; color: rgb(51, 51, 51); text-decoration: none; outline: none;"><span style="color: rgb(0, 0, 255); width: auto; height: auto;">算法</span></a></span> (i)T是主串,其形式是字符串常量或者是以'\0'结尾的字符串数组,如"abc"或{'a','b','c','\0'} (i)P是子串,其形式和主串一样 (i)next数组 (o)匹配成功,返回主串中第一次匹配成功的下标;否则,返回-1 */ int KMP(const char *T, const char *P, const int *<span id="11_nwp" style="width: auto; height: auto; float: none;"><a target=_blank id="11_nwl" href="http://cpro.baidu.com/cpro/ui/uijs.php?adclass=0&app_id=0&c=news&cf=1001&ch=0&di=128&fv=19&is_app=0&jk=e2465702409b2df2&k=next&k0=next&kdi0=0&luki=7&mcpm=0&n=10&p=baidu&q=92037085_cpr&rb=0&rs=1&seller_id=1&sid=f22d9b4025746e2&ssp2=1&stid=9&t=tpclicked3_hc&td=2060332&tu=u2060332&u=http%3A%2F%2Fwww%2Ecfanz%2Ecn%2Findex%2Ephp%3Fc%3Darticle%26a%3Dread%26id%3D180612&urlid=0" target="_blank" mpid="11" style="margin: 0px; padding: 0px; color: rgb(51, 51, 51); text-decoration: none; outline: none;"><span style="color: rgb(0, 0, 255); width: auto; height: auto;">next</span></a></span>) { if (T && P) { //lenT是主串长度,lenP是子串长度 int lenT, lenP; lenT = strlen(T), lenP = strlen(P); //主串长度小于子串,显然无法匹配 if (lenT < lenP) return -1; int i, j, pos; i = j = -1; pos = lenT - lenP; //i最多只需变化到pos位置,想想?很简单的 while (i <= pos && j < lenP) { //匹配成功或第一次匹配 if (j == -1 || T[i] == P[j]) { i++; j++; } else//匹配失败 j = next[j]; } if (j == lenP) return i - lenP; //这个返回值很好理解 else return -1; } return -1; } void print(int *array, int n) { if (array && n > 0) { int i; for (i = 0; i < n; i++) cout << setw(4) << array[i]; cout << endl; } } int main() { cout << "***串的模式匹配:KMP<span id="12_nwp" style="width: auto; height: auto; float: none;"><a target=_blank id="12_nwl" href="http://cpro.baidu.com/cpro/ui/uijs.php?adclass=0&app_id=0&c=news&cf=1001&ch=0&di=128&fv=19&is_app=0&jk=e2465702409b2df2&k=%CB%E3%B7%A8&k0=%CB%E3%B7%A8&kdi0=0&luki=4&mcpm=0&n=10&p=baidu&q=92037085_cpr&rb=0&rs=1&seller_id=1&sid=f22d9b4025746e2&ssp2=1&stid=9&t=tpclicked3_hc&td=2060332&tu=u2060332&u=http%3A%2F%2Fwww%2Ecfanz%2Ecn%2Findex%2Ephp%3Fc%3Darticle%26a%3Dread%26id%3D180612&urlid=0" target="_blank" mpid="12" style="margin: 0px; padding: 0px; color: rgb(51, 51, 51); text-decoration: none; outline: none;"><span style="color: rgb(0, 0, 255); width: auto; height: auto;">算法</span></a></span>***by David***" << endl; char T[] = "cadabababcacadda"; char P[] = "ababca"; cout << "主串" << endl; cout << T << endl; cout << "子串" << endl; cout << P << endl; int n = strlen(P); int *next=new int[n]; setNext(P, next); cout << "打印next数组" << endl; print(next, n); cout << "使用KMP算法进行模式匹配" << endl; int index = KMP(T, P, next); if (index == -1) cout << "无法匹配!" << endl; else cout << "匹配成功!,匹配到的下标是 " << index << endl; delete[]next; system("pause"); return 0; }
运行
串匹配
 

代码下载:KMP算法


转载请注明出处,本文地址:http://blog.csdn.net/zhangxiangdavaid/article/details/35569257


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值