1 串匹配
1.1 蛮力匹配法——版本1
int match(char* P, char* T) { // P是匹配串(模式位复位),T是被匹配串(文本串)
size_t n = strlen(T), i = 0;
size_t m = strlen(P), j = 0;
while (j < m && i < n) {
if (T[i] == P[j])
{
i++;
j++;
}
else { // 文本串回退,模式位复位
i = i - j + 1;
j = 0;
}
}
// i-j是匹配开始位置,若无匹配,i=n+1, j = 0, i-j > n
return i - j;
}
1.2 蛮力匹配法——版本2
- 意思与版本1相同,只不过内for循环的判断语句 【if(T[i+j] != P[j])】 设计的有意思
int match(char* P, char* T) { size_t n = strlen(T), i = 0; size_t m = strlen(P), j; for (int i = 0; i < n-m+1; i++) { for (j = 0; j < m; j++) if (T[i+j] != P[j]) break; if (j >= m) break; } // 若i>n-m则说明无匹配 return i; }
1.3 next表
-
若模式P经适当右移之后,能够与T的某一(包括T[i])子串完全匹配,则一项必要条件是:【其中t是模式串下一轮比对的位置】
P[0, t) = T[i - t, i)
P[0, t) = P[j - t , j) 【这个条件是重点】
即:在P[0, j)中长度为t的真前缀,应与长度为t的真后缀完全匹配N(P, j) = {0 <= t <j | P[0, t) = P(j - t, j)}
-
为使P与T的对齐位置绝不倒退(即index变大),同时不遗漏任何可能的匹配,应在集合N[ P, j) 中挑选最大的t。
next[j] = max( N(P, j) )
1.4 KMP算法
1)匹配,文本串与模式串的指针向前移动1个字符,判断下一字符是否匹配;
2)模式串已无字符与当前字符匹配,文本串与模式串的指针向前移动1个字符,相当于模式串整体向前移动一个1个字符,判断下一字符是否匹配;
综上二种情况均为:i++; j++;
int match(char* P, char* T) { // P是匹配串(模式位复位),T是被匹配串(文本串)
int* next = buildNext(P);
size_t n = strlen(T), i = 0;
size_t m = strlen(P), j = 0;
while (j < m && i < n) {
// 若匹配,或P已移出最左侧(两个判断的次序不可交换)
if ( 0 > j || T[i] == P[j])
{
i++;
j++;
}
else
// 保持失配位置不动,回退模式串的指针
j = next[j];
}
delete [] next;
return i - j;
}
1.4.1 next[0] = -1
- 若在某一轮比对中首对字符即失配,则应将P直接右移一个字符,然后启动下一轮比对。此时i = i + 1; j = j + 1;
策略:令next[j] = -1
1.4.2 next[j+1]
【这种构造方法的核心是真前缀等于真后缀】
-
由于next[j] = t,则必有next[j+1] <= next[j] + 1。
证明如下:
-
当且仅当P[j] = P[t]时等号成立,即next[j+1] = next[j] + 1。
-
当P[j] != P[t]时,next[j+1] 的候选者 应该依次是next[next[j] ] + 1,next[next[next[j] ] ] + 1,…
即只需反复用t = next[t],直至P[j] = P[t]时,令next[j+1] = next[t] + 1
Q:为什么不是 next[j+1] = t + 1?
A:若next[j+1] = t + 1,即P[0, t+1) = P[(j+1) - (t+1), j+1) , 即P[t] = P[j],与条件矛盾!
1.4.3 构造next表
【这种构造方法的核心是真前缀等于真后缀】
int* buildNext(char* P) {
size_t m = strlen(P), j = 0;
int* N = new int[m]; // next表
// 说明整体向右移动一个位置,开启下一阶段的匹配
int t = N[0] = -1;
while(j < m-1) {
if (0 > t || P[j] == P[t]) {
j++;
t++;
N[j] = t; // 此句可改进
}
else
t = N[t];
}
return N;
}
1.4.4 改进next表
上述构造next表的方法核心是真前缀等于真后缀
-
缺点是:与有可能进行多次不必要的比对
-
原因:不仅需要匹配“成功”的经验,还需要匹配失败的“教训”。
-
改进:把“负面”信息引入next表,即除“真前缀等于真后缀”,还需要满足“当前字符不匹配”:
N(P, j) = { 0 <= t <j | P[0, t) = P(j - t, j) 且} P[t] != P[j] }
int* buildNext(char* P) {
size_t m = strlen(P), j = 0;
int* N = new int[m]; // next表
// 说明整体向右移动一个位置,开启下一阶段的匹配
int t = N[0] = -1;
while(j < m-1) {
if (0 > t || P[j] == P[t]) {
j++;
t++;
N[j] = (P[j] != P[t] ? t : N[t]); // 此句可改进
}
else
t = N[t];
}
return N;
}