• [C++] [算法] KMP算法


    KMP串匹配算法是一个经典的算法。

    传统BF算法是传统的字符串匹配算法。很好理解。叶实现。但时间复杂度太高。

    本文将从字符串模式字符串被称为。为了匹配字符串被称为主弦。

    KMP配时能够少移动从串的位置,从而保持主串的索引不移动。


    1 原理


    如上图所看到的,假设在从串中有A=B,然后在匹配的时候,发现B后面的字符与X后面的字符不匹配,又因为B=X,因此,就有X=A,那么。在下次比較的时候就不用回溯了,直接比較X后面的字符与A后面的字符。

    这样就行使遍历主串的索引不移动,仅仅移动从串的索引。因此。时间复杂度就是O(P.size())。


    2 实现

    问题是,怎样在匹配之前获知A=B这一信息呢?也就是,当X后面的字符与B后面的字符不相等时,遍历从串的索引怎样移动呢?

    为此,引入了next数组,它可以保存A=B这一信息。

    比方ababd,它的next数组就是:-1,-1,0,1,-1。

    有人或许要问。这个结果与《算法导论》上的结果不一样啊,这是由于《算法导论》上的字符串的索引是以1開始,因此。next数组的值是以0開始,在这里。数组的索引以0開始。因此,next数组的值是以-1開始。

    我们先来学会怎样使用next数组。之后再来看怎样得到next数组。

    已知next数组,就能够得到kmp算法的匹配函数:

    int kmp(const string& str1, const string& str2)
    {
    	if(str1.size() < str2.size()) {
    		return -1;
    	}
    
    	int j = -1;
    	for(int i = 0; i < str1.size(); ++i) {
    		while(str1[i] != str2[j + 1] && j >= 0) {
    			j = next[j];
    		}
    		if(str1[i] == str2[j + 1]) {
    			++j;
    		}
    		if(j == str2.size() - 1) {
    			return i - j;
    		}
    	}
    	return -1;
    }

    当中str1是主串。str2是从串。i用于遍历主串。j用于遍历从串。将j后面的一个字符与i位置的字符进行比較,假设不相等,而且j >= 0,就将j = next[j]。这里为什么要推断j >= 0呢?当j == -1时,说明str1[i] != str2[0]。那么。就不能运行j = next[j],而应该直接++i。因此,j == -1是str1[i] != str2[j+1]的特殊情况,它要单独提出来。因此,假设跳出了while循环。就有两种情况,一种是str[i] == str2[j+1],还有一种是j == -1,假设str1[i] == str2[j+1]。就比較下一个。运行++j。

    比方,上面的ababd,next数组为-1,-1,0,1,-1。主串为abaababd。

    比較过程为:

    abaababd

    ababd

    (1) 開始i = 0, j = -1。str1[0] == str2[0],++i, ++j => i = 1, j = 0。

    (2) i = 1, j = 0。str1[1] == str2[1],++i, ++j => i = 2, j = 1。

    (3) i = 2, j = 1。str1[2] == str2[2]。++i, ++j => i = 3, j = 2。

    (4) i = 3, j = 2。str1[3] != str2[3] && j >= 0。j = next[j] = next[2] = 0 => i = 3, j = 0。

    (5) i = 3, j = 0。

    str1[3] != str2[1] && j >= 0。j = next[j] = next[0] = -1 => i = 3, j = -1。

    (6) i = 3, j = -1。

    str1[3] == str2[0]。++i, ++j => i = 4, j = 0。

    (7) i = 4, j = 0。str1[4] == str2[1],++i, ++j => i = 5, j = 1。

    (7) i =5, j = 1。str1[5] == str2[2],++i, ++j => i = 6, j = 2。


    (7) i = 6, j = 2。str1[6] == str2[3],++i, ++j => i = 7, j = 3。

    (7) i = 7, j = 3。str1[7] == str2[4]。++i, ++j => i = 8, j = 4。


    此时j == ababd.size() - 1,也就是从串比較完毕了。因此,找到了一个匹配串。此时i指向主串中最后匹配的那个字符。而j指向从串中最后匹配的那个字符。

    从而,i - j就得到匹配的串在主串中的起始索引。


    3 分析

    因此。KMP的实现方式是:


    从图中能够看到,在从串中,0~j与主串中的i-j-1~i-1一样,然而。当比較主串中的i与从串中的j+1时,发现不匹配,此时,因为从串中的0~next[j]与j-next[j]~j一样,于是有:

    从串中的0~next[j]与主串中的i-next[j]-1~i-1一样。因此。能够直接将j = next[j],直接将j跳到下次匹配的位置。而i不变。

    因此,next[j]的意义是:因为从串中0~j子串中。前面一部分和后面一部分一样。于是,当0~j与主串匹配而j+1位置不匹配时,下次匹配j的位置。

    所以。開始循环之前。没有匹配串,j的值就是-1,而《算法导论》中,字符串以1開始,那么,没有匹配串时,j的值就是0。


    4 next数组的产生

    知道了怎样使用next数组,然后就是怎样获得next数组了。

    void get_next(const string& str, vector<int> &next)
    {
    	next[0] = -1;
    	int j = -1;
    
    	for(int i = 1; i < str.size(); ++i) {
    		while(str[i] != str[j + 1] && j >= 0) {
    			j = next[j];
    		}
    		if(str[i] == str[j + 1]) {
    			++j;
    		}
    		next[i] = j;
    	}
    }

    能够看出,next数组的生成跟kmp算法的实现方式类似。

    事实上,从next数组的意义就能够看出。next数组的生成方式也是一个字符串的匹配过程。

    假设str[i] == str[j+1],那么str中0~j+1与i-j-1~i一样,再将++j,变0~j与i-j~i等同。然后,next[i] = j。

    版权声明:本文博主原创文章,博客,未经同意不得转载。

  • 相关阅读:
    Automatically Display Menu on Hover
    WPF自学教程系列1:如何将WPF空间嵌套到Form窗口?
    NET中的内存管理,GC机制,内存释放过程. 转载
    C++ 初始化和赋值的区别
    2.尽量用const, enum, inline代替#define Prefer const, enum, inline to #define.
    1.视C++为一个语言联邦 View C++ as a federation of languages
    C++内存对齐
    C++ class和struct的区别
    redhat AS5 Samba服务配置
    windowsXP & 2003 加固
  • 原文地址:https://www.cnblogs.com/hrhguanli/p/4904311.html
Copyright © 2020-2023  润新知