/
05_string_kmp.cpp
146 lines (126 loc) · 3.98 KB
/
05_string_kmp.cpp
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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
#include <cstdio>
#include <cstring>
#define DEBUG
typedef struct {
int len;
char *text;
} String;
/**********************************************
获取next数组:
从前往后,每个子串前后缀中有 n 个字母相等
就在next数组中代表子串后一位的位置中记录下 n
***********************************************/
void get_next(const String *T, int *nextArr){
int i = 0;
int j = -1; // -1 代表回退到串首
nextArr[0] = -1;
int len = T->len;
while(i < len - 1){ // 需要进行 len - 1 此比较
// 逻辑关系
if(j == -1){
// j = -1 时就将 j 置向串首,将 i 往后推一位
// 并在对应的next数组中记录 0 (因为这种情况—— j == -1
// 只会发生在第一次循环或遇到前后缀不相等的情况下)
nextArr[++i] = ++j; // 这里的 ++j == 0
}else if(T->text[i] == T->text[j]){
// 当前子串前缀和后缀相等则在next数组中代表子串后一位
// 的位置中记录相等的前后缀长度
nextArr[++i] = ++j;
}
else{ // 遇到前后缀不同是 回溯 j
j = nextArr[j];
}
// 简洁写法
/*
if(j != -1 && T->text[i] != T->text[j])
j = nextArr[j];
else
nextArr[++i] = ++j;
*/
}
}
/***************************************************
获取改进版next数组:
将前缀中内容重复的位置在next数组中赋为同样的值
减少不必要的匹配
***************************************************/
void get_nextval(const String *T, int *nextArr){
int i = 0;
int j = -1;
nextArr[0] = -1;
while(i < T->len - 1){
if( j != -1 && T->text[i] != T->text[j])
j = nextArr[j];
else{
// 若当前字符与已存在的对应前缀字符不同
if(T->text[++i] != T->text[++j])
nextArr[i] = j; // 则当前的 j 为nextArr在i位置的值
else
nextArr[i] = nextArr[j]; // 否则将前缀字符的nextArr值赋给
// nextArr在i的位置
}
}
}
/***************************************************
KMP 算法实现
返回子串T在朱串S中第pos个字符串之后的位置
若不存在,则返回 -1
****************************************************/
int indexKMP(const String *S, const String *T, int pos = 0){
int i = pos; // 将i置为开始匹配的位置,默认为0
int j = 0;
int nextArr[255];
#ifdef DEBUG
get_next(T, nextArr); // 获取 nextArr 回溯数组
// 打印 nextArr 数组
for(int i = 0; i < T->len; i++)
printf("%d ", nextArr[i]);
printf("\n");
#endif
get_nextval(T, nextArr); // 获取改进版 nextArr 回溯数组
#ifdef DEBUG
// 打印改进版 nextArr 数组
for(int i = 0; i < T->len; i++)
printf("%d ", nextArr[i]);
printf("\n");
#endif
while(i < S->len && j < T->len){
if(j == -1){
// j 下标回溯至 -1 时需重置 j 为 0 并将 i 往后推一位
++i, ++j;
}else if(S->text[i] == T->text[j]){
// 单个字符匹配成功时 同时将 i j 增加 1
++i, ++j;
}else{
// 单个字符匹配失败时则回溯 j
j = nextArr[j];
}
// 简洁写法
/*
if( j != -1 && S->text[i] != T->text[j])
j = nextArr[j];
else
++i, ++j;
*/
}
if(j >= T->len)
return i - T->len;
return -1;
}
int main(){
char S[] = "ababaaababaaabababaaaba";
String Sstr;
Sstr.len = strlen(S);
Sstr.text = S;
char T[] = "ababaaaba";
String Tstr;
Tstr.len = strlen(T);
Tstr.text = T;
int a = indexKMP(&Sstr, &Tstr);
printf("%d\n", a);
a = indexKMP(&Sstr, &Tstr, 3);
printf("%d\n", a);
a = indexKMP(&Sstr, &Tstr, 9);
printf("%d\n", a);
return 0;
}