首页 > 编程语言 >字符串匹配:BF算法 | KMP算法 | Z函数

字符串匹配:BF算法 | KMP算法 | Z函数

时间:2024-12-26 17:26:26浏览次数:5  
标签:BF abc 匹配 sub int 算法 str KMP

什么是字符串匹配?

给你一个字符串str,问你这个字符串中是否包含字符串sub。例如:str="abcdef",sub="cdef",问str中是不是有sub。

一.BF算法

BF算法(Brute Force),翻译成中文就是暴力匹配算法。

暴力匹配其实很好想,不就让我们判断str中有没有sub嘛,直接一个一个来。定义两个指针,一个指str,一个指sub。如果两个字符相同,继续匹配下一个;不同,sub的指针从头再来,str的指针指到开始位置的下一个(因为开始位置已经匹配不上了,肯定要从下一个开始啦)。

boolean BruteForce(String str,String sub){
    //定义两个指针,cur1指向str,cur2指向sub
    int cur1=0,cur2=0;
    int n=str.length();
    int m=sub.length();
    while(cur1<n && cur2<m){
        if(str.charAt(cur1)==sub.charAt(cur2)){
            cur1++;
            cur2++;
        }else{
            cur1=cur1-cur2+1;
            cur2=0;
        }
    }
    if(cur2==m){
        return true;
    }else{
        return false;
    }
}

我给出的代码只是一个思路,实际使用根据情况来。比如我们实际需要能跟str匹配上sub的起始下标,那方法返回的就是整形就行。

BF算法很简单,而且它不是本文的重点,所有就不过多赘述了。

二.KMP算法

KMP算法是本文的重中之重。关于KMP算法的由来,历史什么的这里就不说了,感兴趣的可以自己查一下。

我们先前使用的BF算法匹配,这个算法有个致命的缺点,那就是每次没匹配上,都要从头再来,很烦,时间复杂度很高(O(n*m))。

我之前写过一篇文章介绍过马拉车(Manacher)算法,马拉车(Manacher)算法就是重复利用了回文串的特性来优化时间复杂度的。其实这个的思路也是一样的,我们使用BF算法的时候,没有很好的利用字符串告诉我们的信息。

要想理解KMP算法,有个很重要的东西要先介绍一下,部分匹配表(Partial Match Table)。其实大家不理解KMP算法的主要原因就是这个部分匹配表。这个表中记录了什么数据这里先不管,我先让大家明白这里的数据是怎么来的。

这里其实有点对称意思,什么意思呢,看这个图:

我们在匹配到了a的时候发现,完了,sub是f,匹配失败了。这里我们要充分利用字符串的信息,我们看f前面是不是有个abc,注意啊,str上面也有一个abc,这是跟f前面abc一一对应的(已经匹配上的)。如果说sub的开头也是这个话,那是不是我们就不用让str从开始位置的下一个开始了呢,直接从这个位置开始(也就是图上箭头指向a的位置)。

为什么?我们画线部分的内容都是匹配好的,都是一一对应的 --> sub的f前有一个abc,str的a前也有一个abc --> sub的开头也是abc --> str的a前的abc直接就与sub开头的abc匹配好了:

我们下次匹配str直接从a开始,sub直接从e开始,大大提高了效率。

有人可能会问,这不凑巧了吗?恰好sub的开头是sub。对!要的就是这个“巧”,要的就是凑巧。

那我们怎么去知道有没有“凑巧”这种情况呢?下面先引出前缀和后缀的概念。

先举个例子:abcde,它的前缀:a,ab,abc,abcd,它的后缀:e,de,cde,bcde。很好理解吧,注意这里的前缀和后缀没有包含其本身即abcde。

说这个有什么用呢?其实我们的部分匹配表就是通过这个求出的。部分匹配值就是前缀和后缀的最长的共有元素的长度。

举个例子:abcdabc,它的前缀:a,ab,abc,abcd...,它的后缀:c,bc,abc,dabc...。从中我们可以得到最长的共有元素的长度为3,即abc。

如果我们用上面图的例子,可以得到sub的部分匹配表值如下:

回到上面我说的这里有点对称的意思,可以前缀和后缀的最长的共有元素的长度,不就是对称的分布在字符串的头和尾,不过这个对称可能不是严谨的对称,只是我们对这个现象的简单总结。

那部分匹配表有什么用呢?它可以求出我们sub应该后退到的位置。比如上面的c下面的3,意味着在f这里不匹配,我们可以回退到e的位置。

为什么在实际写代码的时候会定义一个next数组来存储部分匹配表。

//next数组的求取方法
public int[] getNext(String sub){
    int m=sub.length();
    int[] next = new int[m];

    for(int i = 1 , j = 0; i < m ; i ++ ){
        while(j > 0 && sub.charAt(i) != sub.charAt(j)) {
            j = next[j - 1];
        }

        if(sub.charAt(i) == sub.charAt(j)) {
            j++;
        }

        next[i] = j;
    }
    return next;
}

有了next数组后相当于我们知道了当不匹配时,sub的指针j回退到什么位置。思路与上面的BF算法的思路相同,但是这里我们不用回退str的指针i,同时,sub的指针j不用回退到0的位置。

//匹配str中的sub,把匹配成功的下标返回
public List<Integer> KMP(String str,String sub){
    int n=str.length();
    int m=sub.length();
    List<Integer> result=new ArrayList<>();
    int[] next = getNext(sub);

    for(int i = 0 ,j = 0 ; i < n ; i ++ ){
        while(j > 0 && str.charAt(i) != sub.charAt(j)) {
            j = next[j - 1];
        }
        //相等就++,往后找
        if(str.charAt(i) == sub.charAt(j)) {
            j++;
        }

        if(j == m){
            //sub遍历完,记录下标,同时回退sub
            result.add(i - m + 1);
            j = next[j-1];
        }
    }
    return result;
}

