字符串匹配原理及实现(C++版)

article/2025/10/4 0:00:35

字符串匹配原理及实现(C++版)

  • 1. 字符串匹配概念
  • 2. BF
    • 2.1 原理
    • 2.2 代码实现
  • 3. KMP
    • 3.1 原理
    • 3.2 代码实现
  • 4. BM
    • 4.1 坏字符
    • 4.2 好后缀
    • 4.3 代码实现

1. 字符串匹配概念

在查找操作中,我们用到很重要的概念就是字符串匹配,所谓字符串匹配就是在文本串中搜索模式串是否存在及其存在的位置。下面介绍几种字符串匹配的方法。

2. BF

2.1 原理

BF(暴力法)是一种最简单的字符串匹配算法,匹配过程如下:
字符串匹配1
文本串中的 I 和模式串中的 II 实现了匹配。
如果 III 下一个都是 A ,那么匹配长度增长,I 变成 IIIII 变成 IV
如果 III 的下一个是 AIV 的下一个是 B ,那么匹配失败,模式串向后移动一个字符,重新开始字符串匹配。
BF 的特点:
1.模式串与文本串的匹配是自左向右的进行。
2.一旦模式串与文本串失配,模式串只能向右移动一个字符。

2.2 代码实现

/** 暴力法:用于字符串匹配* string t:文本串* string p:模式串* 返回值:返回首次匹配(完全匹配)位置(失败返回-1)
*/
int BruteForce(string t, string p){int lenT = t.size();int lenP = p.size();int i, j;for (i = 0; i <= lenT - lenP; ++i){for (j = 0; j < lenP; ++j){if (t[i + j] != p[j]){break;}}if (j == lenP){return i;}}return -1;
}

3. KMP

3.1 原理

可以看出,BF 中的匹配过程如下:
1.单次:多次成功,一次失败。
2.总体:多次失败,一次成功。
因为每次比对,都经历了多次成功,而经历了一次失败,然后需要模式串移动一个字符。显然,这种方法没有吸取 先前匹配成功的经验,实际上我们可以通过这种经验改进匹配过程。KMP 就是一种改进版的字符串匹配方法,匹配过程如下:
2
我们考虑在第一个文本串和模式串对齐方式中,III 是匹配的,那么,模式串能够从第一个对齐位置移动到下一个对齐位置的条件是 IIIIV 是匹配的。
由此我们可以总结:
1.移动对齐方式只由文本串与模式串失配位置决定。
2.而与文本串与模式串失配位置的文本串字符无关。
3.也就是说,移动对齐方式只与模式串有关。
那么,我们完全可以根据模式串预先算出一张表,由此得到在不同的位置上失配可以移动模式串的字符距离。
3
在第一个对齐方式中,III 是匹配的,匹配长度是 7 个字符,那么我们可以在表中记录数字 7,即该表存储的是当前字符前面的字符串 匹配的长度。
推导表格的方法我们采用递推的方法,假设已经有了第一个对齐位置的匹配,即 III 是匹配的,匹配长度是 7
如果 III 的下一个字符都是 A,那么 I 变成 IIIII 变成 IV,即匹配长度 + 1,在表中记录数字 8
如果 III 的下一个字符是 AIV 的下一个字符是 B,那么问题就不再那么简单了。
首先,细分 III 字符串,可以看到 VVI 是匹配的,同理,VIIVIII 是匹配的。此时刚好 V 的下一个字符是 B,那么就实现了匹配, V 变成 IXVIII 变成 X。在表格中记录数字 3
如果 V 的下一个字符依旧不是 B,我们就可以将 V 继续细分,方法与上类似。如果细分到最后还是找不到字符 B,那么就只能将模式串移动一个字符,即只能在表中记录数字 0,表示当前字符前面的字符串 匹配的长度是 0
创建 next 表的代码如下:

