simone牌文本编辑器
描述
Simone打算写一个功能强大的文本编辑器,并取一个炫酷拉风,高端优雅的名字,比如“simone牌文本编辑器”之类的。既然功能强大,那肯定得有个查找功能吧。但是她在完成这个功能的时候遇到一点小问题。现在来请求你的帮助。
给你一个文本串s,和一个模式串k,你需要写一个程序来查找k在s中出现了多少次。
输入
输入文件的第一行是一个正整数T,表示总共有T组测试数据。
接下来有T组测试,每组测试数据包括两行。第一行是文本串s,长度不大于10000。第二行是模式串k,长度不大于10。都是只有小写字母组成的字符串。
输出
每组测试对应输出一个正整数答案,表示k在s中出现了多少次。
输入样例 1
2
abababa
aba
abcabc
abc
输出样例 1
3
2
Solution
1.KMP算法
2.字符串哈希
3.暴力
Code
1.KMP
"KMP算法"(下标从1开始)(推荐写法)
const int N = 1e4 + 10;
char s[N],p[N]; //s为文本串,p为模式串
int ne[N];
int ans,T;
int main(){
cin >> T;
while(T --){
//初始化
ans = 0;
memset(ne,0,sizeof ne);
//memset(s,'\0',sizeof s); //字符串数组重复输入不需要初始化
//memset(p,'\0',sizeof p); //前面的会被覆盖,而后面cin时自动添加'\0' 不需要担心后面
//输入(字符数组的特殊输入方法)
cin >> s + 1 >> p + 1; //从下标1开始输入 //注意:string不能这样输入
//还有要注意的一点是,cin输入遇到空格换行会停止可以用cin.getline代替
//sizeof 和 strlen 区别
//int ls = sizeof(s); //sizeof 包含'\0';
//int lp = sizeof(p); //ls,lp = N
int ls = strlen(s+1); //strlen 不包含'\0'
int lp = strlen(p+1);
//KMP算法 (下标从1开始的做法)
//构建ne数组
for(int i = 2, j = 0; i <= lp; i ++ ){ //从第二位开始比较 //此处是p与p比较
while(j && p[i] != p[j + 1]) j = ne[j];
if(p[i] == p[j + 1]) j ++;
ne[i] = j;
}
for(int i = 1,j = 0; i <= ls; i ++ ){
while(j && s[i] != p[j + 1]) j = ne[j]; //如果单位匹配不成功,一直跳直到跳到起点或者成功匹配
if(s[i] == p[j + 1]) j ++; //如果单位匹配成功,下一位继续匹配
if(j == lp ){ //所有位匹配成功
j = ne[j]; //还要继续匹配
ans ++; //匹配成功后的逻辑
}
}
cout << ans << endl;
}
}
"KMP"(从0开始的做法)(注意区别)
ne[0] = -1;
for (int i = 1, j = -1; i < lp; i ++ )
{
while (j >= 0 && p[j + 1] != p[i]) j = ne[j];
if (p[j + 1] == p[i]) j ++ ;
ne[i] = j;
}
for (int i = 0, j = -1; i < ls; i ++ )
{
while (j != -1 && s[i] != p[j + 1]) j = ne[j];
if (s[i] == p[j + 1]) j ++ ;
if (j == lp - 1)
{
cout << i - j << ' ';
j = ne[j];
}
}
2.字符串哈希
"字符串哈希"(下标从0开始)
typedef unsigned long long ULL;
const int N = 1e4 + 10;
const int P = 131; //经验:不易重复
char str[N],pp[N]; //s为文本串,p为模式串
ULL h[N],p[N]; //字符串很长,对应的数太大,通过模 2^64 把它映射到 [0, 2^64 - 1] 溢出相当于取模
int ans,T;
ULL get(int l,int r){
if(l - 1 >= 0)return h[r] - h[l - 1] * p[r - l + 1]; //类似前缀和求区间和
else return h[r]; //加个特判
}
int main(){
p[0] = 1;
for(int i = 1; i < N; i ++ ) p[i] = p[i - 1] * P; //预处理p的n次方 (因为要用多次)
cin >> T;
while(T --){
ans = 0;
cin >> str >> pp; //字符串和h均不需要初始化
int ls = strlen(str);
int lpp = strlen(pp);
//将两个字符数组p进制化
//处理:秦九韶算法思想
h[0] = str[0];
for(int i = 1; i < ls; i ++ )h[i] = h[i - 1] * P + str[i]; //把字符串看成是一个 P 进制数,每个字符的 ASCII 码对应数的一位
ULL ppm = 0;
for(int i = 0; i < lpp; i ++ )ppm = ppm * P + pp[i];
for(int i = 0; i <= ls - lpp; i ++ ){ //注意边界的处理
if(get(i,lpp + i - 1) == ppm) ans ++;
}
cout << ans << endl;
}
return 0;
}
"字符串哈希"(下标从1开始)(注意区别)(推荐写法:更简洁)
ULL get(int l,int r){
return h[r] - h[l - 1] * p[r - l + 1]; //类似前缀和求区间和
}
int main(){
while(T --){
ans = 0;
cin >> str + 1 >> pp + 1; //字符串和h均不需要初始化
int ls = strlen(str + 1);
int lpp = strlen(pp + 1);
for(int i = 1; i <= ls; i ++ )h[i] = h[i - 1] * P + str[i];
ULL ppm = 0;
for(int i = 1; i <= lpp; i ++ )ppm = ppm * P + pp[i]; //注意边界处理即可
for(int i = 1; i <= ls - lpp + 1; i ++ ){
if(get(i,lpp + i - 1) == ppm) ans ++;
}
}
}
特别注意
hash表中的每一个元素i,即字符串对应的每一个字符,其hash值是从左到右由大到小的,符合我们正常写自然数的习惯;而hash表中的所有元素之间,按照我们正常写数组的顺序,从左到右元素中hash值是有小到大的,所以,对应字符串“ABCDEF”,h[‘A’]的hash值是最小的,而字符’A’在hash表每一个元素中的p^是最高位的。
3.暴力
"substr谨慎使用"(时间是以上两种方法5倍以上)
string s,p;
int ans,T;
int main(){
cin >> T;
while(T --){
ans = 0;
cin >> s >> p;
int ls = s.size();
int lp = p.size();
for(int i = 0; i <= ls - lp; i ++ ){
if(s.substr(i,lp) == p)ans ++; //利用string可以直接比较的性质
}
cout << ans << endl;
}
return 0;
}
补充:substr的用法
1.作用
截取子串
2.用法
字符串名.substr(下标,个数);
具体参考以下:
string a = "0123456789"; //输出结果
cout << a.substr(9, 4) << endl; 9 //越界了只输出在界限内的子串
cout << a.substr(4, 4) << endl; 4567 //输出以4为下标后面4个数
cout << a.substr( 4) << endl; 456789 //输出下标为4以及4后面的子串```