//3个字符串匹配算法
var j1=require('./1')
var char2int=j1.char2int
var R=4 //包含a,b,c,d
var pattern='abababac'
var M=pattern.length
var N=300
var txt=j1.getWords(N,N,N,R).join('') //随机生成一个长度N*N的字符串
console.log('indexOf-->',ind=txt.indexOf(pattern));
/*KMP算法:
txt[i]和pattern[j]比较后,不管匹配成功还是失败,下一次都比较txt[i+1]和pattern[jj]。而jj=dfa[char2int(txt[i])-97][j](为了表达方便用jj代替j)。所有核心是构造二维数组dfa。
dfa[?,j](j>2)这一列表示某字符串str的前j-1个字符与pattern前j-1个字符匹配成功后,
再用?(字母表任何一个)和pattern[j]比较后,应该用pattern[dfa[?,j]]和str[j+1]比较。
dfa[?,j]和dfa[?,Y]应该一样(Y是pattern前j-1个字符的最长公共前缀缀长度,简称公共长度),
除了dfa[char2int(pattern[j])-97][j]这个特殊值。因为pattern[:Y]和pattern[j-Y:j]一样,
令Z=pattern前Y个字符的公共长度,则pattern[:Z]和pattern[Y-Z:Y]一样,所以pattern[:Z]和pattern[j-Z:j]一样,
所以pattern[:j]+?的公共长度大于等于pattern[:Y]+?的公共长度,注意这里不是pattern[:j+1]和pattern[:Y+1],
但pattern[:j]+?的公共长度大于pattern[:Y]+?的公共长度显然会导致矛盾,这就是代码dfa[i][j]=dfa[i][X]的含义。
令k=char2int(pattern[j])-97,则dfa[k][j]=dfa[k][X]。而dfa[k][j]正是pattern[:j+1]的公共长度,
这就是代码X=dfa[char2int(pattern[j])-97][X]的含义。遇到特殊值的证明类似。
*/
function KMPSearch(){
let dfa=[]
//构造确定有限状态自动机dfa
for(let i=0;i<R;i++){dfa[i]=[0]}
dfa[char2int(pattern[0])-97][0]=1
let X=0
for(let j=1;j<M;j++){
for(var i=0;i<R;i++){dfa[i][j]=dfa[i][X]}
dfa[char2int(pattern[j])-97][j]=1+j
X=dfa[char2int(pattern[j])-97][X]
}
//console.log(JSON.stringify(dfa))
for(let i=0,j=0;i<txt.length;i++){
j=dfa[char2int(txt[i])-97][j]
if(j==M){return i-M+1}
}
return -1
}
/*Boyer-Moore算法:
找到字母表里每个字符在pattern里最右边的索引,匹配失败后,
比如在txt的'c'失败,则向右移动pattern使这个'c'与pattern最右边的'c'对齐,再从右边第一个字符开始匹配。
如果对齐过程需要将pattern向左移动,则不用对齐,只需将pattern向右移动一位再重新开始匹配。
时间复杂度:一般情况N/M,最坏情况NM,如txt是'abab****',pattern是'aabababab'。
空间复杂度R,因为使用了right数组。*/
function BoyerMooreSearch(){
let right=[]
for(let i=0;i<R;i++){right[i]=-1}
for(let i=0;i<M;i++){
let j=char2int(pattern[i])-97
if(j>-1){right[j]=i}
}
//console.log(right)
var skip=0
for(let i=0;i<txt.length-(M-1);i=i+skip){
skip=0
for(let j=M-1;j>=0;j--){
let currentChar=txt[i+j]
if(currentChar!=pattern[j]){
skip=j-right[char2int(currentChar)-97]
//每次都应该向右移动,但可能出现currentChar在j右边的情况(此时一定有多个currentChar),所以特殊处理
skip=skip<0?1:skip
break
}
}
if(skip==0){
return i
}
}
return -1
}
/*Rabin-Karp(指纹字符串查找)算法:
设pattern长度是len。计算出pattern的散列值patternHash和txt前len位的散列值txtHash,
相等的情况调用check;从txt[1]开始,对每一个txt[i]用txtHash减去txt[i-1]*RM,作用是去掉这个字符的散列值,
结果乘以R再加上txt[i+len-1]的散列值,这样就从txt[i-1:i+len-2]的散列值得到了txt[i:i+len-1]的散列值。
关于check有两种情况。
1:使用拉斯维加斯算法,正常调用check,从第i位开始遍历检查完整个pattern。
2:使用蒙特卡洛算法,取一个非常大的Q,使得散列值冲突的概率极低,这样不需要check。
时间复杂度:一般情况7N,最坏情况7N。空间复杂度1。*/
function check(i){return true}
function RabinKarpSearch(){
let Q=Math.pow(2,31)-1 //取一个大素数即可。这里随便取的,应该不是素数
var RM=Math.pow(R,M-1)
var patternHash=0
var txtHash=0
for(let i=0;i<M;i++){
patternHash=(patternHash*R+char2int(pattern[i])+Q)%Q
txtHash=(txtHash*R+char2int(txt[i])+Q)%Q
}
if(txtHash==patternHash&&check(0)){return 0}
for(let i=1;i<txt.length-(M-1);i++){
//一开始写成txtDeleteFirstHash=(patternHash-××××××,找了半个小时bug
txtDeleteFirstHash=(txtHash-(char2int(txt[i-1])*RM)%Q+Q)%Q
txtHash=(txtDeleteFirstHash*R+char2int(txt[i+M-1])+Q)%Q
if(txtHash==patternHash&&check(i)){return i}
}
return -1
}
/*Sunday算法:
用txt的i和pattern[0]开始一一匹配,匹配失败用txt[i+M]去right里查找,找到的值即指针(当前还在i)需要移动的长度,
即如果找到j,则用txt[i+j]和pattern[0]开始一一匹配。
令c=txt[i+M],如果c不在pattern里需要移动M+1,c在就使txt[i+M]和pattern最右边的c对齐,然后从头开始匹配。
*/
function SundaySearch(){
let right=[]
for(let i=0;i<R;i++){right[i]=M+1}
for(let i=0;i<M;i++){
let j=char2int(pattern[i])-97
if(j>-1){right[j]=M-i}
}
//console.log(right)
let i=0
while(i<=txt.length-M){
let j=0
while(txt[i+j]==pattern[j]&&j++<M){
}
if(j>=M){return i}
else if(i+M>=txt.length){break} //txt[i+M]会越界
else{i=i+right[char2int(txt[i+M])-97]}
}
return -1
}
console.log('BoyerMooreSearch-->',BoyerMooreSearch())
console.log('KMPSearch-->',KMPSearch())
console.log('RabinKarpSearch-->',RabinKarpSearch())
console.log('SundaySearch-->',SundaySearch())
//以下1.js,修改了getWords,可以控制字幕表的长度
exports.getWords=function getWords(maxcount=20,minlen=0,maxlen=11,R=26){
let words=[]
for (let j=0;j<maxcount;j++){
let arr=''
let len_=minlen+Math.floor(Math.random()*(maxlen-minlen+1))
for(let l=0;l<len_;l++){
arr = arr + String.fromCharCode(97+Math.floor(Math.random()*R))
}
words.push(arr)
}
return words
}
exports.char2int=(a)=>{
return a==''?-1:a.charCodeAt()
}