字符串匹配算法

字符串匹配算法


在主串 S 中定位子串 T 称为串的模式匹配或者串匹配,比较著名的匹配算法有BF算法和KMP算法。

BF算法

BF(Brute-Force)算法的基本思想为:从在主串 S 中指定的pos位置及子串 T 的第一个位置开始:
①如果 S[ i ] == T[ j ],则继续匹配下一个字符,即进行++i、++j
②如果 S[ i ] != T[ j ],则 i 指针回退到 ++pos 位置,j指针回退到 0

int BF(string& S, string& T,int pos=0) {
	int m = S.size();
	int n = T.size();
	int i = pos;
	int j = 0;
	while (i < m && j < n) {
		if (S[i] == T[j]) {
			++i;
			++j;
		}
		else {
			i = ++pos;
			j = 0;
		}
	}

	return j == n ? pos : -1;
}

BF算法的空间复杂度为 O(1),时间复杂度为 O(mn),m 为主串 S 的长度,n 为子串 T 的长度,但在实际中其时间复杂度大多数情况下接近 O(m + n)。

KMP算法

在执行BF算法时,主串的指针 i 在失配时回退导致时间复杂度上升,既然 i 之前的字符串我们已经遍历了,是否可以让指针 i 不回退。
我们这里的下标从 0 开始
在这里插入图片描述

由于框起来的3个部分相等,我们发现当S[ 6 ]与T[ 6 ]失配时,i 指针完全可以不用回退,只需将 j 指针回退到 3 即可。

那么当S[ i ]与T[ j ]失配时,j 需要回退到哪个位置呢。假设 j 需要 回退到第 k 个位置,k < j,此时有

t 0 t 1 . . . t k − 1 t_0t_1...t_{k-1} t0t1...tk1== s i − k . . . s i − 2 s i − 1 s_{i-k}...s_{i-2}s_{i-1} sik...si2si1

而我们又知道 i 指针之前的字符都是匹配的,因此有

t j − k . . . t j − 2 t j − 1 t_{j-k}...t_{j-2}t_{j-1} tjk...tj2tj1== s i − k . . . s i − 2 s i − 1 s_{i-k}...s_{i-2}s_{i-1} sik...si2si1

可以推出k应该满足以下条件:

t 0 t 1 . . . t k − 1 t_0t_1...t_{k-1} t0t1...tk1== t j − k . . . t j − 2 t j − 1 t_{j-k}...t_{j-2}t_{j-1} tjk...tj2tj1

即如果子串 T 的前 k(从1开始数数) 个字符与子串 T 的 j 指针之前的 k 个字符相匹配,那么只需子串指针 j 回退到 k 位置即可。

我们也发现 j 指针回退到哪个位置至于子串 T 失配的位置 j 相关,与主串 S 无关,考虑使用一个组数 next,next[ j ] 表示当 j 位置失配时指针 j 应该回退到哪个位置。下面考虑 next 数组的特殊情况,当
T[ 0 ]失配时,指针 j 已经无退可退,此时应将主串 S 的指针 i 前进一个,即 i+=1,可以使用next[ j ]=-1标记这种情况。

下面考虑怎么获得 next 数组

在这里插入图片描述

假设next[ j - 1] = k,表明子串 T 的的前 k 个字符是与 j-1 指针之前的前 k 个字符是相等的,故我们现在需要考虑T[ k ]与T[ j-1 ]的匹配情况即可:

  • 如果T[ k ] == T[ j-1 ]:表明 t 0 t 1 . . . t k t_0t_1...t_{k} t0t1...tk== t j − k − 2 . . . t j − 2 t j − 1 t_{j-k-2}...t_{j-2}t_{j-1} tjk2...tj2tj1
    因此next[ j ] = k+1,即next[ j ] = next[ j -1 ]+1
  • 如果T[ k ] != T[ j-1 ]:表明 t 0 t 1 . . . t k t_0t_1...t_{k} t0t1...tk!= t j − k − 2 . . . t j − 2 t j − 1 t_{j-k-2}...t_{j-2}t_{j-1} tjk2...tj2tj1
    在这里插入图片描述
    我们已知图中①部分是相等的,进而可以得到绿色部分②也是相等的。
    令x=next[ next[ j - 1 ] ],如果T[ x ]==T[ j - 1 ],则 j 位置的最大前后缀匹配长度为next[ x ]+1,即next[ j ] = next[ x ]+1,否则
    x= next [ next[ next[ j - 1 ] ] ],重复上面过程。
void getNext(string& T, vector<int>& next){
	int j = 1;
	int m = T.size();
	next.resize(m, 0);
	next[0] = -1;

	for (;j < m;++j)
	{
		int val = 0;
		if (next[j - 1]>=0&&T[j - 1] == T[next[j - 1]]) {
			val = next[j - 1] + 1;
		}
		else {
			int x = next[j - 1];
			while (x>=0&&T[x] != T[j - 1]) {
				x = next[x];
			}
			val = x + 1;
		}
		next[j] = val;
	}
}

int KMP(string& S, string& T, int pos = 0) {
	vector<int> next;
	getNext(T, next);
	int i = pos;
	int j = 0;
	int m = S.size();
	int n = T.size();
	while (i < m && j < n) {
		if (S[i] == T[j]) {
			++i;
			++j;
		}
		else {
			j = next[j];
			if (j < 0) {
				++i;
				j = 0;
			}
		}
	}

	return j == n ? i - n : -1;
}

计算next值需要的时间复杂度为O(n),空间复杂度为O(n),因此KMP算法整体的时间复杂度为O(m+n),空间复杂度为O(n),其中m 为主串 S 的长度,n 为子串 T 的长度。
在大部分情况下,BF算法与KMP算法的时间复杂度相差不大,只有当主串与子串存在大量部分匹配时KMP算法的时间优势才会显现出来。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值