常用算法3 - 字符串查找/模式匹配算法(BF & KMP算法)

相信我们都有在linux下查找文本内容的经历,比如当我们使用vim查找文本文件中的某个字或者某段话时,Linux很快做出反应并给出相应结果,特别方便快捷!

那么,我们有木有想过linux是如何在浩如烟海的文本中正确匹配到我们所需要的字符串呢?这就牵扯到了模式匹配算法!

1. 模式匹配

什么是模式匹配呢?

  • 模式匹配,即子串P(模式串)在主串T(目标串)中的定位运算,也称串匹配

假设我们有两个字符串:T(Target, 目标串)和P(Pattern, 模式串);在目标串T中查找模式串T的定位过程,称为模式匹配.

模式匹配有两种结果:

  • 目标串中找到模式为T的子串,返回P在T中的起始位置下标值;
  • 未成功匹配,返回-1

通常模式匹配的算法有很多,比如BF、KMP、BM、RK、SUNDAY等等,它们各有千秋,我们此处重点讲解BF和KMP算法(因为比较常用)

2. BF算法

BF,即Brute-Force算法,也称为朴素匹配算法蛮力算法,效率较低!

1). 算法思想

基本思想:

    1. 将目标串T第一个字符与模式串P的第一个字符比较;
    1. 若相等,则比较T和P的第二个字符
    1. 若不等,则比较T的下一个字符与P的第一个字符
    1. 重复步骤以上步骤,直到匹配成功或者目标串T结束

流程图如下:

例如:

T=‘ababcabcacbab‘, P=‘abcac‘, 匹配流程

  • Step 1: 主串T与子串P做顺序比较,当比较到位置2时,主串T[2]=‘a‘与子串P[2]=‘c‘不等(蓝色阴影表示),记录各自的结束位置,并进入Step 2
  • Step 2: 主串T后移一位,主串T与子串P再从头开始比较,比较如Step 1
  • Step 3: 每次比较,子串都从0开始,主串的开始位置与上次的结束位置存在一定的关系;在某些时候需要“回溯”(上次比较结束的位置要向前移动);如Step 1的结束位置为2,Step 2的开始位置为1;Stp3的结束位置为6,Step 4的开始位置为3等;
  • Step 4: 主串T的索引值i 与 子串P的索引值j的关系为:i=i-j+1

2). 代码实现

/*-----------------------------------------------------------------------------
 * Function: BF - Does the P can be match in T
 * Input:   Pattern string P, Target string T
 * Output:  If matched: the index of first matched character
 *          else: -1
-----------------------------------------------------------------------------*/
int BF(const string &T, const string &P)
{
    int j=0, i=0, ret=0;

    while((j < P.length()) && (i<T.length()))
    {
        if(P[j] == T[i])    //字符串相等则继续
        {
            i++;
            j++;        //目标串和子串进行下一个字符的匹配
        }
        else
        {
            i = i - j + 1;
            j = 0;              //如果匹配不成功,则从目标字符串的下一个位置开始从新匹配
        }
    }

    if(i < T.length())      //若匹配成功,返回匹配的第一个字符的下标值
        ret = i - P.length() ;
    else
        ret = -1;

    return ret;
}

3). 效率分析

效率分析主要是分析时间复杂度和空间复杂度. 而本例的空间复杂度较低,暂时不做考虑,我们来看看时间复杂度。

分析时间复杂度通常是分析最坏情况,对于BF算法来说,最坏情况举例如下:

T="ggggggggk", P="ggk"

由上图可知,第i次匹配,前面第i-1次匹配,每次都需要比较m次(m为模式串P的长度),因此为(i-1)m次;第i次匹配成功也需要m次比较,因此总共需要比较mi次。

对于长度为n的主串T,i=n-m+1,每次匹配成功的概率为Pi,且概率相等;则在最坏情况下,匹配成功的概率Cmax可表示为:

一般情况下 n>>m,因此,BF的时间复杂度为 O(m*n)

3. KMP算法

BF算法每次都需要回溯,导致时间复杂度较大,那么有没有一种效率更高的模式匹配算法呢?

答案是肯定的,那就是KMP算法。

1). 名词解释

在进行算法讲解之前,必须要明确以下几个名词,否则无法理解此算法

  • 目标串 T: 即大量的等待被匹配的字符串
  • 模式串 P:即我们需要查找的字符串
  • 字符串前缀:字符串的任意首部(不包括最后一个字符);如"abcd"的前缀为"a","ab","abc",但不包括"abcd"
  • 字符串后缀:字符串的任意尾部(不包括第一个字符);如"abcd"的后缀为"d","cd","bcd",但不包括"abcd"
  • 字符串前后缀相等位数k:即前缀与后缀的最长匹配位数,