三.Z函数(扩展KMP)

Z函数,又叫扩展KMP,但其实跟KMP没有任何关系。Z函数也是用来处理字符串匹配问题的方法。我们定义一个z数组,z[i]表示从 s[i] 到 s[n−1] 的子串与整个字符串s的最长公共前缀的长度。

举个例子,abeabc:

当我们求下标为3的z(图上箭头指向的位置)时,我们用红色画线与蓝色画线(整个字符串)进行匹配,最长公共前缀是ab,长度是2,所以z[3]=2。

public int[] getZ(String s) {
    int n=s.length();
    int[] z=new int[n];
    int l = 0, r = 0;
    for (int i = 1; i < n; i++) {
        if (i <= r) {
            z[i] = Math.min(z[i - l], r - i + 1);
        }
        while (i + z[i] < n && s.charAt(z[i]) == s.charAt(i + z[i])) {
            l = i;
            r = i + z[i];
            z[i]++;
        }
    }
    return z;
}

标签:BF,abc,匹配,sub,int,算法,str,KMP
From: https://blog.csdn.net/lllsure/article/details/144492839

相关文章

  • 用小学生都能理解的方式介绍 TF-IDF 算法
    用小学生都能理解的方式介绍TF-IDF算法什么是TF-IDF?举个例子:小猫和小狗的故事1.计算TF(词频)2.计算IDF(逆文档频率)3.计算TF-IDFTF-IDF的特点另一个例子:更直观的理解总结在信息检索和文本挖掘中,TF-IDF是一个非常重要的算法。它可以帮助我们找到文档中最重要......
  • 算法网关视频分析网关:视频分析技术的准确性和实时性是如何确保的?
    在当今数字化时代,视频分析技术已成为安全监控、交通管理等多个领域不可或缺的工具。然而,确保视频分析技术的准确性和实时性,尤其是在多变的环境条件下,是一个复杂而重要的挑战。以下是一些关键技术和策略,它们共同确保了视频分析技术在各种条件下都能提供高效、准确的结果。1、图像......
  • node.js基于智能算法的健康食材订购系统程序+论文 可用于毕业设计
    本系统(程序+源码+数据库+调试部署+开发环境)带文档lw万字以上,文末可获取源码系统程序文件列表开题报告内容一、选题背景关于健康食材订购系统的研究,现有研究主要以传统的食材订购模式或单一功能的食材相关系统为主,专门针对基于智能算法的健康食材订购系统的研究较少。在国......
  • 基本数据结构——算法学习(三)上
    数据结构——算法学习(三)上前言数据结构是计算机科学的基石,几乎所有的软件开发、算法设计都离不开对数据的组织与管理。它不仅是程序高效运行的保障,也是解决复杂问题的关键工具。学习数据结构的过程,不仅仅是掌握具体的知识点,更是培养逻辑思维能力和问题解决能力的重要途径。在......
  • PyCharm专项训练4 最小生成树算法
    一、实验目的:本文的实验目的是通过编程实践,掌握并应用Prime算法和Kruskal算法来求解给定图的最小生成树问题。二、实验内容:数据准备:使用networkx库创建一个图G,并添加指定的节点和带权重的边。算法实现:实现Kruskal算法,通过构建最小生成树T,并找出构成最小生成树的边......
  • PyCharm专项训练5 最短路径算法
    一、实验目的    本文的实验目的是通过编程实践,掌握并应用Dijkstra(迪杰斯特拉)算法和Floyd(弗洛伊德)算法来解决图论中的最短路径问题。二、实验内容数据准备:使用邻接表的形式定义两个图graph_dijkstra和graph_floyd,图中包含节点以及节点之间的边和对应的权重。算......
  • 【数据集】【YOLO】【目标检测】灭火器识别数据集 3261 张,YOLO灭火器识别算法实战训练
     一、数据集介绍【数据集】灭火器识别数据集3261张,目标检测,包含YOLO/VOC格式标注。数据集中包含1种分类:names:['extinguisher'],表示"灭火器"。数据集图片来自国内外网站、网络爬虫、监控采集等;可用于监控和移动设备灭火器识别。检测场景为工业园区、办公大楼、居民楼......
  • 线性筛与埃氏筛算法详解
    目录线性筛与埃氏筛算法详解第一部分:线性筛与埃氏筛算法概述1.1什么是埃氏筛算法?1.2什么是线性筛算法?1.3埃氏筛与线性筛的比较1.4应用场景第二部分:埃氏筛算法原理与实现2.1埃氏筛算法原理2.2埃氏筛算法的步骤2.3埃氏筛的Python实现2.4代码解释第三部分:线性筛算......
  • 基于springboot+推荐算法的智能书店系统
    博主介绍:java高级开发,从事互联网行业六年,熟悉各种主流语言,精通java、python、php、爬虫、web开发,已经做了多年的设计程序开发,开发过上千套设计程序,没有什么华丽的语言,只有实实在在的写点程序。......
  • 基于EO平衡优化器算法的目标函数最优值求解matlab仿真
    1.程序功能描述基于EO平衡优化器算法的目标函数最优值求解matlab仿真。提供九个测试函数,分别对九个测试函数仿真输出最优解以及对应的优化收敛曲线。2.测试软件版本以及运行结果展示MATLAB2022A版本运行  3.核心程序whilej2<Niters%主循环进行迭代%时......