BF算法和KMP算法

给定两个字符串S和T,在主串S中查找子串T的过程称为串匹配(string
matching,也称模式匹配),T称为模式。这里将介绍处理串匹配问题的两种算法,BF算法和KMP算法。

BF算法

(暴力匹配算法)

BF算法简介

F(Brute Force)算法是普通的模式匹配算法,BF算法的思想就是将目标串S的第一个字符与模式串T的第一个字符进行匹配,若相等,则继续比较S的第二个字符和 T的第二个字符;若不相等,则比较S的第二个字符和T的第一个字符,依次比较下去,直到得出最后的匹配结果。

BF算法原理

首先,给定 “主串” 和 “模式串” 如下:
在这里插入图片描述
BF算法使用简单粗暴的方式,对主串和模式串进行逐个字符的比较:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
检测到字符不匹配,模式串向后挪动一位,和主串的第二个等长子串比较。
在这里插入图片描述
检测到字符不匹配,模式串继续向后挪动一位,和主串的第三个等长子串比较
在这里插入图片描述
···········不断循环上面步骤,直到匹配成功或者匹配失败(即到主串末尾结束)。

算法

int BF(char* S, char* T)
{
    int sLen = strlen(S);        //文本串长度
    int tLen = strlen(T);        //模式串长度

    int i = 0;
    int j = 0;
    while (i < sLen && j < tLen)
    {
        if (S[i] == T[j])
        {
            //①如果当前字符匹配成功(即S[i] == T[j]),则i++,j++
            i++;
            j++;
        }
        else
        {
            //②如果失配(即S[i]! = T[j]),令i = i - (j - 1),j = 0
            i = i - j + 1;
            j = 0;
        }
    }
    //匹配成功,返回模式串t在文本串s中的位置,否则返回-1
    if (j == tLen)
        return i - j;
    else
        return -1;
}

测试

#include<stdio.h>
#include <stdlib.h>
#include <string.h>

int BF(char* S, char* T)
{
    int sLen = strlen(S);        //文本串长度
    int tLen = strlen(T);        //模式串长度

    int i = 0;
    int j = 0;
    while (i < sLen && j < tLen)
    {
        if (S[i] == T[j])
        {
            //①如果当前字符匹配成功(即S[i] == T[j]),则i++,j++
            i++;
            j++;
        }
        else
        {
            //②如果失配(即S[i]! = T[j]),令i = i - (j - 1),j = 0
            i = i - j + 1;
            j = 0;
        }
    }
    //匹配成功,返回模式串t在文本串s中的位置,否则返回-1
    if (j == tLen)
        return i - j;
    else
        return -1;
}

int main()
{
    char S[] = "ABC ABCDAB ABCDABCDABDW";
    char T[] = "ABCDABD";
    int num;
    char *ch = T;
    num = BF(S,T);
    if (num >= 0)
    {
        printf("matched @: %s\n", ch);
    }
    else
    {
        printf("matched fail!\n");
    }
    return 0;
}

执行结果:

matched @: ABCDABD

总结:BF算法比较直接,是一种蛮力法,该算法最坏情况下要进行m*(n-m+1)次比较,时间复杂度为O(m*n),下面来看一个效率非常高的字符串匹配算法,即KMP算法。

KMP算法

KMP算法简介

之所以叫做KMP,是因为这个算法是由Knuth、Morris、Pratt三个提出来的,取了这三个人的名字的头一个字母。KMP算法的关键是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现就是实现一个next()函数,函数本身包含了模式串的局部匹配信息。时间复杂度O(m+n)。

简单理解就是:KMP算法是拿来处理字符串匹配的。换句话说,给你两个字符串,你需要回答,B串是否是A串的子串(A串是否包含B串)。

KMP算法原理

