数据结构-字符串模式匹配BF和KMP算法(Javascript实现)

时间:2022-07-09 06:13:16

模式匹配是数据结构中字符串的一种基本运算,给定一个子串,即在某个字符串中找出与该子串相同的所有子串的过程。

例如,在主串S= "abcdacde" 中找出子串 T = "cd", 找到子串后返回在主串中子串所在的位置索引 [2, 5]。

一、朴素的模式匹配算法(BF)

算法思想:从目标串的的第一个字符起与模式串的第一个字符比较,若相等,则继续对字符进行后续的比较,否则目标串从第二个字符起与模式串的第一个字符重新比较,直至模式串中的每个字符依次和目标串中的一个连续的字符序列相等为止,此时称为匹配成功,否则匹配失败。
若模式子串的长度是m,目标穿的长度是n,这时最坏的情况是每遍比较都在最后出现不等,即没变最多比较m次,最多比较n-m+1遍,总的比较次数最多为m(n-m+1),因此朴素的模式匹配算法的时间复杂度为O(mn)。朴素的模式匹配算法中存在回溯,这影响到匹配算法的效率,因而朴素的模式匹配算法在实际应用中很少采用。在实际应用主要采用无回溯的匹配算法,KMP算法和BM算法均为无回溯的匹配算法。


JavaScript实现 BF 算法:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>字符串</title>
</head>
<body>
<script>
(function(){
//带回溯的朴素模式匹配算法BF
var BF = function(str,s){
	var result = [],index;
	var m = 0,n = 0;//m:主串的索引,n:子串的索引
	for(let m=0;m<str.length;m++){
		if(str[m] === s[n]){
			if(n===0){
				index = m;
			}
			n++;
		}else{
			n=0;
			index=0;
		}
		if(n>=s.length){
			result.push(index);
			n=0;//子串回溯
			m-=s.length-1;//主串回溯
			index = 0;
		}
	}
	return result;
}
var str = "ababaababcb", s ="ababc";//str:主串,s:子串
console.log(BF(str,s));

})();

</script>
</body>
</html>

二、改进的模式匹配算法(KMP)


KMP算法主要改进了主串回溯的问题,将时间复杂度降低到O(n+m)

算法伪代码:

1. 设目标串为S,模式串为T, i、j 分别为指示S和T的指针,i、j的初值均为0。

2. 重复以下操作,直到S 或者T的所有字符都比较完毕。

        a. 如果S[i] 等于 T[i],继续比较S 和T 的下一个字符;

        b. 否则将下表j 回溯到next[j] 的位置,即 j = next[j];

        c. 如果j 等于 -1 ,则将下标i 和 j 分别加 1 ,准备下一趟比较。

3. 如果 T 中所有字符都比较完毕,记录匹配的初始位置。

关于next数组,不明白的可以参考:https://blog.csdn.net/lee18254290736/article/details/77278769

关于KMP算法思想,不明白的可以参考:https://blog.csdn.net/starstar1992/article/details/54913261

KMP算法的JavaScript实现:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>字符串</title>
</head>
<body>
<script>
(function(){

var str = "ababaababcb", s ="ababc";//str:主串,s:子串
//KMP算法
var KMP = function(str,s){
	//计算next 数组
	var getNext = function(s){
		var next = [-1];
		for(let j=1;j<s.length;j++){
			next[j] = 0;
			for(let k=1;k<j;k++){
				var pre = s.substring(0,k-1), tarl = s.substring(j+1-k,j);
				//console.log(j,k,pre,tarl);
				if(pre.length>0 && pre === tarl){
					next[j] = k-1;
				}
			}
		}
		console.log(next);
		return next;
	}
	var next = getNext(s);

	var result = [],index;
	var i = 0,j = 0;//i:主串的索引,j:子串的索引
	for(let i=0;i<str.length;){
		if(str[i] === s[j]){
			if(j===0){
				index = i;
			}
			i++;
			j++;
			if(j>=s.length){
				result.push(index);
				j=0;//子串回溯
			}
			continue;
		}else{
			j=next[j];//子串回溯
		}
		if(j===-1){
			i++;
			j++;
		}
	}
	return result;
}

console.log(KMP(str,s));


})();

</script>
</body>
</html>


三、附带实现一下穷举字符串所有子串的方法

//子串穷举
var substrList = function(str){
	var result = [];
	for(let i=1;i<=str.length;i++){
		for(let j=0;j<str.length-i+1;j++){
			result.push(str.substr(j,i));
		}
	}
	return result;
}
var str = "0123456789";
console.log(substrList(str).length);