字符串匹配

  • 字符串抽象数据类型
  • 字符串模式匹配
    • 简单的字符串匹配
    • Knuth-Morris-Pratt算法
      • 背景分析
      • 失配函数
        • 定义
        • 实现方法
        • 函数分析
      • KMP函数
        • 实现方法
        • 函数分析
      • 失配信息的另一种方法:next函数
        • 定义
        • 实现方法
  • 代码总览

字符串抽象数据类型

  • C++语言中包含一个string类,其ADT中包含很多定义的函数,这里就不再详细赘述。

字符串模式匹配

简单的字符串匹配

  • 检验字符串pat是否在str中最简单但最低效的方法:逐个考虑str内每个位置,判断其是否是匹配的起始地址。

  • 代码如下:

// 若匹配返回匹配起始地址,否则返回-1
int Find(const string &str, const string &pat)
{//获取字符串长度int lengthS = str.size();int lengthP = pat.size();for (int s = 0; s < lengthS; s++){int j;for (j = 0; j < lengthP && str[s + j] == pat[j]; j++);//匹配到结果返回起始地址if (j == lengthP)return s;}//非匹配到结果return -1;
}

Knuth-Morris-Pratt算法

背景分析

  • 上面提到的简单算法的时间复杂度为O(lengthS×lengthP)O(lengthS\times lengthP)O(lengthS×lengthP),能否有一种算法将时间复杂度控制在O(lengthS+lengthP)O(lengthS+lengthP)O(lengthS+lengthP)。
  • 为了实现这一想法,我们希望能无需回溯地在字符串中,即当发生一个不匹配(失配)的情况时,我们希望利用模式串(Pat)中不匹配字母和失配位置等相关信息确定继续搜索的位置。如图所示:
  • 为了确定模式串的适配信息,我们给模式串定义一个失配函数。

失配函数

定义

  • 设p=p0p1...pn−1p=p_0p_1...p_{n-1}p=p0​p1​...pn−1​是一个模式串,定义它的失配函数(failure function),fff为:

f(j)={k,k为满足p0...pk=pj−k...pj且k<j的最大整数,k存在且k≥0−1,无满足kf(j) = \begin{cases} k,&k为满足p_0...p_k=p_{j-k}...p_j且k<j的最大整数,k存在且k\ge 0\\ -1, & 无满足k \end{cases} f(j)={k,−1,​k为满足p0​...pk​=pj−k​...pj​且k<j的最大整数,k存在且k≥0无满足k​

  • 例如模式串pat=abcabcacabpat=abcabcacabpat=abcabcacab,有:
j 0 1 2 3 4 5 6 7 8 9
pat a b c a b c a c a b
f -1 -1 -1 0 1 2 3 -1 0 1

实现方法

  • 基于失配函数的另一种表达形式 :

f(j)={−1,j=0fm(j−1)+1,m是满足pfk(j−1)+1=pj的整数k的最小值−1,其他情况f(j)= \begin{cases} -1,&j=0\\ f^m(j-1)+1,&m是满足p_{f^k(j-1)+1}=p_j的整数k的最小值\\ -1,&其他情况 \end{cases} f(j)=⎩⎪⎨⎪⎧​−1,fm(j−1)+1,−1,​j=0m是满足pfk(j−1)+1​=pj​的整数k的最小值其他情况​
注意:f1(j)=f(j),fm(j)=f(fm−1(j))f^1(j)=f(j),f^m(j)=f(f^{m-1}(j))f1(j)=f(j),fm(j)=f(fm−1(j))

  • 代码如下:
//失配函数
int* FailureFunction(const string &pat)
{int lengthP = pat.size();int* f = new int[lengthP];int j = 0, k = -1;f[0] = -1;while (j < lengthP - 1){if (pat[j + 1] == pat[k + 1]){j++;k++;f[j] = k;}else if (k == -1){j++;f[j] = -1;}else k = f[k];}//返回f[lengthP]指针return f;
}

函数分析

  • 可知失配函数的时间复杂度为O(lengthP)O(lengthP)O(lengthP).

KMP函数

实现方法

  • 字符串(str的指针索引为PosSPosSPosS,模式串(pat的指针索引为PosPPosPPosP;
  • 在匹配失败回溯时,PosSPosSPosS不变,PosP=f[PosP−1]+1(PosP≠0)PosP=f[PosP-1]+1(PosP\ne0)PosP=f[PosP−1]+1(PosP​=0).
    代码如下:
int KMP(const string& str, const string& pat)
{int LengthP = pat.size(), LengthS = str.size();int* f;//生成失配函数f = FailureFunction(pat);//查询过程int PosP = 0, PosS = 0;while (PosP < LengthP && PosS < LengthS){if (pat[PosP] == str[PosS]){PosP++;PosS++;}else{if (PosP == 0)PosS++;else PosP = f[PosP - 1] + 1;}}//匹配到结果返回起始地址;//非匹配到结果返回-1return PosP < LengthP || LengthP == 0 ? -1 : PosS - PosP;
}

函数分析

  • 该算法的时间复杂度为O(lengthS+lengthP)O(lengthS+lengthP)O(lengthS+lengthP).

失配信息的另一种方法:next函数

定义

next[j]={−1,j=0nextm(j)+1,m是满足pnextk(j)=pj的整数k的最小值0,nextm(j)=−1next[j]= \begin{cases} -1,&j=0\\ next^m(j)+1,&m是满足p_{next^k(j)}=p_j的整数k的最小值\\ 0,&next^m(j)=-1 \end{cases} next[j]=⎩⎪⎨⎪⎧​−1,nextm(j)+1,0,​j=0m是满足pnextk(j)​=pj​的整数k的最小值nextm(j)=−1​

实现方法

//失配函数2
int* Next(const string & pat)
{int lengthP = pat.size();int* next = new int[lengthP];int j = 0, k = -1;next[0] = -1;while (j < lengthP){if (k == -1 || pat[j] == pat[k]){j++;k++;next[j] = k;}else k = next[k];}return next;
}
  • 此时KMP算法如下:
int KMP(const string& str, const string& pat)
{int LengthP = pat.size(), LengthS = str.size();int* f;//生成失配函数2f = Next(pat);//查询过程int PosP = 0, PosS = 0;while (PosP < LengthP && PosS < LengthS){if (pat[PosP] == str[PosS]){PosP++;PosS++;}else{if (PosP == 0)PosS++;else PosP = next[PosP];}}//匹配到结果返回起始地址;//非匹配到结果返回-1return PosP < LengthP || LengthP == 0 ? -1 : PosS - PosP;
}

代码总览

#include<iostream>
using namespace std;// 若匹配返回匹配起始地址,否则返回-1
int Find(const string &str, const string &pat)
{//获取字符串长度int lengthS = str.size();int lengthP = pat.size();for (int s = 0; s < lengthS; s++){int j;for (j = 0; j < lengthP && str[s + j] == pat[j]; j++);//匹配到结果返回起始地址if (j == lengthP)return s;}//非匹配到结果return -1;
}//失配函数1
int* FailureFunction(const string &pat)
{int lengthP = pat.size();int* f = new int[lengthP];int j = 0, k = -1;f[0] = -1;while (j < lengthP - 1){if (pat[j + 1] == pat[k + 1]){j++;k++;f[j] = k;}else if (k == -1){j++;f[j] = -1;}else k = f[k];}//返回f[lengthP]指针return f;
}//失配函数2
int* Next(const string & pat)
{int lengthP = pat.size();int* next = new int[lengthP];int j = 0, k = -1;next[0] = -1;while (j < lengthP){if (k == -1 || pat[j] == pat[k]){j++;k++;next[j] = k;}else k = next[k];}return next;
}int KMP(const string& str, const string& pat)
{int LengthP = pat.size(), LengthS = str.size();int* f;//生成失配函数1f = FailureFunction(pat);//生成失配函数2//f = Next(pat);//查询过程int PosP = 0, PosS = 0;while (PosP < LengthP && PosS < LengthS){if (pat[PosP] == str[PosS]){PosP++;PosS++;}else{if (PosP == 0)PosS++;else PosP = f[PosP - 1] + 1;//PosP = next[PosP];}}//匹配到结果返回起始地址;//非匹配到结果返回-1return PosP < LengthP || LengthP == 0 ? -1 : PosS - PosP;
}int main()
{string str = "abcasdababcabcacbddfhs", pat = "abcabcacbd";cout << KMP(str, pat) << endl;
}

上一篇:数据结构-数组-稀疏矩阵表示与多维矩阵(转置、加法、乘法,附完整代码)

数据结构-数组-字符串匹配:Knuth-Morris-Pratt算法(详解附完整代码)相关推荐

  1. Python:实现前缀Knuth–Morris–Pratt 算法(附完整源码)

    Python:实现前缀Knuth–Morris–Pratt 算法 def prefix_function(input_string: str) -> list:# list for the re ...

  2. AC自动机算法详解以及Java代码实现

    详细介绍了AC自动机算法详解以及Java代码实现. 文章目录 1 概念和原理 2 节点定义 3 构建Trie前缀树 3.1 案例演示 4 构建fail失配指针 4.1 案例演示 5 匹配文本 5.1 ...

  3. 编辑距离算法详解和python代码

    编辑距离(Levenshtein Distance)算法详解和python代码 最近做NLP用到了编辑距离,网上学习了很多,看到很多博客写的有问题,这里做一个编辑距离的算法介绍,步骤和多种python ...

  4. kmeans算法详解和python代码实现

    kmeans算法详解和python代码实现 kmeans算法 无监督学习和监督学习 监督学习: 是通过已知类别的样本分类器的参数,来达到所要求性能的过程 简单来说,就是让计算机去学习我们已经创建好了的 ...

  5. python直线拟合_RANSAC算法详解(附Python拟合直线模型代码)

    之前只是简单了解RANSAC模型,知道它是干什么的.然后今天有个课程设计的报告,上去讲了一下RANSAC,感觉这个东西也没那么复杂,所以今天就总结一些RASAC并用Python实现一下直线拟合. RA ...

  6. paxos算法详解以及模拟代码

    0 paxos算法解决了什么问题 现在有n个人组成提一个会议,这个会议的目的是为了确定今年的税率,那么每个人都会提出自己认为的今年的合理的税率,为了大家能够达成一致,有了paxos算法.实际里,这个会 ...

  7. 敏感词或关键词过滤,DFA算法详解及python代码实现

    一.前言 近期项目有了一个过滤敏感词的功能需求,在网上找了一些方法及解说,发现DFA算法比较好用,容易实现,但很多文章解释得不太清楚,这里将其详细描述,并用python代码实现. 二.DFA算法详解 ...

  8. 基于多相滤波器的数字信道化算法详解(Python, Verilog代码已开源)

    基于多相滤波器的数字信道化算法详解 推导过程 总结 仿真 本文详细介绍了基于多相滤波器的数字信道化算法的推导过程, 如果您在阅读的过程中发现算法推导过程中有任何错误, 请不吝指出. 此外, 进入我的G ...

  9. 【数据结构Note4】-串、数组和广义表(kmp算法详解)

    文章目录 串.数组和广义表 1. 串 1.1 串的概念和结构 1.2 顺序串和链串 1.3 BF算法--串的模式匹配法之一 1.5 KMP算法--串的模式匹配法之一 1.5.1 next数组 1.5. ...

最新文章

  1. 【BZOJ】2120: 数颜色
  2. [BZOJ] 4552: [Tjoi2016Heoi2016]排序
  3. Graph Embedding学习笔记(3):Graph Convolution Networks
  4. c++ 获取时间戳_分布式系统理论基础三-时间、时钟和事件顺序
  5. C# http://xamarin.com/
  6. Python中将array类型不按科学计数法存在文件中的方法
  7. oracle行转列 case,Oracle 行转列总结 Case When,Decode,PIVOT 三种方式
  8. Spark在美团的实践
  9. strtotime 获取一个月的开始 或者一个月的结束
  10. 《刺激战场》正式停服!换了个马甲又来了 开启收费变现模式?
  11. POJ 2425 A Chess Game(有向图SG函数)题解
  12. Mac使用技巧:清除 MacBook 上的浏览器缓存
  13. 计算机二进制计算过程
  14. 对抗样本的创建和防御
  15. Intel Edison 装Debian系统
  16. 实体机安装双系统多系统教程 及引导修复指南
  17. JS两个日期之间计算时间差
  18. LOGO的国际标准规范
  19. 豆瓣 9.3 的高分,牛逼的 Git!
  20. 简述Thread的interrupt()、interrupted()及isInterrupted()的区别

热门文章

  1. 行业轮动(股票)——Python量化
  2. 如何提高高通AR的Image识别率
  3. griffin与Livy、hdfs、ES7 、kerberos的集成
  4. matlab求逆矩阵以及行列式,求矩阵行列式和逆,手写的MATLAB程序
  5. 五个网站查物种基因组大小
  6. 直播回顾|基于TESSY的测试用例自动化评估与优化
  7. 开源DNS服务器与安装配置
  8. Consider defining a bean of type 'com.thw.db.service.IArticleService' in your configuration
  9. DHCP snooping
  10. 神经网络和深度学习简史(一)