返回

JavaScript 中五种常用的单模式字符串匹配算法

前端

字符串匹配是计算机科学中的一项基本任务,它广泛应用于各种领域,如文本搜索、自然语言处理、模式识别等。单模式字符串匹配是指在一个字符串(主串)中查找另一个字符串(模式串)。

在 JavaScript 中,有多种算法可以用于单模式字符串匹配,每种算法都有其独特的特点和效率。在本篇文章中,我们将介绍五种最常用的单模式字符串匹配算法:朴素算法、KMP 算法、BM 算法、RK 算法和 BMH 算法。

朴素算法

朴素算法是最简单的一种单模式字符串匹配算法。它的基本思想是逐个字符地比较主串和模式串,如果发现两者的某个位置上的字符不同,则模式串向右移动一位,继续比较。如果模式串中的所有字符都与主串中的字符一一对应,则匹配成功。朴素算法的时间复杂度为 O(n * m),其中 n 是主串的长度,m 是模式串的长度。

function naiveStringMatch(str, pattern) {
  for (let i = 0; i < str.length - pattern.length + 1; i++) {
    let matched = true;
    for (let j = 0; j < pattern.length; j++) {
      if (str[i + j] !== pattern[j]) {
        matched = false;
        break;
      }
    }
    if (matched) {
      return i;
    }
  }
  return -1;
}

KMP 算法

KMP 算法是 Knuth-Morris-Pratt 算法的简称,它是一种改进朴素算法的算法。KMP 算法在预处理阶段计算出一个 next 数组,next[i] 表示模式串中第 i 个字符的下一个匹配位置。这样在匹配过程中,当模式串中的某个字符不匹配时,可以根据 next 数组快速跳到下一个匹配位置,从而提高匹配效率。KMP 算法的时间复杂度为 O(n + m),其中 n 是主串的长度,m 是模式串的长度。

function kmpStringMatch(str, pattern) {
  const next = computeNext(pattern);
  let i = 0;
  let j = 0;
  while (i < str.length) {
    if (str[i] === pattern[j]) {
      i++;
      j++;
    } else {
      if (j > 0) {
        j = next[j - 1];
      } else {
        i++;
      }
    }
    if (j === pattern.length) {
      return i - j;
    }
  }
  return -1;

  function computeNext(pattern) {
    const next = [0];
    let i = 1;
    let j = 0;
    while (i < pattern.length) {
      if (pattern[i] === pattern[j]) {
        next[i] = j + 1;
        i++;
        j++;
      } else {
        if (j > 0) {
          j = next[j - 1];
        } else {
          next[i] = 0;
          i++;
        }
      }
    }
    return next;
  }
}

BM 算法

BM 算法是 Boyer-Moore 算法的简称,它是一种改进朴素算法的算法。BM 算法在预处理阶段计算出一个 last 数组,last[c] 表示模式串中最后一个出现字符 c 的位置。这样在匹配过程中,当模式串中的某个字符不匹配时,可以根据 last 数组快速跳到下一个可能匹配的位置,从而提高匹配效率。BM 算法的时间复杂度为 O(n * m),其中 n 是主串的长度,m 是模式串的长度。

function bmStringMatch(str, pattern) {
  const last = computeLast(pattern);
  let i = pattern.length - 1;
  let j = pattern.length - 1;
  while (i < str.length) {
    if (str[i] === pattern[j]) {
      if (j === 0) {
        return i;
      }
      i--;
      j--;
    } else {
      if (last[str[i]] !== -1) {
        i = i - j + last[str[i]];
        j = pattern.length - 1;
      } else {
        i += pattern.length;
        j = pattern.length - 1;
      }
    }
  }
  return -1;

  function computeLast(pattern) {
    const last = {};
    for (let i = 0; i < pattern.length; i++) {
      last[pattern[i]] = i;
    }
    return last;
  }
}

RK 算法

RK 算法是 Rabin-Karp 算法的简称,它是一种使用哈希函数进行字符串匹配的算法。RK 算法在预处理阶段计算出模式串的哈希值,然后在匹配过程中逐个字符地计算主串的哈希值,并与模式串的哈希值进行比较。如果两个哈希值相等,则进一步比较主串和模式串的字符,以确认匹配是否成功。RK 算法的时间复杂度为 O(n + m),其中 n 是主串的长度,m 是模式串的长度。

function rkStringMatch(str, pattern) {
  const patternHash = computeHash(pattern);
  let windowHash = computeHash(str.substring(0, pattern.length));
  let i = pattern.length;
  while (i < str.length) {
    if (windowHash === patternHash) {
      if (str.substring(i - pattern.length, i) === pattern) {
        return i - pattern.length;
      }
    }
    windowHash = (windowHash - str[i - pattern.length] * Math.pow(26, pattern.length - 1)) * 26 + str[i];
    i++;
  }
  return -1;

  function computeHash(str) {
    let hash = 0;
    for (let i = 0; i < str.length; i++) {
      hash = hash * 26 + str[i].charCodeAt() - 'a'.charCodeAt();
    }
    return hash;
  }
}

BMH 算法

BMH 算法是 Boyer-Moore-Horspool 算法的简称,它是一种改进 BM 算法的算法。BMH 算法在预处理阶段计算出一个 skip 数组,skip[c] 表示模式串中字符 c 上一次出现的位置。这样在匹配过程中,当模式串中的某个字符不匹配时,可以根据 skip 数组快速跳到下一个可能匹配的位置,从而提高匹配效率。BMH 算法的时间复杂度为 O(n * m),其中 n 是主串的长度,m 是模式串的长度。

function bmhStringMatch(str, pattern) {
  const skip = computeSkip(pattern);
  let i = pattern.length - 1;
  let j = pattern.length - 1;
  while (i < str.length) {
    if (str[i] === pattern[j]) {
      if (j === 0) {
        return i;
      }
      i--;
      j--;
    } else {
      if (skip[str[i]] !== -1) {
        i = i - j + skip[str[i]];
        j = pattern.length - 1;
      } else {
        i += pattern.length;
        j = pattern.length - 1;
      }
    }
  }
  return -1;

  function computeSkip(pattern) {
    const skip = {};
    for (let i = 0; i < pattern.length - 1; i++) {
      skip[pattern[i]] = pattern.length - i - 1;
    }
    return skip;
  }
}