2). 算法思想

KMP算法的核心思想是:部分匹配,即不再把主串的位置移动到已经比较过的位置(不再回溯),而是根据上一次比较结果继续后移。

概念相当抽象,那么我们以例子来解释:

  • Step 1: 匹配到索引值index=2时,匹配失败
  • Step 2: 匹配的开始位置为index=2(没有回溯到1), 原因如下:

    Step 1 比较后,已知T[1]=‘b‘, S[0]=‘a‘,理论上已经比较过了,所以无需回溯再次比较

Step 2 一直进行匹配,直到T[6]时刻失配.

  • Step 3: T的位置不进行回溯,还是保持在T[6]开始(KMP算法规定:目标串T不回溯,上一次的结束位置即为下一次的开始位置);

    P的索引值从1开始而非0,原因如下:

    在Step 2 中,T[5]=‘a‘已经比较过,我们已知,且与P[3]相等;因为P[0]==P[3],所以无需比较P[0]与T[5],因为Step 2 理论上已经进行了比较(其实就是看子串P Step2结束位置P[4]之前的P[0-3]的字符串前后缀相等位数k,使得P[k]与上次主串的结束位置T[6]对齐)

由以上分析可知,KMP算法过程中关键点就是求: 子串P结束位置前的前后缀相等位数k

下图是模式串P="abcabca"的前后缀关系分析(包括前后缀字符串相等位数k)

由上图我们可以给出,T串每一个字符做结束位置时,下一次的开始位置的值;

  • j 为T的本次匹配结束位置(失配位置);
  • next[j] 为下次匹配模式串P的开始位置

PS: next[j]就是前后缀字符串相等位数k

根据上面的讨论,我们可以得出next[j]的运算公式:

其中,-1 是一个标记,标识下一次的开始位置目标串为,模式串P为

如果以上你没有明白,不要紧的,只需要记住next[j]的函数就可以,其它一切都是根据它来的!

3). 代码实现

/*-----------------------------------------------------------------------------
 * Function: KMP- Does the P can be match in T
 * Input:   Pattern string P, array next
 * Output:  If matched: the index of first matched character
 *          else: -1
-----------------------------------------------------------------------------*/
void getNext(const string &P, int next[])
{
    int j=0;    //模式串P的下标值/索引值
    int k=-1;   //模式串P的前缀和后缀串相等的位数
    next[0]=-1; //置初值

    while(j < P.length())
    {
        if((k == -1) || (P[j] == P[k])) //从模式串P的开始位置处理 或 顺序比较主串和子串
        {
            j++;
            k++;
            next[j] = k;
        }

        else            //设置重新比较位置:j串不变,k串从next[k]位置开始
            k = next[k];
    }
}

/*-----------------------------------------------------------------------------
 * Function: KMP- Does the P can be match in T
 * Input:   Pattern string P, Target string T
 * Output:  If matched: the index of first matched character
 *          else: -1
-----------------------------------------------------------------------------*/
int KMP(const string &T, const string &P)
{
    int next[MaxSize]={0};
    int i=0;    //目标串T的下标值/索引值
    int j=0;    //模式串P的下标值/索引值
    int ret=0;

    getNext(P, next);   //获取模式串P的next数组

    int PLen = P.length();
    int TLen = T.length();

    while((i < T.length()) && (j < PLen))   //奇怪,此处我用 j<P.length()就不行,待解决
    {
        if((j==-1) || (P[j] == T[i]))   //j=-1表示首次比较
        {
            i++;
            j++;
        }

        else
        {
            j = next[j];
        }
    }

    if(j >= P.length())
        ret = i-P.length();
    else
        ret = -1;

    return ret;
}

4). 效率分析

由于KMP算法不回溯,比较是顺序进行的,因此最坏情况下的KMP时间复杂度为 O(m+n).

其中,m为模式串P的字符串长度,n为目标串T的字符串长度.

原文地址:https://www.cnblogs.com/Jimmy1988/p/8407166.html

时间: 2024-11-07 17:26:13

常用算法3 - 字符串查找/模式匹配算法(BF & KMP算法)的相关文章

字符串与模式匹配算法(一):BF算法

一.BF算法的基本思想 BF(Brute Force)算法是模式匹配中最简单.最直观的算法.该算法最基本的思想是从主串的第 start 个字符起和模式P(要检索的子串)的第1个字符比较,如果相等,则逐个比较后续字符:比较过程中一旦发现不相等的情况,则回溯到主串的第 start+1 个字符位置,重新和模式P的字符进行比较. 二.算法代码 1 package algorithm; 2 3 import java.util.Scanner; 4 5 /** 6 * 字符串匹配算法:BF 7 */ 8

