KMP算法详解附带练习题

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数组
  • ij作为下标,分别遍历主串str1和子串str2
  • ij都不越界的时候,有三种情况
  • 情况一:主串当前位置的字符 与 子串当前位置的字符相等,ij都自增
  • 情况二:当子串的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;
}

推荐文章

Mancher算法详解附带练习题

猜你喜欢

转载自blog.csdn.net/weixin_44839362/article/details/117134881