题目:
判断字符串s中是否包含字符串p(长度均小于1000)。
输入占两行,分别为字符串s和字符串p。
输出包含字符串s中字符串p的起始下标,若有多个匹配则全部输出,每个输出占一行。
案例:
输入:
ABABABA
ABA
输出:
0
2
4
初步探究Pabinkarp算法思路:
先算出字符串p的哈希值,逐一遍历s中长度与字符串p相等的子串(连续)(时间复杂度n),算出其哈希值(时间复杂度m)并与字符串p的哈希值做对比,相同则匹配成功,输出即可。该思路时间复杂度为n*m(n和m分别是字符串s和字符串p的长度),代码如下:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
void match(char *p,char *s);
ll hash(char *s);
const int seed = 31;
const int eps = 1e7+10;
char s[1005];
char p[1005];
int main() {
gets(s);
gets(p);
match(p,s);
return 0;
}
void match(char *p,char *s) {
ll p_hash = hash(p);
int p_len = strlen(p);
int s_len = strlen(s);
//遍历字符串s中长度与字符串p相等的连续字符串
for(int i=0;i+p_len<=s_len;i++) { //注意边界
char t[p_len];
int k = 0;
for(int j=i;j<i+p_len;j++,k++)//取出要检验的字符串
t[k] = s[j];
t[k] = '\0';
ll i_hash = hash(t);
if(i_hash==p_hash) {
printf("%d\n",i);//哈希值相等就输出
}
}
}
ll hash(char *s) {
ll s_hash = 0;
int n = strlen(s);
for(int i=0;i<n;i++)
s_hash = s_hash*seed + (int)s[i];
s_hash %= eps;
return s_hash;
}
不难发现,这种思路和直接暴力匹配在时间复杂度上时一样的,于是我们考虑进一步优化,这种思路类似于预处理,就是直接构造一个哈希数组保存所有字串的哈希值,这样我们就可以用线性的时间复杂度去做匹配。构造哈希数组的方法也需改进,方法是加入后一个元素的哈希值,并删去子串第一个元素的哈希值,注意是加入和删去的方法,不是单纯的加上和减去,而是用类似进制的思想,先乘再加,减去的数也要乘以相应的“权重”(这步是关键),乘的是事先定义好的“种子”,权重是种子的m次方(m是字符串p的规模),这种方式被形象地称为“滚动哈希”。代码如下:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
void match(char *p,char *s);
void match(char *p,char *s,int n,int s_len);
ll hash(char *s);
const int seed = 31;
const int eps = 1e7+10;
char s[1005];
char p[1005];
ll res[1005];
int main() {
gets(s);
gets(p);
int p_len = strlen(p);
int s_len = strlen(s);
ll p_hash = hash(p);
match(p,s,p_len,s_len);
int n = s_len-p_len+1;
for(int i=0;i<n;i++)
if(res[i]==p_hash) printf("%lld\n",i);
return 0;
}
void match(char *p,char *s,int n,int s_len) {
char t[n];
int i;
for(i=0;i<n;i++)
t[i] = s[i];
t[i] = '\0';
res[0] = hash(t);//先算出第一个与字符串p规模相同的子串
res[0] %= eps;
for(int i=n;i<s_len;i++) {
res[i-n+1] = res[i-n]*seed + (int)s[i] - (int)s[i-n]*pow(seed,n);//用加入后一个元素的哈希值,删去子串第一个元素的哈希值得方式,构造哈希数组
res[i-n+1] %= eps;
}
}
ll hash(char *s) {
ll s_hash = 0;
int n = strlen(s);
for(int i=0;i<n;i++)
s_hash = s_hash*seed + (int)s[i];
return s_hash;
}
时间复杂度为O(n),该算法到此结束。
接下来是我在调试过程中遇到的一个问题,在此记录一下。
在优化代码中,我将哈希数组res定义为区局变量,但在写的过程中,我先是把它定义为main函数的局部变量,然后将match函数的返回值定义为ll*类型,试图返回res数组,但是res数组在match函数中记录下的数字,在match函数结束时空间就就释放了,结果自然是错误的。(其实很早的时候就遇到过同样的错误,如今又踩了一次坑...,因为想当然的认为数组就是对地址上存储的东西直接操作),解决办法有三种,第一种就是将res定义为全局变量(竞赛常用,因为可以指定长度)。
第二种是动态分配内存,代码如下:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
void match(char *p,char *s);
void match(char *p,char *s,int n,int s_len);
ll hash(char *s);
const int seed = 31;
const int eps = 1e7+10;
char s[1005];
char p[1005];
ll res[1005];
int main() {
gets(s);
gets(p);
int p_len = strlen(p);
int s_len = strlen(s);
ll p_hash = hash(p);
match(p,s,p_len,s_len);
int n = s_len-p_len+1;
for(int i=0;i<n;i++)
if(res[i]==p_hash) printf("%lld\n",i);
return 0;
}
void match(char *p,char *s,int n,int s_len) {
char t[n];
int i;
for(i=0;i<n;i++)
t[i] = s[i];
t[i] = '\0';
res[0] = hash(t);//先算出第一个与字符串p规模相同的子串
res[0] %= eps;
for(int i=n;i<s_len;i++) {
res[i-n+1] = res[i-n]*seed + (int)s[i] - (int)s[i-n]*pow(seed,n);//用加入后一个元素的哈希值,删去子串第一个元素的哈希值得方式,构造哈希数组
res[i-n+1] %= eps;
}
}
ll hash(char *s) {
ll s_hash = 0;
int n = strlen(s);
for(int i=0;i<n;i++)
s_hash = s_hash*seed + (int)s[i];
return s_hash;
}
这种方法的好处是开辟的内存在堆区(局部变量在栈区),生命周期自由,但一定要记得释放内存。第三种方法是将res数组作为match函数的一个参数,代码如下:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
void match(char *p,char *s);
void match(char *p,char *s,int n,int s_len,ll* res);
ll hash(char *s);
const int seed = 31;
const int eps = 1e7+10;
char s[1005];
char p[1005];
int main() {
gets(s);
gets(p);
int p_len = strlen(p);
int s_len = strlen(s);
ll p_hash = hash(p);
int n = s_len-p_len+1;
ll res[n] = {0};
match(p,s,p_len,s_len,res);
for(int i=0;i<n;i++)
if(res[i]==p_hash) printf("%d\n",i);
return 0;
}
void match(char *p,char *s,int n,int s_len,ll* res) {
char t[n];
int i;
for(i=0;i<n;i++)
t[i] = s[i];
t[i] = '\0';
res[0] = hash(t);
res[0] %= eps;
for(int i=n;i<s_len;i++) {
res[i-n+1] = res[i-n]*seed + (int)s[i] - (int)s[i-n]*pow(seed,n);
res[i-n+1] %= eps;
}
}
ll hash(char *s) {
ll s_hash = 0;
int n = strlen(s);
for(int i=0;i<n;i++)
s_hash = s_hash*seed + (int)s[i];
return s_hash;
}
以上~