1

算法导论字符串匹配章节学习笔记

1. 朴素字符串匹配算法

朴素字符串匹配算法是一种暴力匹配的算法,它的时间复杂度为O(nm),其中n和m分别为文本串和模式串的长度。该算法的思路是从文本串的第一个字符开始,依次和模式串的每一个字符进行比较,如果匹配成功,则继续比较下一个字符,否则从文本串的下一个字符开始重新匹配。

function simple_match(s, p) {
    const n = s.length;
    const m = p.length;

    if(n < m) {
        return -1;
    }

    for(let i = 0; i < n - m; i++) {
        if(s.substr(i, m) === p) {
            return i;
        }
    }

    return -1;
}

2. Rabin-Karp字符串匹配算法

Rabin-Karp字符串匹配算法是一种基于哈希的字符串匹配算法,它的时间复杂度为O(n+m),其中n和m分别为文本串和模式串的长度。该算法的思路是先计算出模式串的哈希值,然后依次计算文本串中每个长度为m的子串的哈希值,如果哈希值相等,则比较子串和模式串是否相等,如果相等,则匹配成功,否则继续计算下一个子串的哈希值。

Rabin-Karp算法的优点是可以在O(1)的时间内计算出子串的哈希值,因此比较快。但是由于哈希值可能会冲突,因此需要在比较子串和模式串是否相等时进行进一步的比较,因此在最坏情况下,时间复杂度仍然为O(nm)。

function rabin_karp_match(s, p) {
    const n = s.length;
    const m = p.length;
    if(n < m) {
        return -1;
    }
    
    const base = 26;  //字符集a-z,所以用26进制
    const q = 997;    //q要大于m
    const h = Math.pow(base, m-1) % q;
    let shash = 0, phash = 0;
    // 计算字符串初始值
    for(let i = 0; i < m; ++i) {
        shash = (shash * base + (s.charCodeAt(i) - 'a'.charCodeAt(0))) % q;
        phash = (phash * base + (s.charCodeAt(i) - 'a'.charCodeAt(0))) % q;
    }

    for(let i = 0; i < n - m; ++i) {
        if(shash === phash && s.substr(i, m) === q) {
            return i;
        }

        if(i < n  - m - 1) {
            shash = (shash - (s.charCodeAt(i) - 'a'.charCodeAt(0)) * h) * base;
            shash = (shash + (s.charCodeAt(i+m) - 'a'.charCodeAt(0))) % q;
        }
    }

    return -1;
}

3. KMP字符串匹配算法

KMP字符串匹配算法是一种基于前缀函数的字符串匹配算法,它的时间复杂度为O(n+m),其中n和m分别为文本串和模式串的长度。该算法的思路是先计算出模式串的前缀函数,然后依次比较文本串和模式串的每一个字符,如果匹配成功,则继续比较下一个字符,否则根据前缀函数的值来移动模式串的指针,从而减少比较的次数。

KMP算法的优点是可以在O(n+m)的时间内完成匹配,而且不需要计算哈希值,因此比Rabin-Karp算法更加稳定。缺点是需要额外的空间来存储前缀函数,因此空间复杂度为O(m)。

function kmp_matching(s, p) {
    const n = s.length;
    const m = p.length;
    const next = getNext(p);
    let j = 0;
    for(let i = 0; i < n; i++) {
        while(j > 0 && s[i] !== p[j]) {
            j = next[j-1];
        }

        if(s[i] === p[j]) {
            j++;
        }

        if(j === m) {
            return i - m + 1;
        }
    }

    return -1;
}

function getNext(p) {
    let m = p.length;
    let next = new Array(m).fill(0);
    let j = -1;
    for(let i = 0; i < m; i++) {
        while(j >= 0 && p.charAt(i) !== p.charAt(j+1)) {
            j = next[j-1];
        }
        if(p.charAt(i) === p.charAt(j+1)) {
            j++;
        }
        next[i] = j;
    }

    return next;
}

4. 有限自动机字符串匹配算法

有限自动机字符串匹配算法是一种基于有限自动机的字符串匹配算法,它的时间复杂度为O(n),其中n为文本串的长度。该算法的思路是先构建出模式串的有限自动机,然后依次扫描文本串中的每个字符,并根据有限自动机的状态转移函数来移动指针,从而完成匹配。

有限自动机算法的优点是可以在O(n)的时间内完成匹配,而且不需要计算哈希值,因此比Rabin-Karp算法更加稳定。缺点是需要额外的空间来存储有限自动机,因此空间复杂度为O(m^3)。

function finiteAutomatonStringMatching(text, pattern) {
    const n = text.length;
    const m = pattern.length;
    const gather = Array.from(new Set(text + pattern)) ;
    const automaton = buildFiniteAutomaton(pattern, gather);
    let j = 0;
    for(let i = 0; i < n; i++) {
        j = automaton[j][text.charAt(i)];
        if(j == m) {
            return i - m + 1;
        }
    }
    return -1;
}

function buildFiniteAutomaton(pattern, gather) {
    const m = pattern.length;
    const n = gather.length;
    
    const automaton = new Array(m+1).fill(null).map(
        () => gather.reduce((ret, c) => {ret[c] = 0; return ret;}, {})
    );
    automaton[0][pattern[0]] = 1;
    for(let q = 1; q < m+1; q++) {
        for(let j = 0; j < n; j++) {
            let k = Math.min(m, q+1);
            let base = `${pattern.substr(0, q)}${gather[j]}`;
            while(pattern.substr(0, k) !== base.substr(base.length -k, k)) {
                --k;
            }
            automaton[q][gather[j]] = k;
        }
    }

    return automaton;
}

https://segmentfault.com/a/1190000020917678


牛刀杀鸡
3 声望0 粉丝