数据结构 四、字符串 和 KMP算法
串:内容受限的线性表
(数据元素只能是字符)
串:String--- 字符组成的有限序列
顺序储存用的多
案例:病毒感染检测(病毒dna环状)
结构类型定义
#define MAXLEN 255 typedef struct { char ch[MAXLEN+1]; int length; }SString;
- 下面是链串定义
#define CHUNKIZE 80//块大小 typedef struct Chunk //块 { char ch[CHUNKIZE]; struct Chunk *next; }Chunk; typedef struct { Chunk *head,*tail;//头指针,尾指针 int curlen;//串的当前长度 }LString; //字符串的块链结
模式匹配算法
提示:
【如果某串是循环串,将该串连续储存两次,然后再匹配】
算法作用:
确定主串(正文串)所含字串(模式串)第一次出现的位置(定义)
分类:
- BF算法(暴力)( O(n*m) )
- KMP算法(O(n))
BF算法(暴力)
时间复杂度:( O(n*m) )
简述:
- 回溯:i={ i - ( j-1 ) } + 1=i-j+1+1
- 重头开始:j=1;
- 字串出现位置:POS=i-t.length
代码:
就不解释了,一看就懂
int index_BF(String S,String T,int pos) //S表示主串(被匹配串),T表示匹配串 //pos表示从主串第pos个位置开始匹配 { int i=pos;//i指向主串 int j=0;//j指向匹配串 while(i<S.length&&j<T.length) { if(S.ch[i]==T.ch[j]) { i++; j++; } else { i=i-j+1; j=0; } } if(j>=T.length) { return i-T.length; } else return -1; }
KMP算法
概述
优点: 速度快
- 主串 不必回溯:
- 模式串 不必重头开始:j=1;
难点:获取next[j]
- next[j]含义1:当第 j 个字符与主串中相应的字符“匹配失败”时,模式串 重新与主链匹配 的位置
- 含义2:next[j] = k 代表T[j] 之前的子串中,有长度为k 的相同前缀和后缀。
算法实现
前期准备:
列出所有公共子序列,求最长公共前后缀
得到前缀表
KMP过程:
流程:
- 假设现在文本串S匹配到 i 位置,模式串P匹配到 j 位置
- 如果j = -1,或者当前字符匹配成功(即S[i] == P[j]),都令i++,j++,继续匹配下一个字符;
- 如果j != -1,且当前字符匹配失败,则令 i 不变,j = next[j]。此举意味着失配时,模式串P相对于文本串S向右移动了j - next [j] 位。
(匹配正确,匹配字串下一个)
错误匹配
看前缀表,从前缀表写的位置开始匹配
代码实现
获取next[j]
含义:
next[j] = k 代表T[j] 之前的子串中,有长度为k 的相同前缀和后缀。
思想:
只有 较长字串比较短字串多的那个字符,是较短字符的最长公共前后缀的前缀的下一个字符, 才有 较长字串的最长公共前后缀的长度是较短的长度+1;
否则就是和找上一个,直到找到匹配的
便于理解代码1(使用结构体):
void get_next(String T,int next[]) { next[0]=0; /* 作用:初始化 含义:当串T第一个元素匹配失败后, 下一次匹配串 T还是从第1格元素开始匹配 */ int len=0; /* len含义: 前一个的最长公共子序列长度 即:最后一个元素所在位置 */ int i=1; /* i含义: . 正在检测串的第i个字母 */ while(i<T.length) { if(T.ch[i]==T.ch[len])// { len++; next[i]=len; i++; } else { if(len>0) { /*aabcd ae aabce 此时len指向d,i指向e 然后去找前一个最长公共序列长度.*/ len=next[len-1]; } else { next[i]=len;//即next[i]=0; i++; } } } //为了方便运算,向后移一位 for(i=T.length; i>0; i--) { next[i]=next[i-1]; } next[0]=-1; }
上面代码便于理解,但是最后需要整体向后移动一位,下面代码直接创建next数组时候便建立向后移动一位的代码
便于理解代码2:
char S[100]; char T[100]; int slen; int tlen; void get_next(int next[]) { next[0]=-1; int alpha=-1; int j=0; while(j<tlen) { if(alpha==-1||T[alpha]==T[j]) { alpha++; j++; next[j]==(alpha+1)-1; //+1代表让第k个字符,所处位置是k(而不是k-1) //-1代表处理的是前一个串 } else { alpha=next[alpha]; } } }
解释1:为什么 alpha=next[alpha];
根据对称性,只需再判断p[next[k]]与p[j]是否相等即可,于是令k = next[k]
上面代码已经完善,但是还有个小问题,匹配串有重复时,可以再优化
案例
此时匹配失败
跳转到该处
但是跳转到该处,必然失配。
原因:
- 当p[j] != s[i] 时,下次匹配必然是p[ next [j]] 跟s[i]匹配,
- 如果p[j] = p[ next[j] ],必然导致后一步匹配失败
(因为p[j]已经跟s[i]失配,然后你还用跟p[j]等同的值p[next[j]]去跟s[i]匹配,很显然,必然失配) - 所以不能允许p[j] = p[ next[j ]]。
解决方案:
令next[j] = next[ next[j] ],即可解决,动手画画就可以理解
最终代码:
char S[100]; char T[100]; int slen; int tlen; void get_next(int next[]) { next[0]=-1; int alpha=-1; int j=0; while(j<tlen) { if(alpha==-1||T[alpha]==T[j]) { alpha++; j++; if(T[j]!=T[alpha]) { next[j]==alpha; } else { next[j]=next[alpha]; } } else { alpha=next[alpha]; } } }
index_KMP
常用版本:
void index_KMP() { int next[tlen]; GetNext(next); int i=0; int j=0; while(i<slen) { if(j==tlen-1&&S[i]==T[j]) { cout<<i-j+1<<endl; return; } if(j==-1||S[i]==T[j]) { i++; j++; } else { j=next[j]; } } cout<<"-1"<<endl; }
使用结构体版:
void index_KMP(String S,String T,int pos) { int i=pos; int j=0; int next[T.length]; get_next(T,next); while(i<S.length) { if(j==T.length-1 && S.ch[i]==T.ch[j]) { printf("%d\n",i-j+1); return; //j=next[j]; /*如果不return,就会搜寻下一个符合的*/ } if(j==-1||S.ch[i]==T.ch[j]) { i++; j++; } else { j=next[j]; } } }
模板代码
#include <bits/stdc++.h> using namespace std; const int N = 2e6 + 10; #define endl '\n' #define ll long long void init() { ios::sync_with_stdio(false); cin.tie(NULL); cout.tie(NULL); ios::sync_with_stdio(false); } char s[N]; char t[N]; int slen; int tlen; int nex[N]; void get_next() { nex[0] = -1; int pre = -1; int j = 0; while (j < tlen) { if (pre == -1 || t[pre] == t[j]) { pre++; j++; if (t[j] != t[pre]) nex[j] = pre; else nex[j] = nex[pre]; } else pre = nex[pre]; } } void index_KMP() { get_next(); int i = 0; int j = 0; while (i < slen) { if (j == tlen - 1 && s[i] == t[j]) { cout << i - j + 1 << endl; return; } if (j == -1 || s[i] == t[j]) { i++; j++; } else j = nex[j]; } cout << "-1" << endl; } signed main() { init(); while (cin >> s) { cin >> t; tlen = strlen(t); slen = strlen(s); index_KMP(); } return 0; }
本文作者:kingwzun
本文链接:https://www.cnblogs.com/kingwz/p/15188370.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步