一、串

串是由零个或多个字符串组成的有限序列

(一)、串的定义

定长顺序存储

  1. 特点:每个串变量分配一个固定长度的存储区,即定长数组

  2. 定义:

    #define MAXLEN 255
    typedef struct{
    char ch[MAXLEN];
    int length;
    }SString;

堆分配存储表示

这里的堆是指c语言中存在一个称之为"堆"的自由存储区,这里的堆是一个链表的结构,和数据结构中的堆是不同的!

  1. 特定:存储空间在程序执行过程中动态分配

  2. 定义

    typedef struct{
    char *ch;
    int length;
    }HString;

块链存储表示

  1. 特点:使用链表结构,每个节点可以存储4个字符

(二)、最小操作集

  1. 串赋值
  2. 串比较
  3. 求串长
  4. 串联结
  5. 求子串

二、串的模式匹配

模式匹配:子串的定位操作

(一)、简单的模式匹配算法

  1. 定义:暴力匹配算法

  2. 功能:在主串s1中查找子串s2,如果找得到就返回位置(下标+1),否则返回-1

  3. 思路:以在主串abababc中匹配子串abc为例

    i为当前匹配主串的位置,j为匹配子串的位置

    • 匹配s1[i]是否等于s2[j],相等到第二步,不相等则到第三步
    • 相等,i++,j++
    • 不相等,使i=i-j+1,j==0,倒退重新匹配
    • 重复以上操作,直到i==strlen(s1)j==strlen(s2)
  4. code

    #include <stdio.h>
    #include <stdlib.h>
    #include <iostream>
    #include <algorithm>
    #include <cstring> using namespace std; int cmp(string s1,string s2){ //s1主串,s1子串
    int ans,i=0,j=0,len1=s1.length(),len2=s2.length();
    while(i<len1&&j<len2){ //注意len与下标差1
    //cout << "i:" << i << " j:" << j << endl;
    if(s1[i]==s2[j]){
    i++,j++;
    }else{
    i=i-j+1; //上一个匹配初始位的下一个pos
    j=0;
    }
    }
    ans=j==len2?i-j:-1;
    return ans+1; //注意pos是下标位置,下标->位置
    } int main(){
    string s1,s2;
    cin >> s1 >> s2;
    int pos = cmp(s1,s2);
    printf("pos:%d\n",pos);
    }
  5. 时间复杂度:O(m*n)(设strlen(s1)=n,strlen(s2)=m)

  6. 主要存在的问题:

    • 显然s1至少要匹配n-m+1
    • 我们可以想办法通过空间换时间的方式减少s2的匹配次数(即想办法膜除第三步中回退的过程)

(二)、KMP算法

解决暴力匹配过程中回退的问题

相关概念

  1. 前缀:除最后一个字符,字符串的所有头部子串
  2. 后缀:除第一个字符外,字符串的所有尾部子串
  3. 部分匹配:字符串的前缀后缀最长相等前后缀长度

具体操作

  1. 列出最长相等前后缀长度

    以待匹配字符串ababa为例

    序号 子字符串 前缀 后缀 最长相等前后缀长度
    1 a 0
    2 ab b a 0
    3 aba a,ab ba,a 1
    4 abab a,ab,aba bab,ab,b 2
    5 ababa a,ab,aba,abab baba,aba,ba,a 3
  2. 构建部分匹配值表(Partial Match)

    编号 1 2 3 4 5
    s a b a b a
    pm(上表最后一列) 0 0 1 2 3
  3. 使用(为了方便理解算法,我们这里用1作为下标起点)

    操作:

    • 匹配s1[i]是否等于s2[j]

    • 相等,i++,j++

    • 不相等,j=j-(j-1-pm[j-1]),即使子串回退

      回退的距离move=已匹配的字符数-对应的部分匹配值=j-1-pm[j-1]

    • 重复以上操作,直到i>=strlen(s1)j>=strlen(s2)

    如:

    s1:abacdababa

    s2:ababa

    • 当i=4,j=4,显然s1[i]!=s2[j]
    • j=4-(4-1-pm[4-1])=2,i不需要回退
  4. 优化pm表

    • 存在问题:

      • pm[5]对应第6个字符匹配失败,显然是用不到的

      优化:将pm表整体右移一格构成一张新表称为next,表示子串下一个应该匹配的位置,使next[1]=-1

      编号 1 2 3 4 5
      s a b a b a
      next -1 0 0 1 2

      此时:

      move=j-1-next[j]

      j=j-move=j-(j-1-next[j])=next[j]+1

      注:关于这里使用-1,王道给出的解释是"因为若是第一个元素匹配失败,则需要将子串向右移动一位,而不需要计算子串移动的位数",简单来说就是此时只要将主串左移,不需要move.(我靠,NewBee,写到这里突然悟了!!小黄鸭原理可以的.)

    • 继续改进:显然,我们可以之际在next[j]上加1出

      编号 1 2 3 4 5
      s a b a b a
      next 0 1 1 2 3

      注:(这里再备注一下next下标的意义)

      • next[i]=0,表示没有一个前缀可以匹配,主要作为标识符使用
      • next[i]=j],j表示有i个前缀可以匹配

      此时:

      move=next[j]

      j=next[j]

