KMP字符串匹配算法

       这里推荐B站大佬的关于kmp算法的视频:

https://www.bilibili.com/video/BV1Px411z7Yo/?spm_id_from=333.788.videocard.0

https://www.bilibili.com/video/BV1hW411a7ys/?spm_id_from=333.788.videocard.0

       视频里讲得很直观,但是原理讲得并不是很清楚。其实kmp算法的原理也很简单,当我们要做字符串匹配的时候,如果用暴力的方法去匹配,可能会很浪费时间。这时候我们维护一个数组,记录子串的前缀。以视频中的例子说明:

0  a

0  a  b

1  a  b  a

2  a  b  a  b

0  a  b  a  b  c

       这里的前缀,长度是小于原来的字符串的。kmp之所以能够提高算法的效率,就是通过相同的前缀和后缀来减少匹配的次数。假设abcaaabcd是我们要寻找的子串,对于abceaaabc来说,当前缀和后缀相同时,最长为3,即abc。假设我们在原来的字符串匹配到子串的最后一位d(假设对于原来的字符串来说是第i位,对子串来说是第j位)的时候,发现不相等。这个时候注意,之所以能够匹配到d,是因为前面的字符都相等。对于子串来说,能在原字符串中第i位之前最多能够匹配的字符,就是最长的前缀,而这个前缀在原字符串中是作为后缀的。这也就是为什么要令前缀和后缀相同,也就是kmp算法的原理,其中蕴含递归的思想,可以好好体会一下。

#include<bits/stdc++.h>

using namespace std;

void prefix_table(char pattern[], int prefix[], int n){
	prefix[0] = 0;
	int len = 0;
	int i = 1;
	while(i < n){
		if(pattern[i] == pattern[len]){
			len++;
			prefix[i] = len;
			i++;
		}else{
			if(len > 0){
				len = prefix[len - 1];
			}else{
				prefix[i] = 0;
				i++;
			}
		}
	}
}

void move_prefix_table(int prefix[], int n){
	for(int i = n - 1; i > 0; i--)
		prefix[i] = prefix[i - 1];
	prefix[0] = -1;
}

void kmp_search(char text[], char pattern[]){
	int n = strlen(pattern);
	int m = strlen(text);
	int *prefix = new int[n];
	prefix_table(pattern, prefix, n);
	move_prefix_table(prefix, n);
	//text[i] len(text) = m;
	//pattern[j] len(pattern) = n;
	int i = 0, j = 0;
	while(i < m){
		if(j == n - 1 && text[i] == pattern[j]){
			cout << "pattern found at " << i - j << endl;
			j = prefix[j];
		}
		if(text[i] == pattern[j]){
			i++; j++;
		}else{
			j = prefix[j];
			if(j == -1){
		            i++; j++;
			}
		}
	}
}

int main(){
	/*
	char pattern[] = "ABABCABAA";
	int prefix[9];
	int n = 9;
	prefix_table(pattern, prefix, n);
	move_prefix_table(prefix, n);
	for(int i = 0; i < n; i++)
		cout << prefix[i] << " ";
	cout << endl;
	*/
	char pattern[] = "ABABCABAA";
	char text[] = "ABABABCABAABABABAB";
	kmp_search(text, pattern);
	return 0;
} 

       一开始的prefix[i]是这么定义的,假设它对应的字符串为abab,它的最长前缀和最长后缀(相同)是ab,它的值是2。可是我们在匹配的时候往往遇到这样的情况,就是匹配到最后一位b的时候发现不相同,这时我们看的是aba的前后缀。所以我们用prefix[i]来记录第i位之前字符串最长前后缀的长度,它的值为1。也就有了move_prefix_table()这个操作。

       建立prefix数组的时候也是依据前面的情况来建立的,aba的最长前缀为1,abab新添加的字符b和第一个a之后的字符是相同的,所以prefix[i] = ++len。如果不同,我们看len = prefix[len - 1]。以ABA'B'CABA'A',最后一位A之前最长前缀是3。此时相互比较的是引号内的'B'和'A'。他们之前的字符都是相等的,我们看‘B’的前一个A之前的最长前缀,它标记的位置是否能与后面匹配,以此建立prefix数组。(这一段还是参考视频更加清楚)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值