【数据结构第四章】- 串的模式匹配算法(BF 算法和 KMP 算法/用 C 语言实现)

news2024/11/23 8:27:52

目录

一、前言

二、BF 算法

三、KMP 算法

3.2.1 - KMP 算法的原理

3.2.2 - KMP 算法的实现

3.2.3 - KMP 算法的优化


创作不易,可以点点赞,如果能关注一下博主就更好了~ 


一、前言

子串的定位运算通常称为串的模式匹配串匹配。此运算的应用非常广泛,比如在搜索引擎、拼写检查、语言翻译、数据压缩等应用中,都需要进行串匹配。

串的模式匹配设有两个字符串 S 和 T,设 S 为主串,也称正文串;设 T 为子串,也称为模式。在主串 S 中查找与模式 T 相匹配的子串,如果匹配成功,确定相匹配的子串中的第一个字符在主串 S 中出现的位置。

著名的模式匹配算法有 BF 算法KMP 算法。

 

 


二、BF 算法

BF 算法,即暴力(Brute Force)算法,是最简单直观的模式匹配算法。

int IndexBF(const char* S, const char* T)
{
    if (S == NULL || T == NULL)  // 判断是否为空指针
        return -1;
    int i = 0;  // 主串指针
    int j = 0;  // 模式串指针
    int slen = strlen(S);
    int tlen = strlen(T);
    while (i < slen && j < tlen)
    {
        if (S[i] == T[j])
        {
            ++i;
            ++j;
        }
        else
        {
            // 回溯
            i = i - j + 1;  // i = i - (j - 1) = i - j + 1
            j = 0;
        }
    }
    if (j == tlen)  
        return i - j;  // 匹配成功
    else
        return -1;  // 匹配失败
}

 

 


三、KMP 算法

3.2.1 - KMP 算法的原理

KMP 是对 BF 改进后的算法,它由 Knuth、Morris 和 Pratt 同时设计实现,因此简称 KMP 算法。

在 BF 算法中,每当一趟匹配过程中出现字符不相等的情况,主串指针 i 回溯到下标为 i - j + 1 的位置,这是因为在模式匹配中,我们并不知道主串的具体内容,但是在不匹配的字符之前,主串 S 中的一个连续的字符序列与模式串相等,KMP 算法正是利用了已经得到的 "部分匹配" 结果对算法进行了改进

KMP 算法的原理:

  1. 在匹配的过程中,主串的指针 i 不需要回溯,只回溯模式串的指针 j

  2. S[i] != T[j] 时,模式串指针 j 回溯的下标位置由模式串的内容决定

假设主串为 ​gif.latex?%22s_1s_2%20...s_%7Bn%7D%22,模式串为 gif.latex?%22t_1t_2%20...t_%7Bm%7D%22​,在匹配过程中,当 ​ gif.latex?s_i%20%5Cne%20t_j 时,模式串指针 j 回溯到下标为 k (k < j) 的位置,则有 gif.latex?%22t_0t_1%20...t_%7Bk-1%7D%22%20%3D%20%22s_%7Bi-k%7D%20...s_%7Bi-2%7Ds_%7Bi-1%7D%22①​。

而已得到的 "部分匹配" 的结果是 gif.latex?%22t_%7Bj-k%7D%20...t_%7Bj-2%7Dt_%7Bj-1%7D%22%20%3D%20%22s_%7Bi-k%7D%20...s_%7Bi-2%7Ds_%7Bi-1%7D%22②​。

由 ① 和 ② 推得 gif.latex?%22t_0t_1%20...t_%7Bk-1%7D%22%20%3D%20%22t_%7Bj-k%7D%20...t_%7Bj-2%7Dt_%7Bj-1%7D%22③​,它们是模式串匹配失败位置前的内容中的最长公共前后缀

例如

4bcaf88a273c468d8e6e14ab01cd0301.png

 

​jgif.latex?t_0t_1...t_%7Bj-1%7D前缀后缀最长公共前后缀k
0-1
1​a0
2​aba​b​0
3​abaa、​aba、ba​a​1
4​ababa、ab、aba​b、ab、bab​​ab2

next[j] = k,则 next[j] 表示在匹配过程中,当 ​ gif.latex?s_i%20%5Cne%20t_j 时,模式串指针 j 回溯的下标位置

982ceec6a9064da9b3f14184d88e8cac.png

 

3.2.2 - KMP 算法的实现

