「字符串匹配算法 2/3」有限自动机匹配字符串算法

news2024/9/19 21:45:47

有限自动机匹配字符串算法需要一定的数论知识,而且也不是很好玩。

本文不会展开说其数学属性,因为要说清楚这点需要读者有一定的离散数学基础,不然就得先解释清楚一些概念。所以如果你不懂自动机、状态机等概念,对集合、关系等概念不熟悉,也不想搞懂,那么理解下面的代码就行了,概念上我会进行一些解释,毕竟也是个记录。

如果你想搞懂背后的数学属性,首先学一下离散数学,重点是数论。有基础之后,就可以看一下《算法导论》和芝加哥大学的String Matching with Finite Automata,后者示例更好一些,本文也参考了一些。

请添加图片描述

自动机干嘛的

自动机(automata)是一种数学模型,包含了一系列状态,输入不同,会转换成不同的状态。一般这种xx机都是用来实现一些语言或者机器的(比如大名鼎鼎的图灵机),但是也可以使用编程语言模拟出来。有限机一般使用状态转移表来进行模拟和实现,就像各种图经常是用矩阵表达的。

下图是《算法导论》中给出的状态转移表格,这个表格怎么来的?要怎么看呢?

这个表格表示了有限自动机的状态是如何根据输入改变的:

  • 这个表格中的input三列表示输入的可能性有三种:abc。(输入d就不能匹配了,所以这个表格的宽度经常等于 ASCII 码的大小)
  • P下面是匹配字符串。
  • 每一行表示一种状态。第一行是初始状态。
  • 每一个格子表示当前状态和输入的情况下,前往哪一个状态。
    请添加图片描述
    如果你没搞明白怎么看这个表格,跟着下面的步骤走一下(我特地让二者能同时显示,不用上下划):
  1. 从第一行开始:如果当前输入的字符是a(也就是检查的字符串中的字符),与P的字符相同,那么前往下一状态1;如果是其他的,还呆在状态0
  2. 假设输入了a,前往了状态1
    • 此时如果输入的依旧是a,那么还在当前行(相当于从状态0又来了一次);
    • 如果输入的是b,那么前往状态2
    • 如果是输入的字符是c,那么返回状态0(因为下次匹配字符串要从头开始)。
  3. 以此类推,直到完全匹配成功一次。这时候在状态7,然后根据下次输入的字符,返回012(为了检查自己有没有真的搞明白,可以想想最后一个状态为什么输入b的时候返回的是状态2)。

算法逻辑

从上面的过程可知,只要有一种输入能前往状态7,那么这个输入就包含匹配的字符串。所以这个算法的逻辑大致如下:

  1. 生成这样一个状态表格(这部分只和匹配字符串有关);
  2. 用字符串在这个表格上“前进”,如果到最后状态了,那么就说明存在一个匹配部分。

生成表格

生成状态表格简单中又包含着不简单:

  • 简单的是:将可能的输入字符(行元素)等于匹配字符串的字符(列元素)的位置等于下一行号。
  • 不简单的是:如果当前值不匹配,返回哪一状态呢?从上面的过程中我们看到,如果直接返回初始状态0的话,有些时候是会出错的。实现方法就是从当前状态往前倒,不过倒到哪里合适呢?

首先是找到一个离当前状态最近,并且和当前输入字符相同的状态。因为重复意味着有可能只用返回到这个位置后的一个就行了。还记得前面那个过程中的状态 1 吗?输入a之后返回的还是1,而不是回到0了。

然后逐步从头检查匹配字符串的子字符串和含当前状态对应的子字符串是否相等,如果相等就不用回到开头了。

完整代码

写这个代码的时候参考了一下别人的,因为我一开始看《算法导论》没搞懂这个算法在干嘛。然后一堆人在如果不匹配,确定要返回哪个状态用的是for递减来计算,我看了半天才反应过来在干嘛,我不喜欢这个表达方式,然后就改递增,但是中间有个地方修改后忘了,最后查了一个小时才找到问题所在,真的是犯蠢了。

完整代码如下:

#include<stdio.h>

//这里的长度是ASCII的长度,因为使用的是ASCII码
#define NumInput 256

