【GDUT】simone牌文本编辑器

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后面的子串```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值