void GetNext(const char* T, int* next)
{
    int tlen = strlen(T);
    if (tlen == 0) { return; }
    if (tlen == 1) { next[0] = -1; return; }
    if (tlen == 2) { next[0] = -1; next[1] = 0; return; }
    // 计算 next[j]
    next[0] = -1;
    next[1] = 0;
    for (int j = 2; j < tlen; ++j)
    {
        int maxlen = 0;  // 最长公共前后缀的长度
        for (int i = 1; i < j; ++i)
        {
            char* prefix = (char*)calloc(i + 1, sizeof(char));  
            char* suffix = (char*)calloc(i + 1, sizeof(char));  
            assert(prefix && suffix);
            strncpy(prefix, T, i);  // 取前缀
            strncpy(suffix, T + j - i, i);  // 取后缀
            // 判断是否为公共前后缀
            if (strcmp(prefix, suffix) == 0)
            {
                maxlen = i;
            }
        }
        next[j] = maxlen;
    }
}
​
int IndexKMP(const char* S, const char* T)
{
    if (S == NULL || T == NULL)  // 判断是否为空指针
        return -1;
    int i = 0;  // 主串指针 
    int j = 0;  // 模式串指针
    int slen = strlen(S);
    int tlen = strlen(T);
    // 获取 next 数组
    int* next = (int*)calloc(tlen, sizeof(int));  
    assert(next);
    GetNext(T, next);
    while (i < slen && j < tlen)
    {
        if (S[i] == T[j] || j == -1)
        {
            ++i;
            ++j;
        }
        else
        {
            j = next[j];  // 根据 next 数组回溯模式串指针
        }
    }
    if (j == tlen)
        return i - j;  // 匹配成功
    else
        return -1;  // 匹配失败
    return 0;
}

 

3.2.3 - KMP 算法的优化

前面定义的 next 数组在某些情况下尚有缺陷。例如模式串 和主串 匹配时,当 S[3] != T[4] 时,由 next[j] 的指示还需要进行 i = 3、j = 2; i = 3、j = 1; i = 3、j = 0 这三次比较。实际上,因为模式串中下标分别为 2、1、0 的三个字符和下标为 3 的字符相等,因此不需要再和主串中下标为 3 的字符相比较,而可以直接进行 i = 4、j = 0 的字符比较。

这就是说,若按上述定义得到 next[j] = k,而模式串中 ,则当 时,不需要再和 进行比较,而直接和 比较,换句话说,此时的 next[j] 应该和 next[k] 相同

void GetNext(const char* T, int* next)
{
    int tlen = strlen(T);
    if (tlen == 0) { return; }
    if (tlen == 1) { next[0] = -1; return; }
    if (tlen == 2) { next[0] = -1; next[1] = 0; return; }
    // 计算 next[j]
    next[0] = -1;
    next[1] = 0;
    for (int j = 2; j < tlen; ++j)
    {
        int maxlen = 0;  // 最长公共前后缀的长度
        for (int i = 1; i < j; ++i)
        {
            char* prefix = (char*)calloc(i + 1, sizeof(char));  
            char* suffix = (char*)calloc(i + 1, sizeof(char));  
            assert(prefix && suffix);
            strncpy(prefix, T, i);  // 取前缀
            strncpy(suffix, T + j - i, i);  // 取后缀
            // 判断是否为公共前后缀
            if (strcmp(prefix, suffix) == 0)
            {
                maxlen = i;
            }
        }
        next[j] = maxlen;
    }
}
​
void GetNextval(const char* T, int* next, int* nextval)
{
    int tlen = strlen(T);
    nextval[0] = -1;
    for (int j = 1; j < tlen; ++j)
    {
        if (T[j] == T[next[j]])
        {
            nextval[j] = nextval[next[j]];
        }
        else
        {
            nextval[j] = next[j];
        }
    }
}
​
int IndexKMP(const char* S, const char* T)
{
    if (S == NULL || T == NULL)  // 判断是否为空指针
        return -1;
    int i = 0;  // 主串指针 
    int j = 0;  // 模式串指针
    int slen = strlen(S);
    int tlen = strlen(T);
    // 获取 next 数组
    int* next = (int*)calloc(tlen, sizeof(int));  
    assert(next);
    GetNext(T, next);
    // 获取 nextval 数组
    int* nextval = (int*)calloc(tlen, sizeof(int));
    assert(nextval);
    GetNextval(T, next, nextval);
    while (i < slen && j < tlen)
    {
        if (S[i] == T[j] || j == -1)
        {
            ++i;
            ++j;
        }
        else
        {
            j = nextval[j];  // 根据 nextval 数组回溯模式串指针
        }
    }
    if (j == tlen)
        return i - j;  // 匹配成功
    else
        return -1;  // 匹配失败
    return 0;
}

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/450341.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

