字符串匹配算法——朴素(暴力)算法和KMP算法的JS实现

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/sinat_36521655/article/details/82081256

一、朴素(暴力)算法

主字符串与模式字符串分别使用一个指针去移动匹配,根据主字符串的指针是否回溯的方式, 暴力解法也存在两种思路。

1.1 主字符串的指针不回退

在外层循环的时候,指针每次只前进一位,且不被内层循环回退,代码实现如下。

/**
 * 这种解法是保证i不动,无需回溯
 * @param mStr 主串
 * @param sStr 子串
 * */
function findIndex(mStr, sStr) {
    const mLen = mStr.length,
        sLen = sStr.length;

    if(mLen < sLen) {
        return -1;
    }

    for (let i=0; i<(mLen-sLen+1); i++) {
        for (let j=0; j<sLen; j++) {
            if (mStr[i+j] !== sStr[j]) {
                break;
            }
            if(j === sLen - 1) {
                return i;
            }
        }
    }
    return -1;
}

1.2 主字符串的指针回退

这一种思路,就是主字符串的指针随着内层模式字符串指针增加而增加,如果不匹配时,回退主字符串的指针,同时模式字符串指针置为0。代码实现如下:

/**
 * 两个指针i,j同时变
 * @param str1 主串
 * @param str2 模式串
 */
function findIndex2(str1, str2) {
    let i = 0, // 主串的位置
        j = 0, // 模式串的位置
        len1 = str1.length,
        len2 = str2.length;

    while (i < len1 && j < len2) {
        if (str1[i] === str2[j]) { // 当两个字符相同,就比较下一个
            i++;
            j++;
        } else {
            i = i - j + 1; // 一旦不匹配,i后退
            j = 0; // j归0
        }
    }
    if (j === str2.length) {
        return i - j;
    } else {
        return -1;
    }

}

二、KMP算法

网上KMP的思想的文章很多,这里就不累述了,推荐阮一峰大牛的《字符串匹配的KMP算法》;比较通俗易懂,本文的算法实现,也是基于这篇文章的思路。

首先是构建模式字符串指针的下一跳的next数组,这个数组的长度和模式字符串的长度一样,所以其下标也对应模式字符串的下标,next数组中的值表示,当前下标的字符不匹配时,模式字符串的指针的下一跳位置。按照文章思路,代码实现如下:

/**
 * 计算返回子串str的next数组
 * */
function getNext(str) {
    const next = [],
        len = str.length;
    for (let i=0; i<len; i++) {
        const subStr = str.slice(0, i);
        for(let j=0; j < i - 1 ; j++) {
            //判断前缀和后缀的情况,存储当j位不匹配时,下一跳的位置,自动更新保证最大值
            if(subStr.slice(0, j + 1) === subStr.slice(i - j - 1)) {
                next[i] = j + 1;
            }
        }
        //如果所有前缀后缀都没有相等的,下一跳就是第一位
        if(!next[i]) {
            next[i] = 0;
        }
    }
    return next;
}

这里涉及到了“前缀”字符串和“后缀”字符串的概念,也非常容易理解。比如字符串“ababa”,它的前缀字符串有[‘a’, ‘ab’, 'aba', 'abab'],它的后缀字符串有[‘a’, ‘ba’, ‘aba’, ‘baba’],在前缀字符串和后缀字符串中,他们相同且长度最长的子字符串就是‘aba’,所以对于形如‘ababax’的模式字符串,当它的‘x'字符不匹配时,模式字符串的指针的下一跳位置就是‘aba’的长度,即3。

下面就是KMP算法的实现,相比于朴素(暴力)方法,主要的变换就是模式字符串下一跳的改变,不会每次都是回退到0,然后重新匹配。具体实现如下:

/**
 * @param sourceStr 主字符串
 * @param searchStr 模式字符串
 * */
function KMP(sourceStr, searchStr) {
    const sourceLen = sourceStr.length,
        searchLen = searchStr.length,
        next = getNext(searchStr);
    //i为源字符串的指针,j为目标字符串的指针
    let i = 0,
        j = 0;

    while(i < sourceLen && j < searchLen) {
        if (sourceStr[i] === searchStr[j]) {
            ++i;
            ++j;
        } else {
            //这里用于判断当前指针的位置,如果指针已经在0了,表示模式字符串的第一位都不匹配,主字符串的指针往后移一位
            if(j === 0 ) {
                ++i;
                continue;
            }
            j = next[j];
        }
    }

    if(j === searchLen) {
        return i - j;
    } else {
        return -1;
    }
}

三、测试验证

console.log(findIndex('helloworld','owo'));
console.log(findIndex('helloworld','wow'));

console.log(findIndex2('helloworld','owo'));
console.log(findIndex2('helloworld','wow'));

console.log(KMP('helloworld','owo'));
console.log(KMP('abcbabce','abce'));
console.log(KMP('abcabce','abce'));
console.log(KMP('helloworld','wow'));

在nodejs中结果如下:

自己测试的例子通过了,别的例子不清楚,如果有问题,希望指出,谢谢

猜你喜欢

转载自blog.csdn.net/sinat_36521655/article/details/82081256