4.2 字符串模式匹配
来源:互联网 发布:淘宝活动报名入口 编辑:程序博客网 时间:2024/06/07 00:34
概念
- 模式匹配的定义:对于一个长度为
n 的文本字符串s[1..n] ,且长度为m 的模式字符串t[1..m] 。其中满足m≤n 。s 和t 都来自有限字符集合Σ 。(例如小写英文字母集Σ={a,b,c,d,...,z} )如果s[r+1..r+m]=t[1..m] ,则t 在s 中出现,其中偏移为r 称为有效偏移。模式匹配问题需要求解到所有的rk ,使t 在s 以rk 的有效偏移出现。
朴素(Brute-Force)算法
既然模式匹配的目标是得到这样的集合
因此朴素算法的核心思想是,穷举从
因此可以得到如下的算法(为了通用起见和隐藏实际的地址指针,这里选择标准库的std::string
类型来描述字符串。):
/*** @brief 字符串模式匹配暴力算法* @param const std::string & source 文本串* @param const std::string & pattern 模式串* @return std::vector<int> 找到的模式串位于文本串的起始位置,没找到则为空*/std::vector<int> naive_string_match(const std::string & source, const std::string & pattern){ std::vector<int> result; int i, j; int end = source.length() - pattern.length() + 1; //确保i + j不会越过source的末尾 for(i = 0; i < end; i++){ for(j = 0; j < pattern.length(); j++) if(source[i + j] != pattern[j]) break; if(j == pattern.length()) result.push_back(i); } return result;}
注意外层循环条件end
的取值是source[i + j]
的取值的最大值为
对其进行时间复杂度分析,最内层循环执行
KMP算法
Knuth, Morris, Pratt算法,简称KMP算法,是一种高效的模式匹配算法,其核心在于先对子串进行预处理,得到一个辅助的前缀函数
有限自动机(DFA)
对于KMP算法的理解,介绍一下有限自动机(deterministic finite-state automaton, DFA)是很有帮助的。
一个有限自动机
M=(Q,q0,A,Σ,δ) 。
其中Q 是所有可能的状态集合,q0∈Q 表示自动机的初始状态,A⊆Q 表示自动机的终态集合(也就是自动机被接受的所有状态),Σ 是所有输入的字符集合,δ 是一个状态转移函数,是一个由Q 和Σ 的笛卡儿积(Q×Σ )到Q 的函数。
自动机从q0 状态开始。对于任意的状态q ,每读取一个字符a ,其状态就由q 变为δ(q,a) 。
自动机的状态转换类似于时序电路中的状态,下一个状态依赖于当前状态和输入。
DFA的模式匹配
对于一个任意的模式串
假设模式串ababaca
。 字符集
则由该模式串产生的自动机a
,ab
,aba
,abab
,ababa
,ababac
,ababaca
。
因此
状态0,如果输入为a
,则进入状态1,反之依然为状态0;
状态1,如果输入为b
,则进入状态2,输入为a
,进入状态1,输入为c
,进入状态0。
状态2,如果输入为a
,则进入状态3,如果输入为b
或c
,则进入状态0.
…(以此类推。)
上图对上例的字符集abababc
进行模式匹配构造的自动机的状态图。7为接受状态。
这样在匹配的过程中,只需要自动机进行到第7个状态,则完成匹配。
利用自动机进行匹配的核心在于:状态
计算转移函数,设
当
以上面例子的状态5为例,
(1)
(2)b
以后,能回退的最大长度,则需要找到前缀与新加进字符b
以后的后缀相等的字符串的长度,很明显未加入b
时,ababa
,而键入b
后得到ababab
,因此取的字符串为abab
,长度为4,即
(3)
由此可见,计算
由DFA到KMP
对于自动机来说,因为
在KMP算法中,引入了辅助的前缀函数
定义
π(q)=max{k:k<q∧t[1..k]⊐t[1..q]}
其中x⊐y 表示x 是y 的后缀
需要注意的是,与
满足了上面的
下面的问题在于如何求取
同样以ababaca
为例,已经求得c
字符后得到的后缀abac
与同样长度的前缀abab
不等,于是应该回退,c
之前,在长度为c
加入后能否满足。显然ab
ac
。再次重复上面的操作,直到a
与c
,产生的新串并不等,于是依然有
求前缀函数的方法也明确以后,就不难写出KMP的匹配算法了。
/*** @brief 计算KMP的前缀函数数组* @param const std::string & pattern 模式字符串* @return std::vector<int> 前缀函数数组*/std::vector<int> kmp_prefix_func(const std::string & pattern){ std::vector<int> result; int k = 0; //满足条件的最大前缀数组长度 result.push_back(0); //初始条件 for(int i = 1; i < pattern.length(); i++){ while(k > 0 && pattern[k] != pattern[i]){ //C/C++数组下标以0开始,pattern[k]就是前缀数组要新加入的字符 k = result[k - 1]; } if(pattern[k] == pattern[i]){ k++; } result.push_back(k); } return result;}/*** @brief KMP字符串模式匹配主函数* @param const std::string & text 文本字符串* @param const std::string & pattern 模式字符串* @return std::vector<int> 所有找到的下标(以0开始)*/std::vector<int> kmp_matcher(const std::string & text, const std::string & pattern){ std::vector<int> prefix = kmp_prefix_func(pattern); //计算前缀函数 std::vector<int> result; int q = 0; //设置状态初始值 for(int i = 0; i < text.length(); i++){ while(q > 0 && text[i] != pattern[q]){ //C/C++数组下标以0开始,pattern[q]就是前缀数组要新加入的字符 q = prefix[q - 1]; } if(pattern[q] == text[i]){ q++; } if(q == pattern.length()){ result.push_back(i - (q - 1)); //找到了,加入进返回值数组 q = prefix[q - 1]; //更新状态,查找下一个 } } return result;}
- 4.2 字符串模式匹配
- 字符串模式匹配
- 字符串模式匹配
- 字符串模式匹配
- 字符串模式匹配算法
- 字符串模式匹配算法
- sql字符串模式匹配
- 字符串模式匹配算法
- 字符串的模式匹配
- 字符串的模式匹配
- 字符串模式匹配
- 多模式字符串匹配
- 字符串模式匹配
- 字符串模式匹配
- 字符串模式匹配
- KMP模式匹配字符串
- 字符串模式匹配
- 字符串模式匹配
- 树莓派GPIO入门11-驱动液晶屏幕(一)
- SEO优化用户体验如何做好呢
- cf870c Maximum splitting 题解
- 简单计算(入门)
- struts2的命名规则
- 4.2 字符串模式匹配
- IIS服务器安全关注点
- XML学习笔记
- Leetcode-DFS+BST
- Xlistview 的使用
- NOIP 2009 靶形数独
- java 集合之TreeSet的用法
- 关于maven的学习资料都会发布到这里
- 常用排序小结