美国主机的带宽和网络速度究竟有多快?

在选择一个主机时&#xff0c;其带宽和网络速度是非常重要的考虑因素。而美国主机在带宽和网络速度方面有着明显的优势&#xff0c;成为了众多用户的首选。那么&#xff0c;美国主机的带宽和网络速度究竟有多快呢?本文将通过分析美国主机的网络基础设施和数据中心设施&#xf…

golang入门项目——打卡抽奖系统

功能介绍 用户加入群组之后&#xff0c;会在签到群组所设的签到地点进行签到和签退&#xff0c;并限制同一个设备只能签到一个用户&#xff0c;签到成功之后。会获取一定的限制在该群组使用的积分。该群组可以设置一些抽奖活动&#xff0c;用户可使用该群组内的积分来进行该群…

Python+mysql+php搭建另类免费代理池

文章目录 前言:思路&#xff1a;开干&#xff1a;php连接MySQL取ip和端口&#xff1a;效果图&#xff1a; 最后调用代理池&#xff1a;总结&#xff1a; 前言: 为什么说另类的&#xff0c;因为我完全是按照我自己的想法来的&#xff0c;比较鸡肋&#xff0c;但是能用&#xff…

短视频app开发:如何提高视频播放稳定性

简介 如今&#xff0c;短视频已经成为人们日常生活中不可或缺的一部分&#xff0c;而短视频app的开发也日益成为了人们热议的话题。在短视频app开发的过程中&#xff0c;如何提高视频播放稳定性是一个非常重要的问题。本文将从短视频源码角度出发&#xff0c;分享提高短视频ap…

如何优化语音交友app开发的搜索和匹配算法

语音交友app开发的挑战 在当今社交媒体行业中&#xff0c;语音交友app开发已经成为一个热门的领域。越来越多的人开始使用语音交友app来寻找新的朋友&#xff0c;这也为开发者们带来了许多机会。然而&#xff0c;这个领域也面临着一些挑战。其中一个最大的挑战是如何优化搜索和…

掏空腰包,日子难过,机缘转岗软件测试,这100个日夜的心酸只有自己知道...

我今年27岁&#xff0c;原本从事着土木工程相关的工作&#xff0c;19年开始有了转行的想法... 大学刚毕业那年&#xff0c;我由于学的是土木工程专业&#xff0c;自然而然的从事了和土木工程相关的工作&#xff0c;房贷、车贷&#xff0c;在经济的高压下&#xff0c;当代社会许…

大数据题目测试(一)

目录 一、环境要求 二、提交结果要求 三、数据描述 四、功能要求 1.数据准备 2.使用 Spark&#xff0c;加载 HDFS 文件系统 meituan_waimai_meishi.csv 文件&#xff0c;并分别使用 RDD和 Spark SQL 完成以下分析&#xff08;不用考虑数据去重&#xff09;。 (1)配置环境…

Java设计模式-day01

1&#xff0c;设计模式概述 1.1 软件设计模式的产生背景 "设计模式"最初并不是出现在软件设计中&#xff0c;而是被用于建筑领域的设计中。 1977年美国著名建筑大师、加利福尼亚大学伯克利分校环境结构中心主任克里斯托夫亚历山大&#xff08;Christopher Alexand…

React Native iOS打包详细步骤

一、在自己项目的iOS文件夹下新建一个文件夹取名bundle 二、将打包命令写到项目package.json文件里&#xff0c;终端执行 npm run bundle-ios 先添加如下&#xff08;注意&#xff1a;这里写的路径"./ios/bundle"就是上面bundle创建的文件夹&#xff09;&#xff1a…

C51单片机介绍

本文为学习51单片机的学习的基础&#xff0c;先介绍单片机是什么。所使用的单片机有什么资源。每一个功能的作用是什么。本文使用的是STC89C52RC 40I-PDIO40&#xff0c;故以此为基础研究学习。 C51单片机介绍 单片机的概述单片机的组成部分中央处理器程序存储器数据存储器定时…

图神经网络能做什么?

