文章目录
概念
在a字符串中匹配b字符串所在的位置
各个算法对比
朴素算法
如上图所示 思路就是 滑动窗口 把str2当作一个滑板从左向右滑动 依次比较每一个字符串
javascript实现
/**
* @param {string} haystack
* @param {string} needle
* @return {number}
*/
var strStr = function(haystack, needle) {
if (needle === '') return 0;
let l = haystack.length;
let n = needle.length;
for (let i = 0; i < l - n + 1; i++) {
if (haystack.substring(i, i + n) === needle) {
return i;
}
}
return -1;
};
Rabin-Karp算法
霍纳法则
算法的思想是这样的
- 我们可以把字符串转化成十进制数字 他们都以一个特定的
基数d
表示 但是转化的这个数字有可能过大 所以我们对一个质数
进行求模
运算 - 滑动窗口(参考上面朴素算法)比较每一个子串的取模值 ----->因为霍纳法则 实际上下一个值可以根据上一个值来计算 时间o(1)
- 当模值一样时 因为有伪命中点的存在 所以还要进行 全等比较
为啥要选素数呢
如果选模为一个素数 使得10q恰好满足一个计算机字长 ,那么可以用单精度算术运算执行所必须的运算
javascript实现
var strStr = function (haystack, needle) {
const Q = 101;//素数
const D = 256;//基数
const N = haystack.length;
const M = needle.length;
let hashHaystackt = 0; //初始化
let hashNeedle = 0;
let h = 1;
for (let i = 0; i < (M - 1); i++) {
h = (h * D) % Q;//计算 d的m-1次方 mod q的值
for (let i = 0; i < M; i++) {
hashNeedle = (D * hashNeedle + needle[i].charCodeAt(0)) % Q;
hashHaystackt = (D * hashHaystackt + haystack[i].charCodeAt(0)) % Q;
}
for (let i = 0; i <= N - M; i++) {
if (hashNeedle === hashHaystackt) {
if (haystack.substring(i - M, i) === needle) {
return i - M
}
}
if (i < N - M) { //计算下一个hashHaystackt
hashHaystackt = (D * (hashHaystackt - haystack[i].charCodeAt(0) * h) + haystack[i + M].charCodeAt(0)) % Q;
if (hashHaystackt < 0) {
hashHaystackt += Q;
}
}
}
}
};
有限自动机
有限自动机(finite automata)亦称时序机,有限离散数字系统的抽象数学模型。一个有限自动机M由五元组(X,Y,S,δ,λ)给定,其中X,Y和S都是非空有限集,分别称为M的输入集、输出集和状态集;δ是笛卡儿积集合S×X到S的映射,称为M的下一状态函数;λ是S×X到Y的单值映射,称为M的输出函数。当δ是单值映射时,称M为确定型有限自动机;当δ是多值映射时,称M为非确定型有限自动机。有限自动机有三种功能:作为序列转换器,将输入序列变换为输出序列;作为序列识别器,识别输入的序列是否具有某种性质;作为序列产生器,产生具有所要求性质的序列
这一大堆定义比较难理解 可以配合这个leetcode的题目来看 简单理解 就是每次输入 都在不同状态之间跳转
实际上在字符串匹配这儿 个人感觉并不是一个好的算法 因为生成map表
需要耗费大量计算
其中,
∑
是一个有穷字母表,它的每一个元素称为一个输入符号;
S
是一个有限状态集,它的每一个元素称为一个状态;
f
是转换函数,定义了从 上的一个单值映射,即 ,指明当前的状态为p,当输入符号为a时,则转换到下一个状态q,q称为p的后继状态;
s0
是一个唯一的初始状态;
Z
是一个终止状态集。
在状态转移的每一步,根据有限自动机当前所处的状态和所面临的输入符号,便能唯一地确定有限自动机的下一个状态,即转换函数的值是唯一的,反映到状态转换图上,就是若 ,则任何结点的出边都有n条,且这些出边上的标记均不相同,这就是为什么我们把按上述方式定义的有限自动机称为确定的有限自动机的原因
伪代码
javascript实现
创建状态map表的实现 这个有了 就很简单了
function createStatusMap(needle) {
// let wordmap = 'abcdefghijklmnopqrstuvwxyz';
let wordmap = 'abc';
let len = needle.length;
let wordmapLength = wordmap.length;
let map = [];
for (let i = 0; i < len + 1; i++) {
let template = needle.substr(0, i); // i 状态下已存在字符
map.push([]);
for (let j = 0; j < wordmapLength; j++) { //遍历输入下一个字符
let status = i;
let output = template + wordmap[j] //组成新字符串
let k = template.length;
for (let q = 0; q < k + 1; q++) { //循环匹配 output的后缀 和 needle的前缀 改变成相应的状态
if (output.substring(q, k + 2) == needle.substring(0, k + 1 - q)) {
status = i + 1 - q;
break
}else{
status = 0
}
}
map[i][j] = status; //输出到map表
}
}
return map
}
console.log(createStatusMap('ababaca'))
Knuth-Morris-Pratt算法
假设一个文本 aaaabaac (text) 一个待匹配 aaab(pattern)
按照正常的匹配如果我们匹配到第4位 aaaa aaab显然不符合了 但是呢又因为已经匹配到三位了
所以 text 和 pattern 的指针
都需要回退三位。这样处理在某些情况下是很耗时的。
所以我们可以通过预先计算一张表 使得能够直接知道
在已经匹配n位下时 只需要重新匹配pattern的某一段即可满足要求
这样我们text端的指针就不必回退了 pattern也不必每次都重头匹配
这张表的意思就是说 当你在匹配到i的位置 i+1失配(就是不匹配了)的时候 必定有π个字符是匹配的 。
就比如説举例 已经匹配到i = 5 ababa 和 ababaca 比较
ababa
的后缀 至少有三个是ababaca
的前缀 也就是说至少已经有三个字符是匹配的了
javascript实现
//核心就在于创建这张表
function createMap(pattern) {
let len = pattern.length;
let map = [...Array(len)].map(() => 0); //初始化map;
let q = 1; //指针
let k = 0; //指针
for (; q < pattern.length; q++) {
while (k > 0 && pattern[q] !== pattern[k]) {
k = map[k - 1];
}
if (pattern[q] === pattern[k]) {//相等最长公共+1
k++;
}
map[q] = k;
}
return map
}