void automataMatch(char str[], char partern[], int lenstr, int lenpart) {
    //保证FA的初始值是0
    int FA[lenpart+1][NumInput];
    for (int i=0; i<(lenpart+1); i++) {
        for (int j=0; j<NumInput; j++) {
            FA[i][j] = 0;
        }
    }
 
    //构建状态转移表格
    for (int state = 0; state <= lenpart; state++) {
        for (int in = 0; in < NumInput; in++) {
            //如果当前状态匹配的字符partern[state]等于输入的元素x
            if (state < lenpart && in == partern[state]) {
                FA[state][in] = state + 1;
            } else {
                //如果不匹配,确定要返回哪个状态
                /*  比如表格中给出的ababaca,
                    如果在c的位置输入了b,那么就往前倒,找和开头相同的部分,此时state=6;
                    先找为b的部分,可以看到是第4个位置,也就是说,此时preState=state-2=4;
                    表示判断从a(状态2)开始判断,发现和开头是一样的,就开始使用i遍历;
                    然后发现直到最后都相同,就返回这个状态编号。
                 */
                int i = 0;
                for (i = 0; i < state; i++) {
                    //如果输入等于之前的某一个状态,那么说明有可能可以返回到这个状态
                    if (partern[state-i-1] == in) {
                        int j=0;
                        for (j = 0; j < state-i-1; j++) {
                            //如果不等于就下一次循环,如果最后都没有,那么直接就是0,回到初始状态
                            if (partern[j] != partern[i+j+1]) {
                                break;
                            }
                        }
                        //如果匹配字符串前面的部分等于当前状态前面的部分,那么返回上一状态就行了
                        if (j == state-i-1) {
                            FA[state][in] = state-i;
                            break;
                        }
                    }
                }
            }
        }
    }
    
    //开始匹配
    for (int i = 0, state = 0; i < lenstr; i++) {
        state = FA[state][str[i]];
        //如果状态机表格中的值等于匹配部分的长度,那么说明匹配好了
        if (state == lenpart) {
            printf ("从%d开始匹配\n", i-lenpart+1);
        }
    }
    
}

int main(void)
{
    char str[]="bccdccdcccd";
    char partern[]="ccd";

    int lenstr=sizeof(str)/sizeof(char)-1;
    int lenpart=sizeof(partern)/sizeof(char)-1;
    
    automataMatch(str, partern, lenstr, lenpart);
    
    return 0;
}

这里的字符串与前面不同,是因为这里要展示一下多个匹配是什么样的。结果如下:

从1开始匹配
从4开始匹配
从8开始匹配

下一篇是关于 KMP(The Knuth-Morris-Pratt algorithm)算法的。

希望能帮到有需要的人~

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

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

相关文章

【Datawhale AI 夏令营】讯飞“基于术语词典干预的机器翻译挑战赛”

背景 机器翻译具有悠长的发展历史&#xff0c;目前主流的机器翻译方法为神经网络翻译&#xff0c;如LSTM和transformer。在特定领域或行业中&#xff0c;由于机器翻译难以保证术语的一致性&#xff0c;导致翻译效果还不够理想。对于术语名词、人名地名等机器翻译不准确的结果&…

PyTorch复现PointNet++——模型训练+模型测试

本博文主要实现对PointNet源码进行调试&#xff0c;训练可视化测试。 一、下载源码和数据集 论文&#xff1a;PointNet: Deep Hierarchical Feature Learning on Point Sets in a Metric Space GitHub源码&#xff1a;Pointnet2_pytorch 数据集包括三种&#xff1a;分类、零部…

修改留言板

<!DOCTYPE html> <html lang"zh-CN"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>备忘录</title><!-- <link rel"…

[Spring] Spring Web MVC基础理论

&#x1f338;个人主页:https://blog.csdn.net/2301_80050796?spm1000.2115.3001.5343 &#x1f3f5;️热门专栏: &#x1f9ca; Java基本语法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12615970.html?spm1001.2014.3001.5482 &#x1f355; Collection与…

如何从 PDF 中删除背景

您是否曾经收到过充满分散注意力背景的扫描 PDF 文档&#xff1f;也许是带有繁忙水印的旧收据或背景光线不均匀的扫描文档。虽然这些背景可能看起来没什么大不了的&#xff0c;但它们会使您的工作空间变得混乱&#xff0c;并使您难以专注于重要信息。轻松删除这些不需要的元素并…

嵌入式基础 硬件接口汇总

在此收集整理嵌入式通信中常见的接口协议&#xff0c;它们具有一定的通用性&#xff0c;在今后的开发中会反复遇到。 包括但不限于以下类别&#xff08;逐步完善中…&#xff09;&#xff1a; GPIOUARTSPII2CUSBEthernetNAND Flash类SDRAM类&#xff08;ram-like&#xff09;LC…

机器学习——随机森林(学习笔记)

目录 一、基础认识 1. 集成算法介绍 2. 集成算法种类 二、sklearn中的随机森林 1. ensemble.RandomForestClassifier &#xff08;随机森林分类&#xff09; &#xff08;1&#xff09;基本参数 &#xff08;2&#xff09;基本属性 &#xff08;3&#xff09;基本接口 …

【Linux】centos7安装PHP7.4报错:libzip版本过低

