• 数据结构:KMP算法的原理图解和代码解析


    本篇总结的是关于串中的KMP算法解析

    应用场景

    现给定两个串,现在要看较短的一个串是不是较长的串的子串,如果是就输出子串后面的内容,如果不是则输出Not Found

    能匹配到:

    长串:qwertabcde
    短串:abcd

    则可以在长串中找到短串的内容,则输出abcde

    匹配不到:

    长串:qwertabcde
    短串:afcd

    则无法在长串中匹配到短串的内容,则输出Not Found

    算法方案

    对于如何匹配串的问题,首先是一种暴力的方案,例如让短串的内容不断地和长串进行匹配,如果在短串和长串中对应到了,就两个同时向后移动,如果短串到头,就说明匹配成功了,如果遇到其他字符,就重新进行匹配,这就是暴力求解的方案,但是时间复杂度高,总体来说是一个O(MN)的时间复杂度

    这样的时间复杂度对于算法来说是比较高的,于是有三个大佬KnuthMorrisPratt,发明了一个著名的字符串匹配算法,因此这个算法的名字就被命名为KMP算法

    算法原理

    为了方便叙述,定义str是这里的长串,pattern是要匹配的串

    算法原理就是创建一个next数组,里面存储的是pattern中,下标为i的字符前的字符串最长相等前后缀的长度

    那什么是最长相等前后缀?用下面的例子来举例:

    假设现在patternabcab,那么对于pattern来说,它的前后缀分别有:

    前缀:{a,ab,abc,abca,abcab}
    后缀:{b,ab,cab,bcab,abcab}

    因此对于pattern来说,它的next数组可以这么表示

    在这里插入图片描述
    pattern的最后一个字符来看,它的前面的字符串是abca,而对于这个串来说的相等的前后缀只有a这一个,因此这里填入的就是a的长度也就是1

    但是这个数组有什么用?从下面这个例子来看:

    假设现在strabcabeabcabcmnpatternabcabcmn

    那么写出patternnext数组:

    在这里插入图片描述
    下面就开始进行匹配了,当匹配到ec的时候匹配失败了,此时如果是暴力算法的思路来看,需要让patternstr的第二个字符开始对齐,再重新匹配,但是对于KMP算法来说,next数组的作用就出现了

    只需要让不匹配的字符下标对应的next下标的值,回溯到pattern下标即可

    以上面的例子为例,现在是s[5]p[5]的匹配失败了,那么next[5]对应的数据是2,那么就意味着现在要让s[5]p[2]进行对齐匹配,也就是说,设匹配失败的字符下标为i,那么就要让s[i]p[next[i]]进行匹配

    在这里插入图片描述
    这样就是一个循环了,进行多次循环即可,这也就是KMP算法的核心所在

    next数组的意义:

    1. 下标为i的字符前的字符串最长相等前后缀的长度
    2. 该处字符不匹配时应该回溯到的字符的下标

    上面的next数组写法只是手算出来的,在实际算法中需要将next数组用代码实现写出来:

    void GetNext(const string& pattern, vector<int>& next)
    {
    	int i = 0, j = -1;
    	next[0] = -1;
    	while (i < pattern.size() - 1)
    	{
    		if (j == -1 || pattern[i] == pattern[j])
    		{
    			next[++i] = ++j;
    		}
    		else
    		{
    			j = next[j];
    		}
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    对于上面的代码来进行解析:

    1. 如果两个i和j的对应的字符相等,那么i和j就同步向后移动
    2. 如果不相等,就要进行回退了,回退到它们原来最长公共前后缀的地方,i指向的是后面的最长公共前后缀,j回退到前面的最长公共前后缀,如果这两个前后缀相等,那么这就组成了一个新的最长相等前后缀,就可以进行数据的写入了

    关于求出next数组后,利用这个数组求KMP算法的代码:

    int KMP(const string& str, const string& pattern, const vector<int>& next)
    {
    	int i = 0, j = 0;
    	while (i < (int)str.size() && j < (int)pattern.size())
    	{
    		if (j == -1 || str[i] == pattern[j])
    		{
    			i++, j++;
    		}
    		else
    		{
    			j = next[j];
    		}
    	}
    
    	if (j == pattern.size())
    	{
    		return i - j;
    	}
    	else
    	{
    		return -1;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    在知道next数组后,解决剩下的问题就很容易了,只需要一一进行比对,如果不满足条件就进行回溯,如果走到头就返回下标,如果不满足条件就返回-1

    完整代码

    #include 
    using namespace std;
    
    // KMP算法,给定两个字符串,用子串去匹配长字符串,如果匹配成功就输出匹配的字符串和后面的内容
    // 如果匹配不成功就输出NOT FOUND
    
    void GetNext(const string& pattern, vector<int>& next)
    {
    	int i = 0, j = -1;
    	next[i] = j;
    	while (i < pattern.size() - 1)
    	{
    		if (j == -1 || pattern[i] == pattern[j])
    		{
    			next[++i] = ++j;
    		}
    		else
    		{
    			j = next[j];
    		}
    	}
    }
    
    int KMP(const string& str, const string& pattern, const vector<int>& next)
    {
    	int i = 0, j = 0;
    	while (i < (int)str.size() && j < (int)pattern.size())
    	{
    		if (j == -1 || str[i] == pattern[j])
    		{
    			i++, j++;
    		}
    		else
    		{
    			j = next[j];
    		}
    	}
    
    	if (j == pattern.size())
    	{
    		return i - j;
    	}
    	else
    	{
    		return -1;
    	}
    }
    
    void PrintString(const string& str, int index)
    {
    	string res;
    	for (int i = index; i < str.size(); i++)
    	{
    		res += str[i];
    	}
    	cout << res << endl;
    }
    
    int main()
    {
    	// str是长字符串,pattern是要匹配的子串
    	string str, pattern;
    	cin >> str >> pattern;
    
    	// KMP算法首先计算出pattern的next数组
    	vector<int> next(pattern.size());
    	GetNext(pattern, next);
    
    	// 根据str,pattern,next数组进行匹配
    	int index = KMP(str, pattern, next);
    
    	// 得出结果
    	if (index == -1)
    	{
    		cout << "NOT FOUND" << endl;
    	}
    	else
    	{
    		PrintString(str, index);
    	}
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
  • 相关阅读:
    AUTOSAR 学习笔记(一):NXP S32K14X AUTOSAR MCAL 软件下载及安装
    大模型Prompt-Tuning技术入门
    Netty+SpringBoot 打造一个 TCP 长连接通讯方案
    购物商场项目实践
    ROS学习笔记(19):建图与定位(3)
    Unicode和UTF-8的关系
    Python压缩解压–tarfile
    WPF Frame content binding page(Using MVVM)
    JavaWeb开发之——DDL-操作表-数据类型(08)
    【网络教程】IPtables官方教程--学习笔记3
  • 原文地址:https://blog.csdn.net/qq_73899585/article/details/133493596