字符串查找与匹配算法

一.字符串查找:1.在Word. IntelliJ IDEA.Codeblocks等编辑器中都有字符串查找功能.2.字符串查找算法是一种搜索算法,目的是在一个长的字符串中找出是否包含某个子字符串. 二.字符串匹配:1.一个字符串是一个定义在有限字母表上的字符序列.例如,ATCTAGAGA是字母表 E ={A,C,G,T}上的一个字符串.2.字符串匹配算法就是在一个大的字符串T中搜索某个字符串P的所有出现位置.其中,T称为文本,P称为模式,T和P都定义在同一个字母表E上.3.字符串匹配的应用包括信

字符串查找与匹配之BM算法

一.字符串查找:1.在Word. IntelliJ IDEA.Codeblocks等编辑器中都有字符串查找功能.2.字符串查找算法是一种搜索算法,目的是在一个长的字符串中找出是否包含某个子字符串. 二.字符串匹配:1.一个字符串是一个定义在有限字母表上的字符序列.例如,ATCTAGAGA是字母表 E ={A,C,G,T}上的一个字符串.2.字符串匹配算法就是在一个大的字符串T中搜索某个字符串P的所有出现位置.其中,T称为文本,P称为模式,T和P都定义在同一个字母表E上.3.字符串匹配的应用包括信

关于的字符串的总结(群,子群,KMP算法,正则表达式):

字符串: 群: ? 群是一种只有一个运算的,简单的线性结构,可用来建立许多其他代数系统的一种基本结构. ? 设G是一个非空集合,a,b,c为它的任意元素.如果对G所定义的一种代数运算"."满足: 封闭性:a.b属于G 结合律:(ab)c=a(bc) 对于G中的任意元素a,b在G中存在唯一一个元素x,y,使得ax=b,ya=b,则称G对于所定义的运算构成一个群. 满足交换律是交换群 子群: ? 设H是群<G,.>的非空子集,则H是G的子群当且仅当H满足以下条件: 对任意的a,

第四章:2.串 -- 串的模式匹配算法(KMP)

前言: 目录: 1.串类型的定义 2.串的表示和实现 3.串的模式匹配算法 4.串操作应用举例 正文: 串的模式匹配即,在给定主串S 中,搜索子串T 的位置,如果存在T 则返回其所在位置,否则返回 0 串的模式匹配算法 主串 S: a b c a b c d s v t 子串 T: a b c d 一.原始算法 匹配一旦失败,子串即向右移动一个单位,直到完全匹配停止. 第一次匹配:(注:红色代表不匹配(失配)) S: a b c a b c a b c d s v t   T: a b c d

串的模式匹配算法(KMP)

算法: #include<IOSTREAM> using namespace std; #define MAXSIZE 100 void calNext(const char *T,int *next);//T为模式串,next为预判数组 int kmp_match(const char *S,const char *T);//在主串S中寻找模式串T,如果找到返回其位置,否则返回-1.位置从0开始 void calNext(const char *T,int *next) { int n =

经典算法题每日演练——第七题 KMP算法

原文:经典算法题每日演练--第七题 KMP算法 在大学的时候,应该在数据结构里面都看过kmp算法吧,不知道有多少老师对该算法是一笔带过的,至少我们以前是的, 确实kmp算法还是有点饶人的,如果说红黑树是变态级的,那么kmp算法比红黑树还要变态,很抱歉,每次打kmp的时候,输 入法总是提示“看毛片”三个字,嘿嘿,就叫“看毛片算法”吧. 一:BF算法 如果让你写字符串的模式匹配,你可能会很快的写出朴素的bf算法,至少问题是解决了,我想大家很清楚的知道它的时间复 杂度为O(MN),原因很简单,主串和模

模式串匹配、KMP算法及其改进(代码)

#include "string.h" #include "stdio.h" #include "stdlib.h" #include "io.h" #include "math.h" #include "time.h" #define OK 1 #define ERROR 0 #define TRUE 1 #define FALSE 0 #define MAXSIZE 100 /* 存

字符串查找函数(BF)

//模拟字符串定位函数 // s: abcbbghi // t: ghi // 返回6 #include <iostream> #include <string> #include <algorithm> using namespace std; int main() { string s, t; int len1, len2; int i, j; while(cin>>s) { cin>>t; len1=s.size(); len2=t.siz