问题描述 configure: error: Package requirements (libzip > 0.11 libzip ! 1.3.1 libzip ! 1.7.0) were not met: checking for libzip > 0.11 libzip ! 1.3.1 libzip ! 1.7.0... no configure: error: Package requirements (libzip > 0.11 libzip ! 1.3.1 libzi…

DAMA学习笔记(五)-数据存储和操作

1.引言 数据存储与操作包括对存储数据的设计、实施和支持&#xff0c;最大化实现数据资源的价值&#xff0c;贯穿于数据创建/获取到处置的整个生命周期。 数据存储与操作包含两个子活动&#xff08;图6-1&#xff09;。 图6-1 语境关系图&#xff1a;数据存储与操作 (1) 数据库…

分布式系统—Ceph块存储系统(RBD接口)

目录 一、服务端操作 1 创建一个名为 rbd-xy101 的专门用于 RBD 的存储池 2 将存储池转换为 RBD 模式 3 初始化存储池 4 创建镜像 5 管理镜像 6.Linux客户端使用 在管理节点创建并授权一个用户可访问指定的 RBD 存储池 ​编辑修改RBD镜像特性&#xff0c;CentOS7默认情…

英特尔CPU研发团队繁忙的一天

早晨&#xff1a;准备与启动 7:00 AM - 起床与准备 研发团队的工程师们早早起床&#xff0c;快速洗漱并享用健康的早餐。部分工程师会进行晨间锻炼&#xff0c;保持头脑清醒和身体活力。 8:00 AM - 到达办公室 工程师们来到位于硅谷的英特尔总部&#xff0c;进入研发中心。…

Open-TeleVision——通过VR沉浸式感受人形机器人视野的远程操作

前言 7.3日&#xff0c;我司大模型机器人(具身智能)线下营群里的一学员发了《Open-TeleVision: Teleoperation with Immersive Active Visual Feedback》这篇论文的链接&#xff0c;我当时快速看了一遍&#xff0c;还是有价值的一个工作(其有受mobile aloha工作的启发)&#x…

MT6816磁编码IC在工控机器人中的应用

在现代工业自动化领域&#xff0c;高精度的位置检测和控制技术对于机器人系统的稳定运行至关重要。MT6816磁编码IC作为一款先进的磁传感器解决方案&#xff0c;以其卓越的性能和稳定性&#xff0c;在工控机器人中得到了广泛的应用。本文将详细探讨MT6816磁编码IC在工控机器人中…

git常用命令及git分支

git常用命令及git分支 git常用命令设置用户签名初始化本地库查看本地库状态将文件添加到暂存区提交到本地库查看历史记录版本穿梭 git分支什么是分支分支的好处分支的操作查看分支创建分支切换分支删除分支合并分支合并冲突 git常用命令 设置用户签名 //设置用户签名 git con…

Golang:数据科学领域中的高性能并发编程新星

文章目录 📖 介绍 📖🏡 演示环境 🏡📒 文章内容 📒📝 并发性能的卓越表现📝 系统级工具的便捷性📝 语言设计的简洁性📝 强类型系统的严格性📝 版本兼容性的稳定性📝 内置工具的全面性⚓️ 相关链接 ⚓️📖 介绍 📖 在数据科学和机器学习的广阔天地…

音视频开发—使用FFmpeg从纯H264码流中提取图片 C语言实现

文章目录 1.H264码流文件解码流程关键流程详细解码流程详细步骤解析 2.JPEG编码流程详细编码流程详细步骤解析 3.完整示例代码4.效果展示 从纯H.264码流中提取图片的过程包括解码和JPEG编码两个主要步骤&#xff0c;以下是详细阐述 1.H264码流文件解码流程 关键流程 查找编解…

微信小程序---分包加载

一、分包加载 1. 什么是分包加载 什么是分包加载 ❓ 小程序的代码通常是由许多页面、组件以及资源等组成&#xff0c;随着小程序功能的增加&#xff0c;代码量也会逐渐增加&#xff0c;体积过大就会导致用户打开速度变慢&#xff0c;影响用户的使用体验。 分包加载是一种小…

线性代数|机器学习-P23梯度下降

文章目录 1. 梯度下降[线搜索方法]1.1 线搜索方法&#xff0c;运用一阶导数信息1.2 经典牛顿方法&#xff0c;运用二阶导数信息 2. hessian矩阵和凸函数2.1 实对称矩阵函数求导2.2. 线性函数求导 3. 无约束条件下的最值问题4. 正则化4.1 定义4.2 性质 5. 回溯线性搜索法 1. 梯度…

nodejs模板引擎(一)

在 Node.js 中使用模板引擎可以让您更轻松地生成动态 HTML 页面&#xff0c;通过将静态模板与动态数据结合&#xff0c;您可以创建可维护且易于扩展的 Web 应用程序。以下是一个使用 Express 框架和 EJS 模板引擎的基本示例&#xff1a; 安装必要的依赖&#xff1a; 首先&#…

(四)stm32之通信协议

一.串口通信 1、全双工、半双工、单工 单工:只能一个人传输,只能向一个方向传输 半双工:只能一个人传输,可以多个方向传输 全双工:多方传输,多个方向传输 2、同步通信、一步通信 异步通信:双方时钟可以不同步,发送的信息封装(加上起始位、停止位)实现同步,效率低,…