KMP算法
1. 简单介绍
KMP算法用作字符串匹配返回匹配成功字符串的起始位置,时间复杂度O(N)
Java中自带indexOf函数,indexOf函数是KMP的优化版本,只优化了常数时间。
2.next数组
作用
- 可以加速匹配的过程,不必暴力匹配
- next数组保存的是前缀字符串和后缀字符串最大匹配长度(不包含字符串本身)
实现过程
next[0]
默认值为-1,人为规定,用于后续做判断next[1]=0
,当i=1
的时候,[0,i-1]
范围内只有一个字符,所以前缀长度与后缀长度为0,因为前缀与后缀长度计算的时候是不包含自身的- 从
i=2
开始遍历字符串,普遍位置有三种情况:- 情况一:
i-1
位置的字符与待匹配的前缀起始位置相等,next[i]
等于前缀起始位置加1,表达式next[i]=++index
- 情况二:前缀和后缀没有匹配成功,就从
next
数组中找index下标中对应的前缀位置,表达式index=next[index]
- 情况三:当前缀和后缀没有匹配成功,并且next数组也不能再往前找值了,
next[i]=0
- 情况一:
图解next数组实现过程
next数组代码
vector<int> getNext(string str) {
// 每个位置字符串的前缀与后缀最大匹配长度,不包含整串
vector<int> next(str.size());
next[0] = -1; //人为规定,0号位置的值是-1
next[1] = 0;
int i = 2; // 从2开始遍历str
// index代表当前是哪个位置的字符,在和index+1也就是i位置比较
int index = 0; // index既用来作为下标访问,也作为值
while (i < next.size()) {
// str[i-1]代表后缀开始的位置, str[index]代表前缀开始的位置
// index保存了上一次匹配的最大长度, str[index]代表了当前前缀位置, 可以通过这个来进行加速匹配
if (str[i - 1] == str[index]) {
// 如果str[i-1](后缀待匹配的字符) 等于 str[index](前缀待匹配的字符)
// next数组i位置的值 直接等于上次最大匹配长度+1
next[i++] = ++index;
}
else if (index > 0) {
// 后缀与前缀没有匹配成功, 并且index还可以往前找 next[index]的前缀, 也就是找当前前缀的前缀开始位置
index = next[index];
}
else{
// index=0, 没有前缀了, 长度记为0
next[i++] = 0;
}
}
return next;
}
3. 主串与子串比较函数
流程
- 调用getNext函数获取子串的next数组
- 用
i
和j
作为下标,分别遍历主串str1
和子串str2
- 当
i
和j
都不越界的时候,有三种情况 - 情况一:主串当前位置的字符 与 子串当前位置的字符相等,
i
和j
都自增 - 情况二:当子串的next数组等于-1,也就是
next[0]
人为规定的值,或者j
等于0的时候,代表匹配失败,i
自增,j
保持0不变 - 情况三:主串当前位置字符 与 子串当前位置的字符不相等, 此时
j>0
,就去找next数组中前一个前缀的位置 - 最后看
j
的值是否等于子串的长度,如果等于子串长度,就代表匹配成功,然后返回i-j
就代表从主串的i-j
位置开始匹配 - 如果匹配失败就返回-1
代码
int getIndex(string str1, string str2) {
vector<int> next = getNext(str2);
int i = 0;
int j = 0;
while (i < str1.size() && j < str2.size()) {
if (str1[i] == str2[j]) {
i++;
j++;
}else if (next[j] == -1) {
i++;
}else{
j = next[j];
}
}
if (j == str2.size()) {
return i - j;
}
return -1;
}
4. 整体代码
#include<iostream>
#include<string>
#include<vector>
using namespace std;
vector<int> getNext(string str) {
// 每个位置字符串的前缀与后缀最大匹配长度,不包含整串
vector<int> next(str.size());
next[0] = -1; //人为规定,0号位置的值是-1
next[1] = 0;
int i = 2; // 从2开始遍历str
// index代表当前是哪个位置的字符,在和index+1也就是i位置比较
int index = 0; // index既用来作为下标访问,也作为值
while (i < next.size()) {
// str[i-1]代表后缀开始的位置, str[index]代表前缀开始的位置
// index保存了上一次匹配的最大长度, str[index]代表了当前前缀位置, 可以通过这个来进行加速匹配
if (str[i - 1] == str[index]) {
// 如果str[i-1](后缀待匹配的字符) 等于 str[index](前缀待匹配的字符)
// next数组i位置的值 直接等于上次最大匹配长度+1
next[i++] = ++index;
}
else if (index > 0) {
// 后缀与前缀没有匹配成功, 并且index还可以往前找 next[index]的前缀, 也就是找当前前缀的前缀开始位置
index = next[index];
}
else{
// index=0, 没有前缀了, 长度记为0
next[i++] = 0;
}
}
return next;
}
int getIndex(string str1, string str2) {
vector<int> next = getNext(str2);
int i = 0;
int j = 0;
while (i < str1.size() && j < str2.size()) {
if (str1[i] == str2[j]) {
i++;
j++;
}else if (next[j] == -1) {
i++;
}else{
j = next[j];
}
}
if (j == str2.size()) {
return i - j;
}
return -1;
}
int main() {
// 在str1中查找有没有子串str2
string str1 = "abbcabcccc";
string str2 = "abcabc";
//cin >> str1 >> str2;
int index = getIndex(str1, str2);
cout << index;
return 0;
}
5. 关于KMP的相关题目
最少添加字符数
给定一个字符串str,只能在str的后面添加字符,生成一个更长的字符串,更长的字符串需要包含两个str,且两个str开始的位置不能一样。求最少添加多少个字符。
输入描述:
输入一行,表示原字符串
输出描述:
输出一个整数,表示所需添加最少字符数
示例1
输入
123123
输出
3
示例2
输入
11111
输出
1
思路
- 根据题意,分三种情况
- 情况一:一个字符,答案是1,加上一个字符就可以了
- 情况二:两个字符,判断一下这两个字符是不是一样的,如果相同,答案就是字符串的长度,因为需要再加上本字符串,如果不同答案就是1,只用把第一个字符加上就可以了
- 情况三:多个字符,字符串最后一个位置的next数组,因为next数组的含义是前缀与后缀的最大匹配长度。所以答案是字符串长度减
next[str.length()]
再减1
#include<iostream>
#include<vector>
#include<string>
using namespace std;
int getNext(string str) {
vector<int> next(str.size());
next[0] = -1; //人为规定,0号位置的值是-1
next[1] = 0;
int i = 2; // 从2开始遍历str
int val = 0; // val既用来作为下标访问,也作为值
while (i < next.size()) {
if (str[i - 1] == str[val]) {
next[i++] = ++val;
}
else if (val > 0) {
val = next[val]; // 取出前一个next数组的值
}
else {
next[i++] = 0;
}
}
return next[str.size() - 1];
}
int main() {
string str;
cin >> str;
int ans = 0;
if (str.size() == 0) {
cout << 0;
return 0;
}
else if (str.size() == 1) {
ans = str.size() + str.size();
}
else if (str.size() == 2) {
ans = str[0] == str[1] ? str.size() + 1 : str.size() + str.size();
}
else {
int next = getNext(str);
ans = str.size() + str.size() -1 - next;
}
ans -= str.size();
cout << ans;
return 0;
}
推荐文章