字符串匹配之Pabinkarp

题目:

判断字符串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;
}

以上~

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值