字符串匹配算法

来源:刘毅
链接:https://subetter.com/

1、蛮力法O (n*m)

如果每个匹配位置复杂度为常数,可以达到O(n)。
在这里插入图片描述

2、KMP O(n+m)

在这里插入图片描述
next数组
在这里插入图片描述
在这里插入图片描述
KMP改进:
P[i]和P[j]相同时,没有比的必要,去前面找。
在这里插入图片描述

#include <iostream>
#include <string>

using namespace std;

/* P 为模式串,下标从 0 开始 */
void GetNext(string P, int next[])
{
    int p_len = P.size();
    int i = 0;   // P 的下标
    int j = -1;  
    next[0] = -1;

    while (i < p_len - 1)
    {
        if (j == -1 || P[i] == P[j])
        {
            i++;
            j++;
            //next[i] = j;
            //改进的KMP
            if (P[i] != P[j])
                nextval[i] = j;
            else
                nextval[i] = nextval[j];  // 既然相同就继续往前找真前缀
        }
        else
            j = next[j];
    }
}

/* 在 S 中找到 P 第一次出现的位置 */
int KMP(string S, string P, int next[])
{
    GetNext(P, next);

    int i = 0;  // S 的下标
    int j = 0;  // P 的下标
    int s_len = S.size();
    int p_len = P.size();

    while (i < s_len && j < p_len)
    {
        if (j == -1 || S[i] == P[j])  // P 的第一个字符不匹配或 S[i] == P[j]
        {
            i++;
            j++;
        }
        else
            j = next[j];  // 当前字符匹配失败,进行跳转
    }

    if (j == p_len)  // 匹配成功
        return i - j;
    
    return -1;
}

int main()
{
    int next[100] = { 0 };

    cout << KMP("bbc abcdab abcdabcdabde", "abcdabd", next) << endl; // 15
    
    return 0;
}

3、Boyer-Moore

规则1——坏字符规则: 单个字符都不成功
在这里插入图片描述
坏字符总是在最后一个(因为单个字符都比较不成功),后移位数=(m-1) - 坏字符前面出现的位置。
移动完重新从末尾开始比较,直到遇到新的坏字符。
多次比较后发现坏字符不在末尾,即已经匹配了后缀,此时进入规则2。
在这里插入图片描述
规则2.好后缀规则: 多个字符匹配,构成好后缀
在这里插入图片描述
好后缀的位置仍为m-1,需要确定最长好后缀上次出现的位置。
移动结果:

从末尾开始查找,遇到坏字符,回到规则1。
在这里插入图片描述
坏字符表和好后缀表
与next数组一样,只与模式串有关,计算过程如下。
计算后缀数组。

void suffixes(char *pat, int m, int *suff)
{
	suff[m - 1] = m;
	for (i = m - 2;i >= 0;--i){
		q = i;
		while (q >= 0 && pat[q] == pat[m - 1 - i + q])
			--q;
		suff[i] = i - q;
	}
}

BMH算法

BM算法的核心在于两个启发式算法,一个叫做坏字符(bad character),一个叫做好后缀(good suffix)。
BMH(Boyer-Moore-Horspool)算法是对BM算法的改进算法。经统计分析,在字符串搜索过程中,遇到坏字符的概率要远远大于好后缀的情况,所以在实际使用时,只使用坏字符表也有很好的效率。
BMH它不再像BM算法一样关注失配的字符(好后缀),它的关注的焦点在于这个坏字符上,根据这个字符在模式串P中出现的最后的位置算出偏移长度,否则偏移模式串的长度。
在这里插入图片描述

const int maxNum = 1005;
int shift[maxNum];
int BMH(const string& T, const string& P) {
    int n = T.length();
    int m = P.length();

    // 默认值,主串左移m位
    for(int i = 0; i < maxNum; i++) {
        shift[i] = m;
    }

    // 模式串P中每个字母出现的最后的下标,最后一个字母除外
    // 主串从不匹配最后一个字符,所需要左移的位数
    for(int i = 0; i < m - 1; i++) {
        shift[P[i]] = m - i - 1;
    }

    // 模式串开始位置在主串的哪里
    int s = 0;
    // 从后往前匹配的变量
    int j;
    while(s <= n - m) {
        j = m - 1;
        // 从模式串尾部开始匹配
        while(T[s + j] == P[j]) {
            j--;
            // 匹配成功
            if(j < 0) {
                return s;
            }
        }
        // 找到坏字符(当前跟模式串匹配的最后一个字符)
        // 在模式串中出现最后的位置(最后一位除外)
        // 所需要从模式串末尾移动到该位置的步数
        s = s + shift[T[s + m - 1]];
    }
    return -1;
}

sunday算法

原理:模式串必定后移,必然需要后一个字符存在于模式串。所以不存在就直接后移m,存在就对齐,看看是否满足。
在这里插入图片描述

#include <iostream>
#include <string>

#define MAX_CHAR 256
#define MAX_LENGTH 1000

using namespace std;

void GetNext(string & p, int & m, int next[])
{
    for (int i = 0; i < MAX_CHAR; i++)
        next[i] = -1;
    for (int i = 0; i < m; i++)
        next[p[i]] = i;
}

void Sunday(string & s, int & n, string & p, int & m)
{
    int next[MAX_CHAR];
    GetNext(p, m, next);

    int j;  // s 的下标
    int k;  // p 的下标
    int i = 0;
    bool is_find = false;
    while (i <= n - m)
    {
        j = i;
        k = 0;
        while (j < n && k < m && s[j] == p[k])
            j++, k++;

        if (k == m)
        {
            cout << "在主串下标 " << i << " 处找到匹配\n";
            is_find = true;
        }

        if (i + m < n)
            i += (m - next[s[i + m]]);
        else
            break;
    }

    if (!is_find)
        cout << "未找到匹配\n";
}

int main()
{
    string s, p;
    int n, m;

    while (cin >> s >> p)
    {
        n = s.size();
        m = p.size();
        Sunday(s, n, p, m);
        cout << endl;
    }

    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值