/*
* 创建next表
* string p:模式串
* int next[]:next表
*/
void CreatNext(string p, int next[]){int lenP = p.size();int i = 0, j = -1;next[0] = -1;while (i < lenP - 1){if (j < 0 || p[i] == p[j]){i++;j++;next[i] = j;  //此句代码还可以改进}else{j = next[j];}}
}

其实,next 表的创建方法还可以改进。
4
首先 III 是匹配的,IIIIV 是匹配的,按第一种创建方式, IIIV 的下一个字符对应的表格都应该记录数字 3。但是实际上,如果 IV 的下一个字符发生了失配,而 IVIII 的下一个字符都是 A 的话,即使将 III 移动到 IV 的位置上,结局依然是失配,而我们可以通过改进 next 表的创建方式来避免这种不必要的操作。

/*
* 创建next表改进版
* string p:模式串
* int next[]:next表
*/
void CreatNext_E(string p, int next[]){int lenP = p.size();int i = 0, j = -1;next[0] = -1;while (i < lenP - 1){if (j < 0 || p[i] == p[j]){i++;j++;next[i] = p[i] == p[j] ? next[j] : j;  //此句代码进行了改进}else{j = next[j];}}
}

KMP 的特点:
1.模式串与文本串的匹配是自左向右的进行。
2.一旦模式串与文本串失配,模式串依靠 next 表向右移动若干个字符。

3.2 代码实现

next 表的创建代码不再赘述。

/*
* KMP法:用于字符串匹配
* string t:文本串
* string p:模式串
* 返回值:返回首次匹配(完全匹配)位置(失败返回-1)*/
int KnuthMorrisPratt(string t, string p){int lenT = t.size();int lenP = p.size();int *next = new int[lenP];//CreatNext(p, next);CreatNext_E(p, next);int i, j;for (i = 0; i <= lenT - lenP; ){for (j = 0; j < lenP; ++j){if (t[i + j] != p[j]){i += j - next[j];break;}}if (j == lenP){return i;}}return -1;
}

4. BM

4.1 坏字符

BF 算法中,总结起来就是:
1.单次:多次成功,一次失败。
2.总体:多次失败,一次成功。
可以看出来,除了成功匹配的那次对比,其余的各次都是因为一次失配引起的。但是,在一般情况下,失败的概率与成功的概率相比,简直是微乎其微。所以,与其说是寻找匹配,不如说是加速失败。这里的坏字符说的就是加速失败。
坏字符 策略中,有这样的情况,这里 III 已经成功匹配。而 I 前面的一个字符是 AII 的前面一个字符是 B,发生了失配。那么,接下来,我们在模式串中找到字符 A,然后将两者相应对齐。那么,会有以下几种情况:
1.A(一个或多个)在 B 的前面:那么这时我们为了 加速匹配 进程而又 避免遗漏,可以把(最右边的 A)移动到文本串的 A 位置,与之对齐。
5
2.A(无或有)在 B 的后面:如果模式串中没有字符 A,那么直接将模式串向右移动一个字符。而如果 AB 的后面,那么就不能把 A 和文本串的 A 对齐,因为这样会引起字符串匹配的回溯,是没有意义的。这时依旧是将模式串向右移动一个字符。
6
因为我们需要知道的是某个字符在模式串中的有无以及最右边的位置,所以我们可以构建一个 bc 表,用来记录这些信息,方便我们查找。显然,bc 表要能够涵盖整个文本串与模式串中包含的字符集合。
下面给出 bc 表的演示(没有的记作 - 1):
7
下面给出实现代码:

/*
* 创建bc表
* string p:模式串
* int bc[]:bc表
*/
void CreatBc(string p, int bc[]){int lenP = p.size();int i;for (i = 0; i < 256; bc[i++] = -1);for (i = 0; i < lenP; ++i){bc[p[i]] = i;}
}

BC 的特点:
1.模式串与文本串的匹配是自右向左的进行。
2.一旦模式串与文本串失配,模式串依靠 bc 表向右移动若干个字符。

4.2 好后缀

8
这里 III 以及成功匹配,AB 发生失配,那么这时模式串移动到下一位置的条件就是 IIIIV 是匹配的。
9
让我们把视野放到模式串上,如果 III 匹配,当 II 前面一个字符发生失配,那么模式串对应需要向右移动 12 个字符。然后在 gs 表中记录数字 12
10
但是,直接得到 gs 表十分困难,我们需要一个 ss 表作为中间转换。这里的 7 表示 III 的匹配长度是 7ss 表的创建过程如下:
11
首先,III 已经匹配,匹配长度为3。
1.此时,III 前面一个字符都是 A,那么匹配长度 + 1 变成 4
2.III 的前面一个字符是 AIV 的前面一个字符是 B,发生了失配。所以在 ss 表中记录数字 4
3.将 hi 指到当前位置,因为之前已经匹配过了,所以不需要重复匹配,直接把 1 记录在ss 表中。
4.将 hi 指到当前位置,虽然这里之前匹配过了,但是 VIII 中记录的是 3,但是这段长度为 3 的字符串中的 B 和前面的 A 已经失配,所以不能直接记录 3,而是要重新比较。
具体实现代码如下:

/*
* 创建ss表
* string p:模式串
* int ss[]:ss表
*/
void CreatSs(string p, int ss[]){int lenP = p.size();int i, hi, lo;ss[lenP - 1] = lenP;for (hi = lo = lenP - 1, i = lo - 1;i >= 0; --i){if (i > lo && ss[lenP - 1 - hi + i] <= i - lo){ss[i] = ss[lenP - 1 - hi + i];}else{hi = i;lo = __min(hi, lo);while (lo >= 0 && p[lo] == p[lenP - 1 - hi + lo]){lo--;}ss[i] = hi - lo;}}
}

那么,接下来讨论从 ss 表到 gs 表的变换。下图中第一行是模式串,第二行是 ss 表,第三行是 gs 表。
12
1.如果匹配方式是 III 匹配,那么 II 前面的字符串中的每个字符发生失配都可能会移动 15 个字符。
2.IIIIV 匹配成功,IV 前面字符串的每个字符发生失配都可能移动 19 个字符。
3.VVI 匹配成功,VI 前面的那个字符发生失配可能需要移动 20 个字符。
注:这里是可能并不是一定,实际移动距离取所有可能的最小值。(为了避免遗漏)

/*
* 创建gs表
* string p:模式串
* int gs[]:gs表
*/
void CreatGs(string p, int gs[]){int lenP = p.size();int *ss = new int[lenP];CreatSs(p, ss);int i, j;for (i = 0; i < lenP; ++i){gs[i] = lenP;}for (i = 0, j = lenP - 1; j >= 0; --j){if (ss[j] == j + 1){while (i < lenP - 1 - j){gs[i++] = lenP - 1 - j;}}}for (i = 0; i < lenP - 1; ++i){gs[lenP - 1 - ss[i]] = lenP - 1 - i;}delete[] ss;
}

GS 的特点:
1.模式串与文本串的匹配是自右向左的进行。
2.一旦模式串与文本串失配,模式串依靠 gs 表向右移动若干个字符。
所谓 BM,就是综合了 BCGS 两个策略进行的字符串匹配算法。
BM 的特点:
1.模式串与文本串的匹配是自右向左的进行。
2.一旦模式串与文本串失配,模式串依靠 bc 表和 gs 表向右移动若干个字符。(取 bc 表和 gs 表的较大值)

4.3 代码实现

/*
* BM法:用于字符串匹配
* string t:文本串
* string p:模式串
* 返回值:返回首次匹配(完全匹配)位置(失败返回-1)
*/
int BoyerMoore(string t, string p){int lenT = t.size();int lenP = p.size();int *bc = new int[256];CreatBc(p, bc);int i, j;/*for (i = 0; i <= lenT - lenP;){for (j = lenP - 1; j >= 0; --j){if (t[i + j] != p[j]){i += (j - bc[t[i + j]] > 0) ? j - bc[t[i + j]] : 1;break;}}if (j == -1){return i;}}*/int *gs = new int[lenP];CreatGs(p, gs);for (i = 0; i <= lenT - lenP;){for (j = lenP - 1; j >= 0; --j){if (t[i + j] != p[j]){int max = __max(j - bc[t[i + j]], gs[j]);i += max;break;}}if (j < 0){return i;}}return -1;
}

http://chatgpt.dhexx.cn/article/gyyE9vRW.shtml

相关文章

C++之单字符串匹配问题

有很多算法可以解决单模式匹配问题。而根据在文本中搜索模式串方式的不同&#xff0c;我们可以将单模式匹配算法分为以下几种&#xff1a; 基于前缀搜索方法&#xff1a;在搜索窗口内从前向后&#xff08;沿着文本的正向&#xff09;逐个读入文本字符&#xff0c;搜索窗口中文…

字符串——字符串匹配

文章目录 字符串匹配一、基本概念字符串匹配问题符号和术语后缀重叠引理 二、朴素字符串匹配算法三、Rabin-Karp算法(字符串Hash算法)进制Hash质数Hash 四、利用有限自动机进行字符串匹配有限自动机字符串匹配自动机计算状态转移函数代码 五、Knuth-Morris-Pratt算法模式的前缀…

朴素字符串匹配

描述 字符串匹配问题的形式定义&#xff1a; 文本(Text)是一个长度为 n 的字符串:T;模式(Pattern)是一个长度为 m 且 m≤n 的字符串:P; T 和 P 中的元素都属于有限的字母表 Σ 表; 有效位移 (Valid Shift): 如果 0≤ s ≤n-m&#xff0c;并且 T[s1…sm] P[1…m]&#xff0c…

算法之字符串匹配一

目录 前言&#xff1a; BF算法&#xff1a; RK算法 总结&#xff1a; 参考资料 前言&#xff1a; 字符串匹配指的是一个短点的字符串与一个长点的字符串进行匹配&#xff0c;并确认短的字符串是否在长的字符串中存在。匹配算法有很多&#xff0c;本文介绍两种简单、容易…

字符串匹配算法(C语言实现)

目录 文章目录 前言 一、BF算法 二、KMP算法 1.算法介绍 2.算法思路 3.整体代码实现 总结 前言 字符串匹配算法又称模式匹配算法&#xff0c;该算法的目的是为了子串从主串中寻找是否有与其匹配的部分&#xff0c; 其可分为BF暴力检索、RK哈希检索、KMP、BM等等&#xff0c;本…

shell字符串匹配

一、简介 Bash Shell提供了很多字符串和文件处理的命令。如awk、expr、grep、sed等命令&#xff0c;还有文件的排序、合并和分割等一系列的操作命令。grep、sed和awk内容比较多故单独列出&#xff0c;本文只涉及字符串的处理和部分文本处理命令。 二、字符串处理 1、expr命令…

golang字符串匹配算法

简介 字符串匹配算法主要用于在一个较长的文本串中查找一个较短的字符串&#xff08;称为模式串&#xff09;。在 Golang 中&#xff0c;可以使用最常见的字符串匹配算法之一&#xff1a;Knuth-Morris-Pratt&#xff08;KMP&#xff09;算法&#xff0c;它的时间复杂度为 O(nm…

【数据结构】字符串匹配(暴力匹配)

原理解析&#xff1a; 字符串匹配方法&#xff0c;创建两个字符串结构&#xff0c;主 串和子串比较。 主串字符 a 和 子串字符 c 不匹配&#xff0c;主串的指针向下移动&#xff0c;移动到上一次开始比较的下一个位置。 子串指向开始的位置。 主串字符 b 和 子串字符 c 不匹配…

字符串匹配算法比较

字符串匹配&#xff08;string match)是在实际工程中经常会碰到的问题&#xff0c;通常其输入是原字符串(String)和子串&#xff08;又称模式&#xff0c;Pattern)组成&#xff0c;输出为子串在原字符串中的首次出现的位置。通常精确的字符串搜索算法包括暴力搜索(Brute force)…

子串查找(字符串匹配)

子串查询 首先&#xff0c;我们来定义两个概念&#xff0c;主串和模式串。我们在字符串 A 中查找字符串 B&#xff0c;则 A 就是主串&#xff0c;B 就是模式串。我们把主串的长度记为 n&#xff0c;模式串长度记为 m。由于是在主串中查找模式串&#xff0c;因此&#xff0c;主串…

字符串匹配算法综述

字符串匹配算法综述 字符串匹配算法综述&#xff1a;BF、RK、KMP、BM、Sunday 字符串匹配算法&#xff0c;是在实际工程中经常遇到的问题&#xff0c;也是各大公司笔试面试的常考题目。此算法通常输入为原字符串&#xff08;string&#xff09;和子串&#xff08;pattern&…

字符串匹配算法详解

希望看到文章的你们&#xff0c;能够在今年的研究生考试中超常发挥。 愿你们都能考上自己心仪的学校&#xff0c;为你们的备考生涯划上一个完美的句号。做为你们的师兄有几句话想对你们说&#xff0c;希望这些话能对你们有一些帮助。 马上就要考试了&#xff0c;不要再继续啃难…

字符串匹配算法

字符串匹配就是在主串A中查找模式串B&#xff0c;例如在主串abababc中查找模式串abc是否存在&#xff0c;记主串A的长度为n&#xff0c;模式串B的长度为m&#xff0c;n>m。 BF算法 BF(Brute Force)算法&#xff0c;又叫暴力匹配算法或者朴素匹配算法&#xff0c;思路很简单…

字符串(字符串匹配)

一、字符串匹配问题、基础 1、假设文本是一个长度为n的数组T&#xff0c;而模式是长度为m的数组P&#xff0c;我们希望在文本T中寻找模式P 如果P出现在T中的第s个位置&#xff0c;那么我们称其有效偏移为s&#xff0c;在其他不匹配的位置称为无效偏移 2、如果字符串w是字符串…

字符串匹配

字符串匹配 1.朴素的串匹配算法(暴力解法) 1.1 分析 设t是目标串&#xff08;母串&#xff09;&#xff0c;p是模式串&#xff08;待匹配串&#xff09;&#xff0c;i , j 分别指向 模式串 和 目标串&#xff0c;m、n分别是模式串p和目标串t的长度。 从目标串的第0个字符&am…

Photoshop怎么给图片添加简介信息或者版权信息

转自&#xff1a;Photoshop怎么给摄影图片添加作者名字版权等信息? 有时我们点开一张图片的详细信息中可能可以看到各种属性信息&#xff0c;比如作者&#xff0c;时间&#xff0c;关键字&#xff0c;图片信息描述等属性&#xff0c;但是我们自己的拍摄的或者从别的地方获取的…

2022年中国版权保护中心计算机软件著作权登记最全申请步骤流程

一、前言二、实名认证1. 用户注册2. 实名认证 三、办理步骤1. 办理流程2. 填写申请表3. 提交申请文件4. 登记机构受理申请5. 审查6. 获得登记证书 四、登记申请所需文件1. 软件著作权登记申请表2. 软件&#xff08;程序、文档&#xff09;的鉴别材料3. 有关证明文件 五、申请表…

IDEA设置版权信息

File→Settings或者CtrlS快捷键。 Editor下面有个Copyright→Copyright Profiles 点击加号&#xff0c;然后输入名称。 然后修改成自己的信息&#xff1a; 其中第一个年份是本文件新建日期&#xff0c;后面的是最后一次修改年份。 中文版本&#xff1a; 版权所有(c) Jack魏 …

版权和版本信息

版权和版本信息的主要内容有&#xff1a; &#xff08;1&#xff09;版权信息&#xff1b; &#xff08;2&#xff09;文件名称、简要描述、创建日期和作者&#xff1b; &#xff08;3&#xff09;当前版本信息和说明&#xff1b; &#xff08;4&#xff09;历史版本信息和…

版权和商标权有什么关系?版权和商标的区别在哪里?

版权和商标权存在着一定的关系&#xff0c;版权和商标又有着很多区别&#xff0c;具体的关系和区别是怎样的&#xff0c;大家都知道吗&#xff1f;今天企多多就带大家来了解&#xff01; 版权和商标权的关系 版权和商标权的关系主要有以下三点&#xff1a; 1、关联性&#xf…