字符串模式匹配(一)——单模匹配(KMP)
字符串模式匹配(一)——单模匹配(KMP)
单模匹配的常用算法为KMP,多模匹配常用算法为AC自动机。
暴力匹配法(BF, O ( ∣ S ∣ × ∣ T ∣ ) O(|S|\times |T|) O(∣S∣×∣T∣))
设源串 S S S的匹配指针为 i i i,模式串 T T T的匹配指针为 j j j。暴力法通过逐个比较 S S S与 T T T中的每个字符进行模式匹配。
算法流程:循环执行下列步骤,直到 i i i越界,匹配结束:
- 若
S
[
i
]
=
T
[
j
]
S[i]=T[j]
S[i]=T[j],该点成功匹配,继续比较。若
j
j
j越界则为成功匹配,
j=0
进行下次匹配。 - 若
S
[
i
]
≠
T
[
j
]
S[i]\ne T[j]
S[i]=T[j],则为二者的失配点,回溯二者的匹配指针。二者在失配前均为成功匹配,匹配指针一直同步变化,
j
j
j为相对于匹配开始点的增量,因此本轮匹配起点即为
i
−
j
i-j
i−j。
i
i
i回溯到起点的下一个位置
i=i-j+1
,j=0
。
string s,t;
vector<int>ans;//记录所有成功匹配开始点下标
void bf(){
int i=0,j=0;
while(i<s.size()){
if(t[j]==s[i]){
i++,j++;
if(j>=t.size()) ans.push_back(i-t.size()),j=0;//j越界则产生答案
}else i=i-j+1,j=0;//匹配失败,回溯
}
}
KMP( O ( ∣ S ∣ + ∣ T ∣ ) O(|S|+|T|) O(∣S∣+∣T∣))
KMP是在BF上进行改进的单模匹配算法,其主要改进是最长公共前后缀数组,以避免大量的非必要回溯。
大量回溯的不必要性
设源串 S S S的匹配指针为 i i i,模式串 T T T的匹配指针为 j j j。设当前轮匹配的失配点前, S S S中成功匹配部分为其子串 S ′ S' S′, T T T中成功匹配部分为其前缀 T ′ T' T′, S ′ S' S′与 T ′ T' T′完全相同。
- 若
T
′
T'
T′中每个字符都不同:失配时暴力法的回溯为
i=i-j+1
,j=0
,但 i i i的回溯是完全不必要的。由于 T ′ T' T′中每个字符都不同,因此S[i-j+1]
也不可能与T[0]
相同。 i i i不动,j=0
即可。 - 若
T
′
T'
T′中有部分字符相同,且相同部分为
T
′
T'
T′的长度为
L
L
L的前缀和后缀:
S
′
S'
S′也具有等长的相同前后缀,因此
T
′
T'
T′前缀与
S
′
S'
S′后缀已经匹配,这部分无需重复匹配。失配时
i
i
i不动,
j
j
j移动到
T
′
T'
T′前缀的下个位置
L
继续匹配即可。 - 若
T
′
T'
T′中部分字符相同,且相同部分并非
T
′
T'
T′的前缀和后缀:此种情况等价于
T
′
T'
T′中每个字符都不同。
i
i
i不动,
j=0
即可。
由此可得, i i i完全没有必要回溯,只对 j j j回溯即可。
最长公共前后缀数组
定义数组 N e x t [ j ] Next[j] Next[j],设模式串 T T T的前 j − 1 j-1 j−1个字符构成其前缀 T ′ T' T′,则 N e x t [ j ] Next[j] Next[j]存储 T ′ T' T′的前缀与后缀的最长交集长度(注意此处均指不包含 T ′ T' T′自身的真前后缀),则 T ′ T' T′的后缀中最后下标为 j − 1 j-1 j−1, T ′ T' T′前缀中最后下标为 N e x t [ j ] − 1 Next[j]-1 Next[j]−1(公共长度-1,注意字符串下标从 0 0 0开始),代表当失配时, j j j所回溯的位置。
- 若 T [ i ] = T [ j ] T[i]=T[j] T[i]=T[j],则 [ 0 , i ] [0,i] [0,i]的长度转移自 [ 0 , i − 1 ] [0,i-1] [0,i−1]的长度并 + 1 +1 +1。 N e x t [ i + 1 ] = N e x t [ i ] + 1 Next[i+1]=Next[i]+1 Next[i+1]=Next[i]+1。
- 若 T [ i ] ≠ T [ j ] T[i]\ne T[j] T[i]=T[j],则前缀与后缀失配,属于回溯分析中的情况3,后缀失去后缀特性,无法在KMP中用于直接跳跃, j j j回溯。 N e x t [ i + 1 ] = 0 , j = N e x t [ j ] Next[i+1]=0,j=Next[j] Next[i+1]=0,j=Next[j]。
获取最长公共前后缀数组的本质:模式串本身在进行自我模式匹配。
string s,t;//下标从0开始
vector<int>Next,ans;//注意next是cpp的关键字
void getNext(){//本质:模式串在进行自我模式匹配
Next.resize(t.size()+1);
Next[0]=0,Next[1]=0;//Next[0]不用;由于在找真前后缀的最长交集长度,因此Next[1]为0
for(int i=1;i<t.size();i++){
int j=Next[i];
while(j&&t[i]!=t[j]) j=Next[j];//j在失配点跳跃到Next[j]
if(t[i]==t[j]) Next[i+1]=j+1;//[0,i]的答案转移自[0,i-1]+1
else Next[i+1]=0;//后缀失去后缀特性
}
}
void kmp(){
getNext();
int j=0;
for(int i=0;i<s.size();i++){
while(j&&s[i]!=t[j]) j=Next[j];//j在失配点跳跃到Next[j]
if(s[i]==t[j]) j++;
if(j==t.size()) ans.push_back(i+1-t.size());//j越界,则匹配成功
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具