Brute-Force算法
function indexOf(longStr, shortStr, pos) {
var i = pos || 0
/*------------------------------------*/
//若串S中从第pos(S的下标0<= pos <=StrLength(S))个字符起存在和串T相同的子串,则匹配成功。
//返回第一个这样的子串在串S中的下标;否则返回-1
var j = 0;
while (true) {
if (longStr[i + j] == void 0)
break
if (longStr[i + j] === shortStr[j]) {
j++; //继续比较后一个字符
if (shortStr[j] === void 0) {
return i
}
} else {
//重新开始新一轮的匹配
i++;
j = 0;
}
}
return -1; //串S中(第pos个字符起)不存在和串T相同的子串
}
console.log(indexOf('aadddaa', 'ddd'))
KMP算法
function overlay(str, i, cached) { // 计算每一个字符的next值
if (i <= 1) // a, ab
return cached[i] = -1
var last = cached[i - 1],
ret
if (str[last + 1] === str[i]) {
ret = last + 1
} else {
//如果不能快速比较,只能比较最前与最后
ret = str[0] === str[i] ? 0 : -1
}
return cached[i] = ret
}
function getNext(str) {//求得next数组
var prefix = '',
next = [],
cached = {}
for (var i = 0, n = str.length; i < n; i++) {
var suffix = str[i]
next[i] = overlay(prefix + suffix, i, cached)
prefix += suffix
}
return next
}
/**
*
*
* @param {string} original 长字符串
* @param {string} find 要匹配的字符串
* @param {any} once 是否匹配第一个就立即返回,默认为true,否则返回一个数组
*/
function kmp(original, find, once) {
if (once === void 0)
once = true
var ret = !once ? [] : -1
var next = getNext(find)
var j = 0;
for (var i = 0; i < original.length; i++) {
while (j > 0 && original.charAt(i) != find.charAt(j))
j = next[j - 1] + 1; //不会归零,向右移动
if (original.charAt(i) == find.charAt(j))
j++;
if (j == find.length) {
if (once) {
return i - j + 1
} else {
ret.push(i - j + 1)
}
j = next[j - 1] + 1
}
}
return ret
}
console.log(kmp("banananobanonano", "nano"))
KMP2
更清楚的getNext与getNextVal方法,这两个数组都可以用到KmpSearch中
function getNext(str) {
// 求出每个子串的前后缀的共有长度,然后全部整体后移一位,首项为定值-1,得到next数组:
//首先可以肯定的是第一位的next值为0,第二位的next值为1,后面求解每一位的next值时,
//根据前一位的next值对应的字符与当前字符比较,相同,在前一位的next值加1,
//否则直接让它与第一个字符比较,求得共有长度
//比如说ababcabc
var next = [0] //第一个子串只有一个字母,不用比较,没有公共部分,为0
for (var i = 1, n = str.length; i < n; i++) {
var c = str[i]
var index = next[i - 1]
if (str[index] === c) { // a, a
next[i] = index + 1
} else {
next[i] = str[0] === c ? 1 : 0 //第一次比较a, b
}
}
// [0, 0, 1, 2, 0, 1, 2, 0]
next.unshift(-1)
next.pop();
// -1, 0 , 0, 1,2 ,0,1,2
return next
}
function getNextVal(str) {
//http://blog.csdn.net/liuhuanjun222/article/details/48091547
var next = getNext(str)
//我们令 nextval[0] = -1。从 nextval[1] 开始,如果某位(字符)与它 next 值指向的位(字符)相同,
//则该位的 nextval 值就是指向位的 nextval 值(nextval[i] = nextval[ next[i] ]);
//如果不同,则该位的 nextval 值就是它自己的 next 值(nextvalue[i] = next[i])。
var nextval = [-1]
for (var i = 0, n = str.length; i < n; i++) {
if (str[i] === str[next[i]]) {
nextval[i] = nextval[next[i]]
} else {
nextval[i] = next[i]
}
}
return nextval
}
/**
* KMP 算法分三分,第一步求next数组,第二步求nextval数组,第三步匹配
* http://blog.csdn.net/v_july_v/article/details/7041827
*
* 前两步的求法
* http://blog.csdn.net/liuhuanjun222/article/details/48091547
*
*/
function KmpSearch(s, p) {
var i = 0;
var j = 0;
var sLen = s.length
var pLen = p.length
var next = getNextVal(p)
while (i < sLen && j < pLen) {
//①如果j = -1,或者当前字符匹配成功(即S[i] == P[j]),都令i++,j++
if (j == -1 || s[i] == p[j]) {
i++;
j++;
} else {
//②如果j != -1,且当前字符匹配失败(即S[i] != P[j]),则令 i 不变,j = next[j]
//next[j]即为j所对应的next值
j = next[j];
}
}
if (j == pLen)
return i - j;
else
return -1;
}
console.log(KmpSearch('abacababc', 'abab'))
Boyer-Moore算法
Boyer-Moore算法实现必须对模式串进行预处理,得到坏字符规则和好后缀规则移动的映射表,下面代码中MakeSkip是建立坏字符规则移动的映射表,MakeShift是建立好后缀规则的移动映射表。
MakeSkip是构造数组skip[],skip[k]表示字符k距离模式串末尾的距离。
MakeShfit是构造数组shfit[],shfit[k]表示模式串的以k为边界的后缀子串的最靠近的模式子串(或最前缀子串)到模式子串末尾的距离,例如:abcab,shfit[3]=3和shfit[2]=3(即都是第一个b到末尾的距离),k=2时,后缀子串为cab,这时只有最长前缀ab,shfit[2]=3。
2.从b_idx开始查找,得到坏字符和好后缀,得到最大移动距离,移动b_idx,直至b_idx到达母串的末尾。
//http://blog.csdn.net/joylnwang/article/details/6785743
// http://blog.chinaunix.net/uid-24774106-id-2901288.html
function makeSkip(pattern) { //效率更高
var skip = {}
for (var n = pattern.length - 1, i = 0; n >= 0; n--, i++) {
var c = pattern[n]
if (!(c in skip)) {
skip[c] = i //最后一个字符串为0,倒二为1,倒三为2,重复跳过
}
}
return skip
}
function makeShift(pattern) {
var i, j, c, goods = []
var patternLen = pattern.length
var len = patternLen - 1
for (i = 0; i < len; ++i) {
goods[i] = patternLen
}
//初始化pattern最末元素的好后缀值
goods[len] = 1;
//此循环找出pattern中各元素的pre值,这里goods数组先当作pre数组使用
for (i = len, c = 0; i != 0; --i) {
for (j = 0; j < i; ++j) {
if (pattern.slice(i, len) === pattern.slice(j, len)) {
if (j == 0) {
c = patternLen - i;
} else {
if (pattern[i - 1] != pattern[j - 1]) {
goods[i - 1] = j - 1;
}
}
}
}
}
//根据pattern中个元素的pre值,计算goods值
for (i = 0; i < len; i++) {
if (goods[i] != patternLen) {
goods[i] = len - goods[i];
} else {
goods[i] = len - i + goods[i];
if (c != 0 && len - i >= c) {
goods[i] -= c;
}
}
}
return goods
}
function BMSearch(text, pattern) {
var i, j, m = 0
var patternLen = pattern.length
var textLen = text.length
i = j = patternLen - 1
var skip = makeSkip(pattern) //坏字符表
console.log(skip)
var goods = makeShift(pattern) //好后缀表
var matches = []
while (j < textLen) { //j 是给text使用
//发现目标传与模式传从后向前第1个不匹配的位置
while ((i != 0) && (pattern[i] == text[j])) {
--i
--j
}
//找到一个匹配的情况
var c = text[j]
if (i == 0 && pattern[i] == c) {
matches.push(j)
j += goods[0]
} else {
//坏字符表用字典构建比较合适
j += Math.max(goods[i], typeof skip[c] === 'number' ? skip[c] : patternLen)
}
i = patternLen - 1 //回到最后一位
}
return matches
}
console.log(BMSearch('HERE IS ASIMPLE EXAMPLE', 'EXAMPLE'))
对于进阶的单模式匹配算法而言,子串(前缀/后缀)的自包含,是至关重要的概念,是加速模式匹配效率的金钥匙,而将其发扬光大的无疑是KMP算法,BM算法使用后缀自包含,从>后向前匹配模式串的灵感,也源于此,只有透彻理解KMP算法,才可能透彻理解BM算法。
坏字符表,可以用于加速任何的单模式匹配算法,而不仅限于BM算法,对于KMP算法,坏字符表同样可以起到大幅增加匹配速度的效果。对于大字符集的文字,我们需要改变坏字符表>的使用思路,用字典来保存模式串中的字符的跳转步数,对于在字典中没有查到的字符,说明其不在模式串中,目标串当前字符直接滑动patlen个字符。
BMH算法
BM算法为了确定在不匹配的情况下最大的可移位距离而使用了两种启发,即“坏字符”启发和“好后缀”启发,两种启发均能导致最大为m的移动距离。但是,由于“好后缀”启发的预处理和计算过程都比较复杂,Horspol于1980年发表了改进与简化BM 算法的论文,即Boyer—Moore—Horspoo(BMH)算法。BMH算法在移动模式时仅考虑了“坏字符”策略。它首先比较文本指针所指字符和模式串的最后一个字符,如果相等再比较其余m一1个字符。无论文本中哪个字符造成了匹配失败,都将由文本中和模式串最后一个位置对应的字符来启发模式向右的移动。关于“坏字符”启发和“好尾缀”启发的对比,孙克雷的研究表明:“坏字符”启发在匹配过程中占主导地位的概率为94.O3 ,远远高于“好尾缀”启发。在一般情况下,BMH算法比BM有更好的性能,它简化了初始化过程,省去了计算“好尾缀”启发的移动距离,并省去了比较“坏字符”和“好尾缀”的过程。
算法思想:
搜索文本时,从后到前搜索;
如果碰到不匹配时,移动pattern,重新与text进行匹配;
关键:移动位置的计算shift_table如下图所示。
其中k为Pattern[0 ... m-2]中,使Pattern [ k ] ==Text [ i+m-1 ]的最大值;
如果没有可以匹配的字符,则使Pattern[ 0 ]==Text [ i+m ],即移动m个位置
如果与Pattern完全匹配,返回在Text中对应的位置;
如果搜索完Text仍然找不到完全匹配的位置,则返回-1,即查找失败
function BMHSearch(test, pattern) {
var n = test.length
var m = pattern.length
var shift = {}
// 模式串P中每个字母出现的最后的下标,最后一个字母除外
// 主串从不匹配最后一个字符,所需要左移的位数
for (var i = 0; i < m - 1; i++) {
shift[pattern[i]] = m - i - 1; //就是BM的坏字母表
}
// 模式串开始位置在主串的哪里
var s = 0;
// 从后往前匹配的变量
var j;
while (s <= n - m) {
j = m - 1;
// 从模式串尾部开始匹配
while (test[s + j] == pattern[j]) {
j--;
// 匹配成功
if (j < 0) {
return s;
}
}
// 找到坏字符(当前跟模式串匹配的最后一个字符)
// 在模式串中出现最后的位置(最后一位除外)
// 所需要从模式串末尾移动到该位置的步数
var c = test[s + m - 1]
s = s + (typeof shift[c] === 'number' ? shift[c] : m)
}
return -1;
}
console.log(BMHSearch('HERE IS ASIMPLE EXAMPLE', 'EXAMPLE'))
console.log(BMHSearch('missipipi', 'pip'))
Sunday算法
Sunday算法思想跟BM算法很相似,在匹配失败时关注的是文本串中参加匹配的最末位字符的下一位字符。如果该字符没有在匹配串中出现则直接跳过,即移动步长= 匹配串长度+1;否则,同BM算法一样其移动步长=匹配串中最右端的该字符到末尾的距离+1。
function sundaySearch(text, pattern) {
var textLen = text.length
var patternLen = pattern.length
if (textLen < patternLen)
return -1
var shift = {} //创建跳转表
for (i = 0; i < patternLen; i++) {
shift[pattern[i]] = patternLen - i
}
var pos = 0
while (pos <= (textLen - patternLen)) { //末端对齐
var i = pos,
j
for (j = 0; j < patternLen; j++, i++) {
if (text[i] !== pattern[j]) {
var c = text[pos + patternLen]
pos += typeof shift[c] === 'number' ? shift[c] : patternLen + 1
break
}
}
if (j === patternLen) {
return pos
}
}
return -1
}
console.log(sundaySearch('HERE IS ASIMPLE EXAMPLE', 'EXAMPLE'))
console.log(sundaySearch('missipipi', 'pip'))
Shift-And和Shift-OR算法
bitmap算法思想
32位机器上,一个整形,比如int a; 在内存中占32bit位,可以用对应的32bit位对应十进制的0-31个数,bitmap算法利用这种思想处理大量数据的排序与查询.
优点:1.运算效率高,不许进行比较和移位;2.占用内存少,比如N=10000000;只需占用内存为N/8=1250000Byte=1.25M。
缺点:所有的数据不能重复。即不可对重复的数据进行排序和查找。