推理next数组的一般公式

  1. next函数的一般公式(设首位下标为1):

    • next[j]=0,j=1(即第一位不匹配时需要移动)
    • next[j]=max{k|1<k<j且\(`P_1...P_{k-1}`=`P_{j-k+1}...P_{j-1}`\)}
    • 1,其它
  2. 尝试通过已知next[j]推导next[j+1]:

    k=next[j],s2为子串

    • k=0,则next[j+1]=next[j]+1
    • s2[j]=s2[k],则next[j+1]=next[j]+1
    • s2[j]!=s2[k],则k=next[k],继续循环匹配

编写代码

#include <bits/stdc++.h>

using namespace std;

const int N=1e5+10;

string s1,s2;
int nextValue[N]; void getNext(string s,int *nextValue){
int now=1,k=0,len=s.length();
nextValue[0]=-1; //设定nextValue[0]=-1,作为一个特殊的标识符表示第一个值没有匹配到
while(now<len){
if(k==-1){ //递归出口
nextValue[++now]=++k;
continue;
}
if(s[now]==s[k]){ //递归体
nextValue[++now]=++k;
}else{
k=nextValue[k];
}
}
} //找得到返回第一次出现的pos,否则返回-1
int kmpCmp(string s1,string s2){
int i=0,j=0,ans=-1;
int len1=s1.length(),len2=s2.length();
while(i<len1&&j<len2){
if(j==-1||s1[i]==s2[j]){
i++;
j++;
}else{
j=nextValue[j];
}
}
if(j==len2){
ans=i-len2+1;
}
return ans;
} int main()
{
cin >> s1;
cin >> s2;
getNext(s2,nextValue);
cout << kmpCmp(s1,s2);
return 0;
}

注:感觉在局部有点问题,没有找到好的测试样例

优化

  1. 问题产生:显然当s1[i]!=s2[j]时,我们会置换j=next[j].而当s1[i]!=s2[j],j=next[j]时,显然s1[i]!=s2[next[j]],这是一次失效的匹配

  2. 解决:当我们在创建next数组时,我们可以先判断,再通过next[j]=[next[j]]来消除这一情况(记住,这一过程是近似递归的!)

  3. 代码

    #include <bits/stdc++.h>
    
    using namespace std;
    
    const int N=1e5+10;
    
    string s1,s2;
    int nextValue[N]; void getNext(string s,int *nextValue){
    int now=1,k=0,len=s.length();
    nextValue[0]=-1; //设定nextValue[0]=-1,作为一个特殊的标识符表示第一个值没有匹配到
    while(now<len){
    if(k==-1){ //递归出口
    nextValue[++now]=++k;
    continue;
    }
    if(s[now]==s[k]){ //递归体
    nextValue[++now]=++k;
    }else{
    k=nextValue[k];
    }
    }
    } //找得到返回第一次出现的pos,否则返回-1
    int kmpCmp(string s1,string s2){
    int i=0,j=0,ans=-1;
    int len1=s1.length(),len2=s2.length();
    while(j<len2){
    if(j==-1||s1[i]==s2[j]){
    i++;
    j++;
    }else{
    if(s1[j]!=s1[nextValue[j]]){
    j=nextValue[j];
    }else{
    j=nextValue[nextValue[j]];
    }
    }
    }
    if(j==len2){
    ans=i-len2+1;
    }
    return ans;
    } int main()
    {
    cin >> s1;
    cin >> s2;
    getNext(s2,nextValue);
    cout << kmpCmp(s1,s2);
    return 0;
    }

