字符串匹配:
-:Brute Force(BF或蛮力搜索)算法:朴素字符串匹配
这是世界上最简单的算法了。
首先将匹配串和模式串左对齐,然后从左向右一个一个进行比较,如果不成功则模式串向右移动一个单位。
普通的字符串匹配算法必须要回溯。即j=0,重新开始匹配。
KMP算法思想
“ KMP 算法中,如果当前字符匹配成功,即 S[i]==T[j] ,令 i++ , j++ ,继续匹配下一个字符;如果 匹配失败,即S[i] != T[j] ,需要保持 i 不变,并且让 j = next[j] ,这里 next[j] <=j -1 ,即模式串 T 相对于原始串 S 向右移动了至少 1 位 ( 移动的实际位数 j - next[j] >=1)next数组的含义
重点来了。下面解释一下next数组的含义,这个也是KMP算法中比较不好理解的一点。
令原始串为: S[i],其中0<=i<=n;模式串为: T[j],其中0<=j<=m。
假设目前匹配到如下位置
S0,S1,S2,...,Si-j,Si-j+1...............,Si-1,Si, Si+1,....,Sn
T0,T1,.....................,Tj-1,Tj, ..........
S和T的绿色部分匹配成功,恰好到Si和Tj的时候失配,如果要保持i不变,同时达到让模式串T相对于原始串S右移的话,可以更新j的值,让Si和新的Tj进行匹配,假设新的j用next[j]表示,即让Si和next[j]匹配,显然新的j值要小于之前的j值,模式串才会是右移的效果,也就是说应该有next[j] <= j -1。那新的j值也就是next[j]应该是多少呢?我们观察如下的匹配:
1)如果模式串右移1位(从简单的思考起,移动一位会怎么样),即next[j] = j - 1, 即让蓝色的Si和Tj-1匹配(注:省略号为未匹配部分)
S0,S1,S2,...,Si-j,Si-j+1...............,Si-1,Si, Si+1,....,Sn
T0,T1,.....................,Tj-1,Tj, .......... (T的划线部分和S划线部分相等【1】)
T0,T1,.................Tj-2,Tj-1,....... (移动后的T的划线部分和S的划线部分相等【2】)
根据【1】【2】可以知道当next[j] =j -1,即模式串右移一位的时候,有T[0 ~ j-2] == T[1 ~ j-1],而这两部分恰好是字符串T[0 ~j-1]的前缀和后缀,也就是说next[j]的值取决于模式串T中j前面部分的前缀和后缀相等部分的长度(好好揣摩这两个关键字概念:前缀、后缀,或者再想想,我的上一篇文章,从Trie树谈到后缀树中,后缀树的概念)。
2)如果模式串右移2位,即next[j] = j - 2, 即让蓝色的Si和Tj-2匹配
S0,S1,...,Si-j,Si-j+1,Si-j+2...............,Si-1,Si, Si+1,....,Sn
T0,T1,T2,.....................,Tj-1,Tj, ..........(T的划线部分和S划线部分相等【3】)
T0,T1,...............,Tj-3,Tj-2,.........(移动后的T的划线部分和S的划线部分相等【4】)
同样根据【3】【4】可以知道当next[j] =j -2,即模式串右移两位的时候,有T[0 ~ j-3] == T[2 ~ j-1]。而这两部分也恰好是字符串T[0 ~j-1]的前缀和后缀,也就是说next[j]的值取决于模式串T中j前面部分的前缀和后缀相等部分的长度。
3)依次类推,可以得到如下结论:当发生失配的情况下,j的新值next[j]取决于模式串中T[0 ~ j-1]中前缀和后缀相等部分的长度, 并且next[j]恰好等于这个最大长度。
S: ababcababa
P: ababa
next [j]表示j之前的最长前缀匹配最长后缀的长度是多少。P a b a b a
j 0 1 2 3 4
next -1 0 0 1 2
Horspool字符串匹配算法
Horspool和Kmp算法有点相识,都是采用空间换时间的想法,从而达到算法运算速率的提高,运算效率也都是θ(n),在最佳情况下,它的时间复杂度是O(n/m),
Horspool算法
这个算法是由R.Nigel Horspool在1980年提出的。其滑动思想非常简单,就是从后往前匹配模式串,若在某一位失去匹配,此位对应的文本串字符为c,那就将模式串向右滑动,使模式
串之前最近的c对准这一位,再从新从后往前检查。那如果之前找不到c怎么办?那好极了,直接将整个模式串滑过这一位。
1)A G A T A C G A T A T A T A C
A T A T A
在主串的G处失配,此时模式串向右移动的距离是d[A]=2
2)A G A T A C G A T A T A T A C
A T A T A
又在主串G处失配,此时模式串向右移动的距离是d[G]=5
3)A G A T A C G A T A T A T A C
A T A T A
此处发现了一个匹配,我们可以继续往下找,此时模式串向右移动的距离是d[A]=2
4)A G A T A C G A T A T A T A C
A T A T A
呃。。。又发现了一个匹配,再继续往下找吧,此时模式串向右移动的距离依然是d[A]=2
5)A G A T A C G A T A T A T A C
A T A T A
移动后,主串指针pos>n-m了,搜索过程结束。
而BM算法在移动模式串的时候是从左到右,而进行比较的时候是从右到左的,基本框架是:
- j = 0;
- while (j <= strlen(主串) - strlen(模式串)) {
- for (i = strlen(模式串) - 1; i >= 0 && 模式串[i] ==主串[i + j]; --i)
- if (i < 0)
- match;
- else
- ++j;
- }
显然BM算法并不是上面那个样子,BM算法的精华就在于++j
BM算法在移动模式串的时候是从左到右,而进行比较的时候是从右到左的,
BM算法在进行匹配时,包含两个并行的算法,坏字符算法和好后缀算法,这两种算法的目的就是为了让模式串每次向右移动尽可能大的距离(j+=s,s尽可能的大)
几个定义:
例主串和模式串如下:
主串 : mahtavaatalomaisema omalomailuun
模式串: maisemaomaloma
好后缀:模式串中的aloma为“好后缀”。
坏字符:主串中的“t”为坏字符。
1、好后缀算法
好后缀确定的偏移量分为如下两种情况:
Case1:模式传中有字串和好后缀完全比配,则将最靠右的那个字串移动到好后缀的位置,继续进行匹配。(shift即为偏移量)(m-1-address(u))
Case2:如果不存在和好后缀完全匹配,则在好后缀中找到具有如下特征的最长子串,使得P[m-s,…,m]=P[0,…,s]。最长前后缀。
好后缀算法
如果程序匹配了一个好后缀, 并且在模式中还有另外一个相同的后缀, 那
把下一个后缀移动到当前后缀位置。好后缀算法有两种情况:
Case1:模式串中有子串和好后缀安全匹配,则将最靠右的那个子串移动到好后缀的位置。继续进行匹配。
Case2:如果不存在和好后缀完全匹配的子串,则在好后缀中找到具有如下特征的最长子串,使得P[m-s…m]=P[0…s]。
3、坏字符算法
当出现一个坏字符时, BM算法向右移动模式串, 让模式串中最靠右的对应字符与坏字符相对,然后继续匹配。坏字符算法也有两种情况。
Case2:模式串中不存在坏字符。见图。
移动规则
BM算法的移动规则是:
将概述中的++j,换成j+=MAX(shift(好后缀),shift(坏字符)),即
BM算法是每次向右移动模式串的距离是,按照好后缀算法和坏字符算法计算得到的最大值。