从概念上讲&#xff0c;我们可以将图神经网络的基本学习任务分为 5 个不同的方向&#xff1a; &#xff08;1&#xff09;图神 经网络方法&#xff1b; &#xff08;2&#xff09;图神经网络的理论理解&#xff1b; &#xff08;3&#xff09;图神经网络的可扩展性&#xff1b…

Git的进阶使用(二)

本篇文章旨在分享本人在学习Git时的随笔记&#x1f929; 文章目录 概述1、Git 分支1.1 主干分支1.2 其他分支1.2.1 创建分支1.2.2 查看分支1.2.3 切换分支1.2.4 删除分支 2、Git 合并2.1 主干分支2.2 其他分支2.3 合并分支 3、Git 冲突3.1 主干分支3.2 其他分支3.3 切换分支 -B…

Replika:AI智能聊天机器人

【产品介绍】 Replika&#xff0c;这个名字可能有点拗口&#xff0c;但如果你知道这是复制品Replica的同音变体&#xff0c;你即刻能明白这个产品的定位了。官方Luka公司定义它是你的AI朋友&#xff0c;默默学习你&#xff0c;最终成为你的复制品。它不像现在市面上各大厂的AI助…

《ChatGPT开发应用指南》,Datawhale开源了!

Datawhale发布 开源教程&#xff1a;HuggingLLM&#xff0c;Datawhale团队 随着ChatGPT的爆火&#xff0c;我们相信未来会有越来越多的大模型及类似OpenAI提供的服务出现&#xff0c;AI 正在逐渐平民化&#xff0c;将来每个人都可以利用大模型轻松地做出自己的AI产品。 Huggin…

【历史上的今天】3 月 23 日:网景创始人出生;FORMAC 语言的开发者诞生;PRMan 非商业版发布

整理 | 王启隆 透过「历史上的今天」&#xff0c;从过去看未来&#xff0c;从现在亦可以改变未来。 今天是 2023 年 3 月 23 日&#xff0c;在 141 年前的今天&#xff0c;1882 年 3 月 23 日&#xff0c;抽象代数之母艾米诺特&#xff08;Emmy Noether&#xff09;诞生。她的…

JUC-多线程(12. AQS-周阳)学习笔记

文章目录 1. 可重入锁1.1. 概述1.2. 可重入锁类型1.3. Synchronized 可重入实现机理 2. LockSupport2.1. LockSupport 是什么2.2. 3种线程等待唤醒的方法2.2.1 Object 的等待与唤醒2.2.2. Condition接口中的等待与唤醒2.2.3. 传统的 synchronized 和 Lock 实现等待唤醒通知的约…

本地搭建属于自己的ChatGPT:基于PyTorch+ChatGLM-6b+Streamlit+QDrant+DuckDuckGo

本地部署chatglm及缓解时效性问题的思路&#xff1a; 模型使用chatglm-6b 4bit&#xff0c;推理使用hugging face&#xff0c;前端应用使用streamlit或者gradio。 微调对显存要求较高&#xff0c;还没试验。可以结合LoRA进行微调。 缓解时效性问题&#xff1a;通过本地数据库…

YOLOv7如何提高目标检测的速度和精度,基于模型结构提高目标检测速度

目录 一、目标检测二、目标检测的速度和精度的权衡1、速度和精度的概念和定义2、如何评估目标检测算法的速度和精度3、速度和精度之间的权衡 三、基于模型结构提高目标检测速度1、Backbone网络的选择2、特征金字塔网络的设计3、通道注意力机制4、混合精度训练 一、目标检测 目…

光纤网卡传输速率和它的应用领域有哪些呢?通常会用到哪些型号网络变压器呢?

Hqst盈盛&#xff08;华强盛&#xff09;电子导读&#xff1a;常有客户问起光纤网卡该如何选用到合适的产品&#xff0c;选用时要注意到哪些事项&#xff0c;这节将结合配合到的网络变压器和大家一起探讨&#xff0c;希望对大家有些帮助。 1&#xff0e;光纤网卡传输速率与网络…

【教程】一文读懂 ChatGPT API 接入指南

ChatGPT 是一个基于自然语言处理技术的 API&#xff0c;它能够根据用户的输入&#xff0c;生成智能回复。结合当前最先进的AI技术&#xff0c;AP智能续写&承接上下文&#xff1b;可以回答各种问题&#xff0c;例如&#xff1a;历史&#xff0c;科学&#xff0c;文化&#x…