shift-and算法是基于kmp算法灵感的改进,建议先去了解kmp算法
kmp的核心是找到模式串一样的前缀后,以此信息在遍历母串的时候,做一些跳过操作,达到优化结果
const shift_and = (text, pattern) => {
/******* 模式串处理-start *******/
let p_len = pattern.length;
//code存储的是模式串中,当前字符出现过的位置
//需要注意的是,遍历字符串都是从左到右,对应存储二进制数字的低位到高位
//如'abcac'中的c 最终code数据是10100
let code = {};
//console.log(code);
for (i = 0; i < p_len; i++) {
code[pattern[i]] |= 1 << i; // 这是简写,不简写拆分开来是下面的代码
//模式串遍历到第i的字符,说明肯定是存在的,并且一定处于i位数二进制的最高位
//move是需要往左移动的i位后的二进制
// const move = 1 << i;
//通过下面一行打印可以看到,i=0时,二进制move=1;i=1时,二进制move=10;i=2时,二进制move=100...
// console.log(move.toString(2));
//通过以下二进制或运算,得到并更新字符的二进制数字标记
// code[pattern[i]] = code[pattern[i]] | move;
}
//查看处理完毕模式串后得到的二进制数据,可以打开一下三行注释,方便理解对于模式串的处理
// Object.keys(code).forEach((key) => {
// console.log(key, code[key].toString(2));
// });
/******* 模式串处理-end *******/
let s_len = text.length;
let p = 0; //初始化p
//p存储的是二进制数据,信息解释:(这个意义很难理解)
//二进制是1的位置,如第n位是1,同时第m位也是1,则前第n个字符,是母串的后缀,同时是模式串前缀,第m个字符也一样
//如p位001010的时候,前2个字符,和前4个字符是母串的后缀,同时是模式串前缀
for (let i = 0; i < s_len; i++) {
// 获取上一次的p,向左移动一位(因为正在按顺序遍历字符,i每次++,就左移动一位),
// 并且个位数添加1(因为正在遍历字符text[i])
// 拿当前的 字符预处理的二进制跟以上的p做交操作比较
p = ((p << 1) | 1) & code[text[i]];
// console.log(p.toString(2));
//理解p存储信息的意思,就可以明白以下代码的判断理由了
if (p & (1 << (p_len - 1))) return i - p_len + 1;
}
return -1;
};
console.log(shift_and("ababcac", "abcac"));
console.log(shift_and("annual_announce", "announce"));
图片转载至https://www.iteye.com/blog/wlh0706-163-com-1845919