前言
KMP算法是什么?
作为一个学习计算机或者从事计算机工作的人来说,数据结构与算法几乎是我们必须要了解甚至精通的学科,而学数据结构与算法时总有几个难点,KMP算法就是其中一个,这里我按照我的思路给大家详细讲一下。
KMP算法是一个性能优秀的字符串模式匹配算法,这里就有一点铺垫的概念要解释一下了:
- KMP算法主要用于在一个长字符串中搜索一个短字符串的位置,一般我们把这个要搜索的短字符串称为模式串,搜索的过程称为匹配,因此字符串的子串搜索就称为模式匹配。
- Knuth-Morris-Pratt算法(简称KMP),主要以同时发现这个算法3个人的名字来命名,所以大家只要知道它叫KMP,并且用来做字符串的子串搜索就好了。
1.KMP算法的意义——暴力匹配算法的原理和弊端
1.1暴力匹配算法的原理
在一个字符串中搜索一个子串的位置,最简单的算法就是暴力匹配算法(Brute-Force,BF算法),暴力匹配算法的原理比较简单,就是循环把字符串和模式串进行逐一比较,不过暴力匹配算法的效率十分低下,所以就出现了效率更高的KMP算法,这里先给大家解释一下暴力匹配算法。
附注:暴力匹配算法有很多种实现方式,不过原理都是一样的,许多书本和网上的文章对于暴力匹配算法的解释是在同一个循环中用i和j分别代表长短两个字符串的匹配的位置,当字符失配时,i回退,j归零,这里讲的可能有点抽象,总之你们要知道暴力匹配算法有时候实现方式不同但是原理是一样的就好了,这里给出的是我认为的比较好理解的实现方式,不过这个如果你们懂了的话应该看别的实现方式也是看得懂的。
因为暴力匹配算法比较简单,这里我就直接上代码了,后面再解释:
#include <stdio.h>
//获取字符串长度函数
int length(char *s){
int i=0;
if(s!=NULL) while(s[i]) i++;
return i;
}
//暴力匹配算法 Brute-Force简称BF算法
int indexOf(char *lstr,char *str){
int i,j,m,n;
//1.获取两个字符串的长度
m = length(lstr);
n = length(str);
if(lstr==NULL||str==NULL||m==0||n==0) return -1;//两个字符串都不能为NULL
//2.如果字符串短于模式串则不用进行搜索,直接return -1;
if(m>=n){
for(i=0;i<=m-n;i++){//外循环从0到m-n
for(j=0;j<n;j++){//内循环从0到n-1
if(lstr[i+j]!=str[j]) break;
}
if(j==n) return i;//当j==n时,说明内循环里的break没有被执行,
//也就是从i开始有n个字符串刚好和短字符串匹配 ,所以return i;就是返回这个下标i
}
}
return -1;
}
int main(){
char str[] = "Hello";
char s[] = "lo";
printf("%d",indexOf(str,s));
return 0;
}
暴力匹配算法的执行过程是这样的:
假设在字符串”abdabc”中搜索子串”abc”,用m和n分别代表字符串的长度和子串的长度,这里的m=6,n=3;我们把外循环临时变量i的值从0循环到m-n,每一轮循环都把长字符串中编号为i到i+n-1的n个字符与短字符串中的n个字符进行比较。(内循环的逻辑比较简单就不解释了)。
下面模拟一下循环过程:
- 当i=0时,把长字符串的0到2位和短字符串的0到2位进行比较,如果完全匹配的话就提前结束循环,并返回i的值。
- 很显然,上面i=0时,字符串是不匹配的,所以循环继续,i自增,即i=1,再匹配
- 上面i=1时字符串也是不匹配的,所以我们就这样把i一位一位的往后移,直到匹配的子串或者i已经走到了m-n就停止循环。
…(省略i=2的情况)
当匹配到子串时,函数则会返回此时i的值,如果没有匹配到子串,函数返回-1。
1.2暴力匹配算法的弊端
暴力匹配算法之所以效率低下,主要是因为循环过程中进行了很多不必要的匹配,例如:
下面的一个例子,在一个字符串中搜索子串ABCDABD。当有一处外循环i的值能够与子串的前六个都匹配,但唯独在子串的最后一个位D不匹配,那么我们在外循环的下一次移位时,就没有必要再一位一位的移,而是可以利用已经匹配的字符串信息计算出移位数,例如下面这样,在最后一位D位失配之后我们可以直接把比较位置挪成这样:
(下面这张图片来自另外一篇博客