字符串匹配算法(摘)

时间:2022-02-25 22:25:55
KMP字符串匹配算法,记录一下。 命题:设计算法,在字符串s中,从pos位置开始,查找第一个与目标字符串t相同的子字符串的起始位置。 kmp算法实现:第一步,预处理目标字符串t,求出t中每一个字符在与源字符串s中字符不等时移到的位置。方法是根据如下公式 next[0] = -1; next[j] = max{k| 0<k<j && "t0t1...t(k-1)" == "t(j-k)t(j-k+1)...t(j-1)"}; next[j] = 0;  此公式可如下证明 首先,假设目标字符串下一次移动到k位置,那么这个k位置有什么特性呢,k之前的所有字符(k个,从0开始),应该和源字符串s中i位置前的k个字符相同,即: "t0t1...t(k-1)" == "s(i-k)s(i-k+1)...s(i-1)" 而且,这时候,源字符串i位置之前的k个字符和目标字符串j位置之前的k个字符也相同,即: "t(j-k)t(j-k+1)...t(j-1)" == "s(i-k)s(i-k+1)...s(i-1)" 那么得到如下结论 "t0t1...t(k-1)" == "t(j-k)t(j-k+1)...t(j-1)"  第二步,循环源字符串和目标字符串,如下吧,这段不好说了 while(s[i] != ’’ && t[j] != ’’) { if(j == -1 || s[i] == t[j]) //j==-1表示s[i]与t[0]不同 { i++; j++; } else { j = next[j]; } } if(t[j] == ’’)  return i-j; else return 0; 查找模式字符串在文本中的所有出现是文本编辑软件经常要面对的一个问题。一般而言文本就是要编辑的文档,而模式字符串往往由用户来指定,高效的字符串匹配 算法可以提高程序的响应性能,当然字符串匹配算法的应用远远不止于此,例如在生物计算科学中查找特定的DNA序列,也是字符串匹配算法的一个重要应用。我们可以如下定义字符串匹配问题,我们假定文本是一个长度为n的字符数组T[1..n],而模式字符串也是一个字符数组P[1..m],并且m <= n。进一步的,我们假定T所包含的所有字符构成一个有限的字符表E,|E|表示字符表所包含的自负的个数。本文所提及的字符串匹配算法除了朴素字符串匹配算法以外都需要一定的预处理时间,此预处理时间根据算法的不同也不尽相同,图1展示了本文将要提到的各个算 法所需要的预处理时间以及匹配的时间,可以大致看出字符串匹配算法的演变过程,而且会发现后缀树算法与其他算法有很大的不同,而正是由于这一点使得后缀树 的应用更加贴近于今天互联网的大环境。2.朴素字符串匹配算法朴素字符串匹配算法的思想非常直白,既然是查找模式字符串在文本字符串中的出现,那么我么就从文本字符串的每一位开始一次判断是否与模式字符串相同,如果 相同那么就找到了模式字符串的一次出现,可以看出此算法的时间复杂度为O(mn),m,n分别为文本字符串与模式字符串的长度,但是通过更加深入的分析可 以得出此算法的平均时间复杂度为O(n+m),所以此算法在模式字符串与文本字符串都是随即选择的情况下工作地是相当的不错,还有就是此算法并没有其他算 法所必须的预处理时间。3.Rabin-Karp算法Rabin-Karp算法是由Rabin和Karp[1]提出的一个在实际中有比较好应用的字符串匹配算法,此算法的预处理时间为O(m),但它的在最坏 情况下的时间复杂度为O((2n-m+1)m),而平均复杂度接近O(m+n),此算法的主要思想就是通过对字符串进行哈稀运算,使得算法可以容易的派出 大量的不相同的字符串,假设模式字符串的长度为m,利用Horner法则p = p[m] + 10(p[m -1] + 10(p[m-2]+...+10(p[2]+10p[1])...)),求出模式字符串的哈稀值p,而对于文本字符串来说,对应于每个长度为m的子串的 哈稀值为t(s+1)=10(t(s)-10^(m-1)T[s+1])+T[s+m+1],然后比较此哈稀值与模式字符串的哈稀值是否相等,若不相同, 则字符串一定不同,若相同,则需要进一步的按位比较,所以它的最坏情况下的时间复杂度为O(mn)。4.字符串匹配自动机与字符串相关的自动机[2]是一个五元组(Q,q,A,E,%)其中Q是状态的有限集合,q<Q是开始状态,A<+Q是接受状态的集合,E是 输入字符集合,%是一个QXE->Q的映射,称为转移函数。通过模式字符串,我们可以构造出识别此字符串的自动机,此自动机对应于字符表中的任何一 个输入在某个状态下都有一个转移到另一个状态(包括它自身),当进行到接受状态时就找到了模式字符串在目标字符串中的一次出现,由于自动机需要对于任何一 个字符集中出现的字符都给出相应的转移,也就意味着构造自动机的时间复杂度与|E|有关,而事实上,构造自动机的时间复杂度为O(m^3|E|),但当此 自动机构造完成后,搜索目标字符串的时间为O(n)。5.KMP算法现在要提到一个伟大的算法,它的出现归功于Knuth,Morris和Pratt的伟大工作[3],此算法采用类似上面所提到的字符串自动机的原理,但完 全避免了计算转移函数,它仅仅用了一个只需要O(m)就能计算出来的辅助函数pai[1..m]就达到了搜索时间复杂度为O(n).函数pai称为前缀函数,是根据模式字符串构造的,对于一个给定的模式字符串p[1..m],它的前缀函数定义如下:pai[q] = max{k:k<q and p(k) > p(q)}即:pai[q]是p的最长的与后缀p(q)相同的前缀。通过均摊分析可以证明计算前缀函数的时间复杂度为O(m),而同样把此方法用于分析KMP算法的时间复杂度分析,我们可以得出KMP匹配算法的时间复杂度为O(m+n)。6.后缀树算法6-1后缀字典树算法对于目标字符串的所有后缀,可以把他们构造成字典树,此字典树的每一个节点(除叶子节点外),都可能有|E|个分支,这样在目标字符串中搜索模式字符串的 出现变得非常的简单,只需要从字典树的跟节点出发,按照对应的分支向下查找,最多比较m次,就可以确定此模式字符串是否在目标字符串钟出现。所以查找字符 串的时间复杂度为O(m)与目标字符串n无关,这种好处是非常明现的,它意味着对于一个目标字符串一旦建立它的后缀字典树后,以后在此目标字符串查找任意 字符串都只需要O(m)的比较,但是构造后缀字典树无疑是很花费时间与空间的,通过分析可以知道,构造此字典树的时空花费为O(n^2),对于很大的目标 字符串来说,这是无法忍受的。6-2非在线后缀树算法为了减少构造后缀字典树所花费的时间与空间,Edward McGreight[4] 1976年发表了后缀树的论文,此后缀树与后缀字典树在代表的含义上完全相同,但由于它采用了路径压缩的方法,即每条边不只代表一个字符,有可能是一个字 符串,大大节省了构造此后缀树所花的时间与空间,可以证明此后缀树最多有2n个节点,构造的时间为O(n),但是此算法有个非常明显的缺点,就是它的倒序 处理字符串的,也就是它是非在线的。6-3在线后缀树算法1995年Ukkonen[5]提出了在线的后缀树算法,支持从左到右的输入字符串,并在输入的同时构造出后缀树,至此给后缀树画上了完美的句号,通过分析可以知道基于后缀树的字符串匹配算法的时间复杂度为O(m+n)。7.总结通过上面的阐述,我们可以清出的看出一个问题的提出经常半随着一个新算法的出现,而一个新算法的出现又会带来一些新的问题。前面所述的这些算法虽然从理论 上来说一个比一个高效,但是在实际应用中,每个算法都有自己独特的价值,即便是最原始的朴素字符串匹配算法也仍然有较好的应用,因为它的平均性能很好,而 且不需要预处理时间。而Rabin-Karp算法则往往用于单个模式字符串在多个目标字符串中的同时查找,以及模糊匹配。KMP无疑是应用最广的而且它的 实现也很简单,几乎无所不在。后缀树算法对于在同一个目标文本中查找多个模式字符串则非常的适合,尤其对于很大的目标文本,工作地非常好,所以在网络搜索 中成为了主流算法。目前后缀树的研究已经比较成熟,主要在进行后缀数组的研究,后缀数组与后缀树的想法相同,但是在实现上更加高效,不过它并不是O(n) 的算法,但是由于其简单的结构,高效的实现使得它的应用非常的诱人。字符串匹配算法还有很多,这里只是提到一些比较经典的算法,不过沿着字符串匹配算法的 轨迹,我们可以清楚的看到它的迷人之处