(参考自:看,未来的博客
首先,给定 “主串” 和 “模式串” 如下:
在这里插入图片描述
第一轮,模式串和主串的第一个等长子串比较,发现前5个字符都是匹配的,第6个字符不匹配,是一个“坏字符”:
在这里插入图片描述
这时候,我们可以有效利用已匹配的前缀 “GTGTG” 。通过观察我们可以发现,在前缀“GTGTG”当中,后三个字符“GTG”和前三位字符“GTG”是相同的:
在这里插入图片描述
在下一轮的比较时,只有把这两个相同的片段对齐,才有可能出现匹配。这两个字符串片段,分别叫做最长可匹配后缀子串和最长可匹配前缀子串。

第二轮,我们直接把模式串向后移动两位,让两个“GTG”对齐,继续从刚才主串的坏字符A开始进行比较:
在这里插入图片描述

显然,主串的字符A仍然是坏字符,这时候的匹配前缀缩短成了GTG:
在这里插入图片描述
按照第一轮的思路,我们来重新确定最长可匹配后缀子串和最长可匹配前缀子串:
在这里插入图片描述
第三轮,我们再次把模式串向后移动两位,让两个“G”对齐,继续从刚才主串的坏字符A开始进行比较:
在这里插入图片描述
·········接下来还是重复步骤,就不往写了。

next数组

什么是next数组?next数组用来干什么?
next数组是决定kmp算法快速移动的核心。

next数组的生成示例

在这里插入图片描述

说白了就是把子串拆分,然后看尾部有多少个字符重复,没有写-1,有一个就写0,有两个写1,依次类推(你也可以写0,1,2,具体想写啥看个人吧,我这里只是全部减1了,当然在代码里也要记得修改)。

有了next数组,我们就可以通过已匹配前缀的下一个位置(坏字符位置),快速寻找到最长可匹配前缀的下一个位置,然后把这两个位置对齐。

算法

void compute_prefix(const char *pattern, int next[])
{
    int i;     //前缀
    int j = -1;   //前缀尾巴
    const int m = strlen(pattern);
    next[0] = j;
    for (i = 1; i < m; i++)
    {
        while (j > -1 && pattern[j + 1] != pattern[i])  //前后缀不相等
        {
            j = next[j];
        }
        if (pattern[i] == pattern[j + 1])  //前后缀相等
        {
            j++;
        }
        next[i] = j;

    }
}

int kmp(const char *text, const char *pattern)
{
    int i;
    int j = -1;
    const int n = strlen(text);
    const int m = strlen(pattern);
    if (n == 0 && m == 0) return 0;
    if (m == 0) return 0;
    int *next = (int*)malloc(sizeof(int) * m);
    compute_prefix(pattern, next);
    for (i = 0; i < n; i++)
    {
        while (j > -1 && pattern[j + 1] != text[i]) j = next[j];
        if (text[i] == pattern[j + 1]) j++;
        if (j == m - 1)
        {
            free(next);
            return i-j;
        }
    }
    free(next);
    return -1;
}

测试

#include<stdio.h>
#include <stdlib.h>
#include <string.h>

void compute_prefix(const char *pattern, int next[])
{
    int i;     //前缀
    int j = -1;   //前缀尾巴
    const int m = strlen(pattern);
    next[0] = j;
    for (i = 1; i < m; i++)
    {
        while (j > -1 && pattern[j + 1] != pattern[i])  //前后缀不相等
        {
            j = next[j];
        }
        if (pattern[i] == pattern[j + 1])  //前后缀相等
        {
            j++;
        }
        next[i] = j;

    }
}
int kmp(const char *text, const char *pattern)
{
    int i;
    int j = -1;
    const int n = strlen(text);
    const int m = strlen(pattern);
    if (n == 0 && m == 0) return 0;
    if (m == 0) return 0;
    int *next = (int*)malloc(sizeof(int) * m);
    compute_prefix(pattern, next);
    for (i = 0; i < n; i++)
    {
        while (j > -1 && pattern[j + 1] != text[i]) j = next[j];
        if (text[i] == pattern[j + 1]) j++;
        if (j == m - 1)
        {
            free(next);
            return i-j;
        }
    }
    free(next);
    return -1;
}
int main()
{
    char text[] = "ABC ABCDAB ABCDABCDABDE";
    char pattern[] = "ABCDABD";
    char *ch = pattern;
    int i = kmp(text, pattern);
    if (i >= 0)
    {
        printf("matched @: %s\n", ch);
    }
    else
    {
        printf("matched fail!\n");
    }
    return 0;
}

执行结果:

matched @: ABCDABD
  • 9
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

别呀

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值