1.定义
串就是字符串,是0个或者多个字符组成的有序序列。
2.串的操作:
- 赋值操作
- 获取长度操作
- 串比较操作
- 串连接操作
- 求子串操作:求给定字符串,从某一位置开始,到某一位置结束的串的操作
- 串清空操作
3.串的模式匹配算法
- 幼稚字符匹配算法(优点容易理解,缺点效率低)
- 匹配串和原字符串对齐,
- 依次比较,是否所有字符串相同。
- 如果不相同,匹配串开始下班,和原字符串第二个对齐,比较指针指向匹配串开头(称为回溯)重新开始比较
- 如果再发现不同,匹配串移动到,原字符和第三位置对齐,比较指针回溯。重新比较,依次类推。
- KMP算法
(1)算法原理说明
KMP和幼稚算法的大体步骤相同,但是比较到不同位置后,回溯和重新对齐的位置不同。
假如,我们有两个字符串,(原字符串:ABBABBABABAAABABAAAA,模式串:ABBABAABABAA)我们我们是比较了多个才发现不同,比如6个,如下。
第一次比较,我们再横线处发现不同。
原字符串:ABBAB|BABABAAABABAAAA
模式串 :ABBAB|AABABAA
如果是幼稚模式我们会这样,模式串左移一位,比较指针,回溯到模式串开头。
A|BBABBABABAAABABAAAA
|ABBABAABABAA
可是其实我们已经比较过5个字符串,这些比较过的字符,比较指针根本不用用回溯。模式串则可以移动3位
原字符串:ABBAB|BABABAAABABAAAA
模式串 : AB|BABAABABAA
为什么是三位不是5位,我们看一下比较串中已经比较的5位(AB)B(AB),主意括号括起来的部分是一样的,其实我们只敢确定(AB)B是不同的,从原字符串中 ABB(AB)|BABABAAABABAAAA 和(AB)B(AB)中(AB)B后面部分又有可能是相同的了。这个匹配串中相同的部分,我们就叫公共前后缀。
所以,KMP算法的核心就是:
1.比较指针不回溯(比较变成O(n))
2.模式串,从前缀移动到后缀位置。
2. next 数组
还是上面这个例子(原字符串:ABBABBABABAAABABAAAA,模式串:ABBABAABABAA)
原字符串:ABBAB|BABABAAABABAAAA
模式串 :ABBAB|AABABAA
如果我们每次发现,不同,都取出匹配串前面的子串,再计算出,公共前后缀的大小,再回溯,其实也是很费时的。索性一旦确定子串,子串的公共前后缀就确定了。我们需要移动子串的位置就是,子串长度-公共子串大小。 我们可以利用匹配串提前算出这些信息并且存到一个数组里。逻辑对应关系如下。
注意:next数组的1位置代表 匹配串第一个字符不是0
模式串 : A B B A B A A B A B A A
数组下标 : 0 1 2 3 4 5 6 7 8 9 10 11 12
匹配串移动数 : 0 0 1 1 1 2 3 2 2 3 2 3 2
next数据计算规则如下:
特殊情况1:1的位置,不匹配固定是 0,这时,比较指针,和匹配串一起向前移动一位
特殊情况2: 2的位置,1匹配,二不匹配,比较串移动一位。
特殊情况3:前两个位置匹配,第三位置匹配,也是固定移动一位
情况4 :A B B 没有前后缀,值为1
情况5 :A B B A 公共前后缀为A,值为2 (公共前后缀数加1)
情况6 :A B B A B 公共前后缀为AB,值为3(公共前后缀数加1)
注意前后缀的原则是:
前后缀是最大前后缀,但是不是子串本身。
//next 数组算法c语言实现
void getnext(char ch[],int length, int next[]){//length为串的长度
next[1]=0;
int i=1, j=0;
while(i<length){
if(j==0|| ch[i]== ch[j]){ //j是上一次确定可以移动的位置,i是已经比较过的位置
//
++i;
++j;
next[i] = j;
} else {
j=next[j];
}
}
}