两种简单的字符串匹配算法
在机试笔记4中的统计子字符串个数题目中,使用了一种时间复杂度为O(n*m)的字符串匹配算法,他也叫暴力匹配算法或者BF算法。在实际的开发中,它却是一个比较常用的字符串匹配算法。原因有以下几点:
第一,实际的软件开发中,大部分情况下,模式串和主串的长度都不会太长。而且每次模式串与主串中的子串匹配的时候,当中途遇到不能匹配的字符的时候,就可以就停止了,不需要把 m 个字符都比对一下。所以,尽管理论上的最坏情况时间复杂度是 O(n*m),但是,统计意义上,大部分情况下,算法执行效率要比这个高很多。
第二,朴素字符串匹配算法思想简单,代码实现也非常简单。简单意味着不容易出错,如果有 bug 也容易暴露和修复。在工程中,在满足性能要求的前提下,简单是首选。这也是我们常说的KISS(Keep it Simple and Stupid)设计原则。
所以,在实际的软件开发中,绝大部分情况下,朴素的字符串匹配算法就够用了。
一个长为n的主串中去查找长为m的模式串,最多要对比n-m+1个子串与模式串。
另一个字符串匹配算法叫RK算法,它的算法思想是:
我们通过哈希算法对主串中的 n-m+1 个子串分别求哈希值,然后逐个与模式串的哈希值比较大小。如果某个子串的哈希值与模式串相等,那就说明对应的子串和模式串匹配了。这里当然会有哈希冲突的可能。
哈希函数的设计:
假设字符串只有小写字母,可以把每个子字符串当成一个26进制数,哈希函数就是把这个26进制数转成10进制数。
代码如下:
#include <bits/stdc++.h> using namespace std; double my_hash(string str) { double sum = 0; int j=0; for(int i=str.size();i>=0;i--){ double index = pow(26,j); sum+=(str[i]-'a')*index; j++; } return sum; } int main() { int n; int m; string a,b; cin >> n>> m; getchar(); getline(cin,a); getline(cin,b); double* arr = new double[n-m+1]; for(int i=0;i<n-m+1;i++){ string c = a.substr(i,m); arr[i]=my_hash(c); } double v = my_hash(b); int sum = 0; for(int i=0;i<n-m+1;i++){ if(arr[i]==v) sum++; } cout << sum<< endl; return 0; }
当然这个代码还有需要优化的地方,一是计算指数可以变成查表的方式,二是求其十进制时前后两个子串的计算是有交集的。