串和KMP算法的更多相关文章

  1. hdu 3336:Count the string(数据结构,串,KMP算法)

    Count the string Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) ...

  2. 数据结构与算法JavaScript (五) 串(经典KMP算法)

    KMP算法和BM算法 KMP是前缀匹配和BM后缀匹配的经典算法,看得出来前缀匹配和后缀匹配的区别就仅仅在于比较的顺序不同 前缀匹配是指:模式串和母串的比较从左到右,模式串的移动也是从 左到右 后缀匹配 ...

  3. 第4章学习小结_串(BF&KMP算法)、数组(三元组)

    这一章学习之后,我想对串这个部分写一下我的总结体会. 串也有顺序和链式两种存储结构,但大多采用顺序存储结构比较方便.字符串定义可以用字符数组比如:char c[10];也可以用C++中定义一个字符串s ...

  4. 第十一章 串 (c3)KMP算法:理解next[]表

  5. 第十一章 串 (c2)KMP算法:查询表

  6. 第十一章 串 (c1)KMP算法:从记忆力到预知力

  7. 数据结构与算法5—KMP算法

    串的模式匹配算法 子串(模式串)的定位操作通常称为串的模式匹配. 这是串的一种重要操作,很多 软件,若有“编辑”菜单项的话, 则其中必有“查找”子菜单项. 串的顺序存储实现 #include<s ...

  8. 《数据结构》之串的模式匹配算法——KMP算法

    //串的模式匹配算法 //KMP算法,时间复杂度为O(n+m) #include <iostream> #include <string> #include <cstri ...

  9. 数据结构- 串的模式匹配算法:BF和 KMP算法

      数据结构- 串的模式匹配算法:BF和 KMP算法  Brute-Force算法的思想 1.BF(Brute-Force)算法 Brute-Force算法的基本思想是: 1) 从目标串s 的第一个字 ...

随机推荐

  1. Linux之shell变量

    一.变量名的规范 定义形如:class_name='xiaohemiao' 使用形如:echo $class_name 1.变量名后面的等号左右不能有空格 2.命名只能使用英文字母,数字和下划线,首个 ...

  2. go基础——goto语法

    package main import "fmt" func main() { a := 10 LOOP: for a < 20 { if a == 15 { a += 1 ...

  3. Java数据库连接池--DBCP浅析.

    一. 为何要使用数据库连接池假设网站一天有很大的访问量,数据库服务器就需要为每次连接创建一次数据库连接,极大的浪费数据库的资源,并且极易造成数据库服务器内存溢出.拓机.数据库连接是一种关键的有限的昂贵 ...

  4. 关于git和SVN的介绍和区别

    主要对git,svn进行一个简单的介绍. 顺带,我会在后面把我整理的一整套CSS3,PHP,MYSQL的开发的笔记打包放到百度云,有需要可以直接去百度云下载,这样以后你们开发就可以直接翻笔记不用百度搜 ...

  5. Spring Boot AOP 扫盲,实现接口访问的统一日志记录

    AOP 是 Spring 体系中非常重要的两个概念之一(另外一个是 IoC),今天这篇文章就来带大家通过实战的方式,在编程猫 SpringBoot 项目中使用 AOP 技术为 controller 层 ...

  6. mysql视图,索引

    一.视图 View 视图是一个虚拟表,是sql语句的查询结果,其内容由查询定义.同真实的表一样,视图包含一系列带有名称的列和行数据,在使用视图时动态生成.视图的数据变化会影响到基表,基表的数据变化也会 ...

  7. docker下安装nginx,启动ngixn,修改nginx配置等--超详细

    1.获取nginx版本 docker中nginx版本信息:https://hub.docker.com/_/nginx?tab=tags&page=1&ordering=last_up ...

  8. Linux C/C++ UDP 网络通信

    昨晚 Vv 想让我给她讲讲网络编程,于是我就傻乎乎的带她入了门... 以下内容为讲课时制作的笔记- 1. socket() 函数 1.1 头文件 #include<sys/socket.h> ...

  9. excel仪表盘制作,商业智能仪表盘的作用

    ​商业仪表盘被称为管理驾驶舱的重要组成部分,无论是管理决策者,还是企业业务流程中的普通员工,都可以利用它来展示分析的结果,让决策更加快速准确,更快地推动业务流程的进展,提高工作效率. 一个明确地了解自 ...

  10. 通过shell脚本批量操作mysql数据库

    创建建表语句 ============================================= 学生表:Student(Sno,Sname,Ssex,Sage,Sdept) ------(学 ...