kmp算法next数组的产生方法
2018-02-26 20:53
183 查看
一、说明
(1)看到网上同一个字符串求 next 数组的值有两种,一种是 -1 开头,一种是 0 开头,虽然有差别,但是以 0 开头的next数组的每一项都比以 -1 开头的next数组的对应项大1,所以,具体是以 0 开头还是以 -1 开头看需要吧,算法都是一样的.KMP 的原始论文 (K,M,P 三个家伙写的原文)中是以 0 开头的,所以下面的写法是以 0 开头的.
(2)关于 next 数组的求法,网上能找到很多流行简洁的写法,也有很多文章对简洁代码讲解得非常细致,然而本文并不是对流行算法的剖析,而只是记录一下自己比较喜欢的计算方法,并用代码实现一下.
二、求法的文字描述
(1)第一种求法:根据前一个字符的next值求字符串记作 p;next 数组记作 next;
约定:
下标从 1 开始算,注意,不是从 0 开始算
字符串长度 >2
1)第一个字母的 next 值置 0 (next[1] = 0),第二个字母的 next 值置 1(next[2] = 1) ;
2)从第 3 个开始,计算第 i 个位置的 next 值时,检查
p[i-1]== p[next[i-1]] ?(即这两个值是否相等)
解释:第 i 个位置的前一个位置的值(即 p[i-1],记作 m)与以 m 的 next 值(即 next[i-1])为下标的值(即 p[next[i-1]],记作 n)是否相等,(看的懵懵的也没关系,后面会有例子)
若相等,则 next[i] = next[i-1] + 1
若不等,则继续往回找,检查
p[i-1]== p[next[next[i-1]]] ?
若相等,则 next[i] = next[next[i-1]] + 1
若不等,则继续往回找,直到找到下标为 1 还不等(即字符串第一个元素),直接赋值 next[i] = 1
(2)第二种求法:根据最大公共元素长度求
首先附上讲解的博文地址,里面有详细讲解
http://blog.csdn.net/v_july_v/article/details/7041827
1)算出每一个字母前缀后缀的最大公共元素长度
2)最大公共元素长度整体向后移动一个长度,最前面的元素值填 -1,即为 next 数组的第一版本
3)(如果你需要的 next 数组第一个值为 -1,这步就可以省略了)next 数组的每一个值分别+1,即求得 next 数组。
三、实例
字符串 P =“ababaaababaa”
求解:
(1)对应上面第一种求法
1)初始化
2)求下标为 3 的字符的 next 值
P[3-1] = P[2] = ‘b’;
next[3-1] = next[2] = 1 ;
P[next[3-1]] = P[1] = ‘a’;
P[3-1] != P[next[3-1]] ,但是此时已经回溯到了第一个元素,
∴ 直接P[3] = 1 ;
3)求下标为 4 的字符的 next 值
P[4-1] = P[3] = ‘a’;
next[4-1] = next[3] = 1 ;
P[next[4-1]] = P[1] = ‘a’;
P[4-1] == P[next[4-1]] ;
∴ next[4] = next[4-1] + 1 = 2 ;
4)求下标为 5 的字符的 next 值
P[5-1] = P[4] = ‘b’;
next[5-1] = next[4] = 2 ;
P[next[5-1]] = P[2] = ‘b’;
P[5-1] == P[next[5-1]] ;
∴ next[5] = next[5-1] + 1 = 3 ;
5)求下标为 6 的字符的 next 值
推导过程同上 => next[6] = next[6-1] + 1 = 4 ;
6)求下标为 7 的字符的 next 值
P[7-1] = P[6] = ‘a’;
next[7-1] = next[6] = 4 ;
P[next[7-1]] = P[4] = ‘b’;
P[7-1] != P[next[7-1]] && 此时还未回到第一个,继续
next[next[7-1]] = next[4] = 2 ;
P[next[next[7-1]]] = P[2] = ‘b’;番外(1)
P[7-1] != P[next[next[7-1]]] && 但是此时还未回到第一个,继续
next[next[next[7-1]]] = next[2] = 1 ;
P[next[next[next[7-1]]]] = P[1] = ‘a’ ;
P[7-1] == P[next[next[next[7-1]]]] ;
∴ next[7-1] = next[next[next[7-1]]] + 1 = next[2] + 1 = 2 ;
7)求下标为 8 的字符的 next 值
P[8-1] = P[7] = ‘a’;
next[8-1] = next[7] = 2 ;
P[next[8-1]] = P[2] = ‘b’;
P[8-1] != P[next[8-1]] ,但是还没回到第一个元素,继续
next[next[8-1]] = next[2] = 1 ;
P[next[next[8-1]]] = P[1] = ‘a’;
P[8-1] == P[next[next[8-1]]];
∴ next[8] = next[next[8-1]] + 1 = 2
8)求下标为 9 的字符的 next 值
推导过程同4) => next[9] = next[9-1] + 1 = 3 ;
9)求下标为 10 的字符的 next 值
推导过程同4) => next[10] = next[10-1] + 1 = 4 ;
10)求下标为 11 的字符的 next 值
推导过程同4) => next[11] = next[11-1] + 1 = 5 ;
11)求下标为 12 的字符的 next 值
推导过程同4) => next[12] = next[12-1] + 1 = 6 ;
(2)对应上面第二种求法
1)算出每一个字母前缀后缀的最大公共子串长度(下一步会把最后一位移走,所以最后一位可以不算)番外(2)
2)最大公共子串长度整体向后移动一个长度,最前面的元素值填 -1,即为 next 数组的第一版本
3)(如果你需要的 next 数组第一个值为 -1,这步就可以省略了)next 数组的每一个值分别+1,即求得 next 数组。
四、代码实现
(1)对应上面第一种方法
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<
d15b
li style="color:rgb(153,153,153);">32
33
34
35
36
37
38
39
40
41
(2)对应上面第二种方法
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
五、验证
2
3
4
5
6
7
8
9
10
六、番外
(1)在这个地方,我们可以发现,P[2] == P[4] == ‘b’ ,由于P[4] != P[6] ,∴ P[2] != P[6] 是一定的,就可以跳过 P[2] 和 P[6] 的比较,直接比较 P[1] 和 P[6];
(2)前缀后缀的最大公共元素长度
前缀:简单来说,也就是,从第一个字母(必包括)开始往后看到最后一 个字母(不包括)为止的字符串的以第一个字母开头的子串
(比如“abab”的前缀有a,ab,aba);
后缀:简单来说,也就是,从最后一个字母(必包括)开始往前看到第一个字母(不包括)为止的字符串的子串
(比如“abab”的后缀有b,ab,bab);
最大公共子串长度:也就是前缀和后缀拥有的相同子串的最大长度
以“abab”为例:
一种稍微快一点的小方法:
“abab”前后缀的公共子串必然是以 a(字符串第一个字母) 开头,b(字符串最后一个字母)结尾的子串,“abab”的前缀串中满足条件的子串集合为A={“ab”},后缀串中满足条件的子串集合为B={“ab”},再找出A,B集合中相等的子串集合C,最后算出C中最长子串的长度即为最大公共子串长度。
(1)看到网上同一个字符串求 next 数组的值有两种,一种是 -1 开头,一种是 0 开头,虽然有差别,但是以 0 开头的next数组的每一项都比以 -1 开头的next数组的对应项大1,所以,具体是以 0 开头还是以 -1 开头看需要吧,算法都是一样的.KMP 的原始论文 (K,M,P 三个家伙写的原文)中是以 0 开头的,所以下面的写法是以 0 开头的.
(2)关于 next 数组的求法,网上能找到很多流行简洁的写法,也有很多文章对简洁代码讲解得非常细致,然而本文并不是对流行算法的剖析,而只是记录一下自己比较喜欢的计算方法,并用代码实现一下.
二、求法的文字描述
(1)第一种求法:根据前一个字符的next值求字符串记作 p;next 数组记作 next;
约定:
下标从 1 开始算,注意,不是从 0 开始算
字符串长度 >2
1)第一个字母的 next 值置 0 (next[1] = 0),第二个字母的 next 值置 1(next[2] = 1) ;
2)从第 3 个开始,计算第 i 个位置的 next 值时,检查
p[i-1]== p[next[i-1]] ?(即这两个值是否相等)
解释:第 i 个位置的前一个位置的值(即 p[i-1],记作 m)与以 m 的 next 值(即 next[i-1])为下标的值(即 p[next[i-1]],记作 n)是否相等,(看的懵懵的也没关系,后面会有例子)
若相等,则 next[i] = next[i-1] + 1
若不等,则继续往回找,检查
p[i-1]== p[next[next[i-1]]] ?
若相等,则 next[i] = next[next[i-1]] + 1
若不等,则继续往回找,直到找到下标为 1 还不等(即字符串第一个元素),直接赋值 next[i] = 1
(2)第二种求法:根据最大公共元素长度求
首先附上讲解的博文地址,里面有详细讲解
http://blog.csdn.net/v_july_v/article/details/7041827
1)算出每一个字母前缀后缀的最大公共元素长度
2)最大公共元素长度整体向后移动一个长度,最前面的元素值填 -1,即为 next 数组的第一版本
3)(如果你需要的 next 数组第一个值为 -1,这步就可以省略了)next 数组的每一个值分别+1,即求得 next 数组。
三、实例
字符串 P =“ababaaababaa”
求解:
(1)对应上面第一种求法
1)初始化
P | a | b | a | b | a | a | a | b | a | b | a | a |
---|---|---|---|---|---|---|---|---|---|---|---|---|
下标 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
next | 0 | 1 |
P[3-1] = P[2] = ‘b’;
next[3-1] = next[2] = 1 ;
P[next[3-1]] = P[1] = ‘a’;
P[3-1] != P[next[3-1]] ,但是此时已经回溯到了第一个元素,
∴ 直接P[3] = 1 ;
P | a | b | a | b | a | a | a | b | a | b | a | a |
---|---|---|---|---|---|---|---|---|---|---|---|---|
下标 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
next | 0 | 1 | 1 |
P[4-1] = P[3] = ‘a’;
next[4-1] = next[3] = 1 ;
P[next[4-1]] = P[1] = ‘a’;
P[4-1] == P[next[4-1]] ;
∴ next[4] = next[4-1] + 1 = 2 ;
P | a | b | a | b | a | a | a | b | a | b | a | a |
---|---|---|---|---|---|---|---|---|---|---|---|---|
下标 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
next | 0 | 1 | 1 | 2 |
P[5-1] = P[4] = ‘b’;
next[5-1] = next[4] = 2 ;
P[next[5-1]] = P[2] = ‘b’;
P[5-1] == P[next[5-1]] ;
∴ next[5] = next[5-1] + 1 = 3 ;
P | a | b | a | b | a | a | a | b | a | b | a | a |
---|---|---|---|---|---|---|---|---|---|---|---|---|
下标 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
next | 0 | 1 | 1 | 2 | 3 |
推导过程同上 => next[6] = next[6-1] + 1 = 4 ;
P | a | b | a | b | a | a | a | b | a | b | a | a |
---|---|---|---|---|---|---|---|---|---|---|---|---|
下标 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
next | 0 | 1 | 1 | 2 | 3 | 4 |
P[7-1] = P[6] = ‘a’;
next[7-1] = next[6] = 4 ;
P[next[7-1]] = P[4] = ‘b’;
P[7-1] != P[next[7-1]] && 此时还未回到第一个,继续
next[next[7-1]] = next[4] = 2 ;
P[next[next[7-1]]] = P[2] = ‘b’;番外(1)
P[7-1] != P[next[next[7-1]]] && 但是此时还未回到第一个,继续
next[next[next[7-1]]] = next[2] = 1 ;
P[next[next[next[7-1]]]] = P[1] = ‘a’ ;
P[7-1] == P[next[next[next[7-1]]]] ;
∴ next[7-1] = next[next[next[7-1]]] + 1 = next[2] + 1 = 2 ;
P | a | b | a | b | a | a | a | b | a | b | a | a |
---|---|---|---|---|---|---|---|---|---|---|---|---|
下标 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
next | 0 | 1 | 1 | 2 | 3 | 4 | 2 |
P[8-1] = P[7] = ‘a’;
next[8-1] = next[7] = 2 ;
P[next[8-1]] = P[2] = ‘b’;
P[8-1] != P[next[8-1]] ,但是还没回到第一个元素,继续
next[next[8-1]] = next[2] = 1 ;
P[next[next[8-1]]] = P[1] = ‘a’;
P[8-1] == P[next[next[8-1]]];
∴ next[8] = next[next[8-1]] + 1 = 2
P | a | b | a | b | a | a | a | b | a | b | a | a |
---|---|---|---|---|---|---|---|---|---|---|---|---|
下标 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
next | 0 | 1 | 1 | 2 | 3 | 4 | 2 | 2 |
推导过程同4) => next[9] = next[9-1] + 1 = 3 ;
P | a | b | a | b | a | a | a | b | a | b | a | a |
---|---|---|---|---|---|---|---|---|---|---|---|---|
下标 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
next | 0 | 1 | 1 | 2 | 3 | 4 | 2 | 2 | 3 |
推导过程同4) => next[10] = next[10-1] + 1 = 4 ;
P | a | b | a | b | a | a | a | b | a | b | a | a |
---|---|---|---|---|---|---|---|---|---|---|---|---|
下标 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
next | 0 | 1 | 1 | 2 | 3 | 4 | 2 | 2 | 3 | 4 |
推导过程同4) => next[11] = next[11-1] + 1 = 5 ;
P | a | b | a | b | a | a | a | b | a | b | a | a |
---|---|---|---|---|---|---|---|---|---|---|---|---|
下标 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
next | 0 | 1 | 1 | 2 | 3 | 4 | 2 | 2 | 3 | 4 | 5 |
推导过程同4) => next[12] = next[12-1] + 1 = 6 ;
P | a | b | a | b | a | a | a | b | a | b | a | a |
---|---|---|---|---|---|---|---|---|---|---|---|---|
下标 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
next | 0 | 1 | 1 | 2 | 3 | 4 | 2 | 2 | 3 | 4 | 5 | 6 |
1)算出每一个字母前缀后缀的最大公共子串长度(下一步会把最后一位移走,所以最后一位可以不算)番外(2)
P | a | b | a | b | a | a | a | b | a | b | a | a |
---|---|---|---|---|---|---|---|---|---|---|---|---|
前后缀最大公共子串长度 | 0 | 0 | 1 | 2 | 3 | 1 | 1 | 2 | 3 | 4 | 5 |
P | a | b | a | b | a | a | a | b | a | b | a | a |
---|---|---|---|---|---|---|---|---|---|---|---|---|
next 数组第一版 | -1 | 0 | 0 | 1 | 2 | 3 | 1 | 1 | 2 | 3 | 4 | 5 |
P | a | b | a | b | a | a | a | b | a | b | a | a |
---|---|---|---|---|---|---|---|---|---|---|---|---|
next 数组第二版 | 0 | 1 | 1 | 2 | 3 | 4 | 2 | 2 | 3 | 4 | 5 | 6 |
(1)对应上面第一种方法
#include <iostream> #include <vector> #include <string.h> using namespace std; class Solution{ public: vector<int> getNext2(string ps){ vector<int> next; int l = (int)ps.length(); if(l == 0 ){ return next; }else if(l == 1){ next.push_back(0); return next; }else if(l == 2){ next.push_back(0); next.push_back(1); return next; } char p[20]; strcpy(p, ps.c_str());//字符串转字符数组 next.push_back(0); next.push_back(1); for(int i = 2;i<(int)ps.length();i++){ int k = next[i-1]; while(k!=0){ if(p[i-1] == p[k-1]){//k-1是因为,在计算机里,数组下标是从0开始的 next.push_back(k+1); break; }else{ k = next[k-1]; } } if(k==0) { next.push_back(1); } } return next; } };1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<
d15b
li style="color:rgb(153,153,153);">32
33
34
35
36
37
38
39
40
41
(2)对应上面第二种方法
#include <iostream> #include <vector> #include <string.h> using namespace std; class Solution{ public: vector<int> getNext2(string ps){ vector<int> next; int l = (int)ps.length(); if(l == 0 ){ return next; }else if(l == 1){ next.push_back(0); return next; } next.push_back(0);//next数组第一个值为0 for(int i = 0; i < l-1; i++ ){//最后一个最大公共子串长度不用算 //计算每一个子字符串的前缀后缀最大公共子串长度 int l = max_pub_substr(ps.substr(0,i+1)); //右移后+1 //for循环之前已加了一个数据0进next数组,此时再加进去元素时,元素在next数组里的下标比得到该元素值的子串最后字符的下标大1,也就相当于向后移一位了 next.push_back(l+1); } return next; } //计算前缀后缀的最大公共子串长度 int max_pub_substr(string ps){ int l = (int)ps.length(); if(l == 0 || l == 1){ return 0; } char p[20]; strcpy(p, ps.c_str());//字符串转字符数组 int len = 0; int m = -1;//最后一个字符(不包括)之前与最后一个字符相等的字符下标 m int k = -1;//已经查找过的字符下标 while( k != l-1 ){ k = m+1; for(; k < l-1; k++){ if(p[k] == p[l-1]){ m = k; break; } } if( m==-1 || k==l+1){//表示没有与最后一个字符相等的字符 return len; }else{//检查前缀串和后缀串是否相等 int i = 0,j = l-1-m; for(; i <= m,j < l; i++,j++){//i前缀下标,j后缀下标 //只要有不相等的就失败 if(p[i] != p[j]){ break; } } if( i == m+1 ){//说明前后串相等,因为全都比较了,没有中断 len = m+1; } } } return len; } };1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
五、验证
int main(){ string s = "ababaaababaa"; Solution slt; vector<int> next = slt.getNext2(s); vector<int>::iterator it; for( it = next.begin(); it != next.end(); it++){ cout<<*it; } return 0; }1
2
3
4
5
6
7
8
9
10
六、番外
(1)在这个地方,我们可以发现,P[2] == P[4] == ‘b’ ,由于P[4] != P[6] ,∴ P[2] != P[6] 是一定的,就可以跳过 P[2] 和 P[6] 的比较,直接比较 P[1] 和 P[6];
(2)前缀后缀的最大公共元素长度
前缀:简单来说,也就是,从第一个字母(必包括)开始往后看到最后一 个字母(不包括)为止的字符串的以第一个字母开头的子串
(比如“abab”的前缀有a,ab,aba);
后缀:简单来说,也就是,从最后一个字母(必包括)开始往前看到第一个字母(不包括)为止的字符串的子串
(比如“abab”的后缀有b,ab,bab);
最大公共子串长度:也就是前缀和后缀拥有的相同子串的最大长度
以“abab”为例:
模式串的各个子串 | 前缀 | 后缀 | 最大公共元素长度 |
---|---|---|---|
a | 空 | 空 | 0 |
ab | a | b | 0 |
aba | a,ab | a,ba | 1 |
abab | a,ab,aba | b,ab,bab | 2 |
“abab”前后缀的公共子串必然是以 a(字符串第一个字母) 开头,b(字符串最后一个字母)结尾的子串,“abab”的前缀串中满足条件的子串集合为A={“ab”},后缀串中满足条件的子串集合为B={“ab”},再找出A,B集合中相等的子串集合C,最后算出C中最长子串的长度即为最大公共子串长度。
相关文章推荐
- KMP算法:KMP算法个人理解+next数组细节处理的方法
- KMP算法中next数组的手工计算方法
- KMP算法中next数组的计算方法
- KMP算法next数组计算方法的优化
- KMP算法求next数组和nextval数组的简单方法
- KMP算法简介(next数组的计算方法)
- KMP算法中next和nextval数组的计算方法
- KMP算法求next数组的方法
- KMP算法求next数组和nextval数组的简单方法
- KMP算法及Next数组求解方法
- KMP算法求next数组和nextval数组的简单方法
- KMP算法中next数组的手工计算方法
- KMP算法NEXT数组计算方法
- KMP算法求next数组和nextval数组的简单方法
- KMP算法中next数组、nextval数组的手工计算
- KMP算法java版,next数组求法简单易懂
- KMP算法中next数组的构建
- 详解KMP算法中Next数组的求法
- KMP算法及next数组详解
- 字符串模式匹配之KMP算法图解与 next 数组原理和实现方案