ACM Weekly 5(待修改)

涉及的知识点

第五周练习主要涉及string类、Kmp、manacher、字符串hash

拓展:BM、Sunday、

string类

string是C++相对于C特有的类型,将字符串单独封装作为类使用,其基本的操作不再赘述。

以下内容参考《C++STL基础及应用

基本操作

插入

insert():第一个参数表明插入源串的位置,第二个参数表明要插入的字符串;
append():仅有一个输入参数,在源字符串尾部追加该字符串
size():无参数,返回字符串长度值,即多少个字符

替换

replace():三个输入参数,第一个用于指示从字符串什么位置开始改写,第二个用于指示从原字符串中删除多少个字符,第三个是替换字符串的值

查询

string::npos:string类的成员变量,一般与系统查询函数的返回值比较,若等于该值,表明没有符合查询条件的结果值

find:在一个字符串中查找指定的单个字符或字符组,找到返回首次匹配的开始位置,否则返回nops,一般为两参数,待查询字符串和起始位置(默认0)

find_first_of:在一个字符串中进行查找,返回第一个与指定字符串中任何字符匹配的字符位置,否则为npos,一般为两参数,待查询字符串和起始位置(默认0)

find_last_of:在一个字符串中进行查找,返回最后一个与指定字符串中任何字符匹配的字符位置,否则为npos,一般为两参数,待查询字符串和起始位置(默认0)

find_first_not_of:在一个字符串中进行查找,返回第一个与指定字符串中任何字符不匹配的字符位置,否则为npos,一般为两参数,待查询字符串和起始位置(默认0)

find_last_not_of:在一个字符串中进行查找,返回最后一个与指定字符串中任何字符不匹配的字符位置,否则为npos,一般为两参数,待查询字符串和起始位置(默认0)

rfind:对一个串从头到尾查找指定的单个字符或字符组,如果找到,返回首次匹配开始位置,否则返回npos,一般为两参数,待查询字符串和起始位置(默认串尾)

字符串输入输出流

头文件<sstream>

istringstream:字符串输入流,提供读string功能
ostringstream:字符串输出流,提供写string功能
stringstream:字符串输入输出流,提供读写string功能

此三类配合>>和<<使用,方向代表存入的对象

KMP

KMP原理

KMP算法应用于查找B串在A串中的出现位置,也可以通过修改来求得两个字符串的最大公共序列,初次理解总觉晦涩难懂,查找多方资料并细细品读后才得以理解,本文尽力将KMP算法论述清楚

参考了该篇博客,其详尽自愧不如

还有mooc上的网课数据结构-浙江大学

匹配机理

每次匹配,正在匹配的模式串的部分分为三个板块

该处对称的定义:最大的相同前缀与后缀
在这里插入图片描述
正常来讲是这样,但是为了简化操作,只取前三块
在这里插入图片描述

匹配如图(前提:匹配失败
在这里插入图片描述
首先是直观的理解,在KMP中,每次失配后,串移动的参照物为文本串,也就是每次都是移动模式串,即更改模式串的匹配位置,由图可知,每次失配后,为了减少再次匹配的次数,我们由对称进行移动,将左对称区间移动到右区间位置,中间的元素必定不能匹配,由上一张图可知,模式串中已匹配部分有三个板块,若按照暴力的思想右移一位,如图
在这里插入图片描述

可以看到,右移之后a与c匹配判断,b与d匹配判断,看下面的例子

A A C D A A F AACDAAF AACDAAF
A A C D A A AACDAA AACDAA

A A C D A A F AACDAAF AACDAAF
∗ A A C D A A *AACDAA AACDAA
如果需要对称部分匹配成功,则C=A,D=A,模式串变为AAAAAA,显然与设定的条件矛盾

如果没有对称区间,那么就直接右移一位
设两个区间左端相隔m位

易证,右移一位,则串各元素全相等,右移两位,则奇数相等,偶数相等,由不完全归纳可得右移m位,则以m为模的对应位数元素相等

那么,接下来的问题就是如何进行移动操作,以及求对称长度的大小

next/match数组的构建

如图

在这里插入图片描述
当s与p指向的元素相同时,两者都自增,当发生匹配失败时,p=match[p-1]+1,进行回溯,即将左对称移到右对称,或者说重新从左对称的最大下标的下一个开始匹配,这里的match的定义是

在这里插入图片描述
简单来说,match记录的是当模式串到下标j(是下标,也就是j+1个元素)时,满足下标0~j的字符串中有最大相等的前缀后缀的下标i

那么,在match已经构造好的前提下,代码如下

int KMP(char*text,char*pattern)
{
	int n = strlen(string);
	int m = strlen(pattern);
	int s=0, p=0;
	if ( n < m ) return -1;
	while ( s<n && p<m ) {
		if ( text[s]==pattern[p] ) 
		{
			s++; 
			p++;
		}
		else if(p>0)//p=0代表第一个元素不匹配 
			p = match[p-1]+1;
		else s++;//第一个元素不匹配,则进行text的下一位匹配
	}
	return ( p==m )? (s-m) : -1;//判断p是否到达结尾,是,则说明已经找到,否则说明匹配失败
}

现在来讨论一下match的构造

我们用递归的方式来思考,对j-1来言,与其相等的字符下标为match[j-1],当我们讨论j的值时,有两种情况

  1. pattern[j]的存在能使pattern的最大相同前缀与后缀值增加
    由图可知,绿色部分到match[j-1]与紫色部分到j-1两部分相等,如果pattern[j]与pattern[match[j-1]+1]相等,那么相同前缀后缀的长度便可以增加,增加的长度为1

在这里插入图片描述

  1. pattern[j]的存在不能使pattern的最大相同前缀与后缀值增加
    此时就要思考如何利用先前的结果了,此时已经知道pattern[match[j-1]+1]≠pattern[j],如图,但是由于对称,绿1部分等于紫2部分,之后便又回到了上一点的判断,即判断pattern[match[match[j-1]]]是否等于pattern[j],若不相等,则循环,如果到最后仍不相等,match[j]=-1

在这里插入图片描述

代码如下

void BuildMatch(char*pattern)
{
	memset(match,0,sizeof(match));//清空
	int i=0,len=strlen(pattern);//数值初始化
	match[0]=-1;//第一个为-1
	for(int j=1;j<=m;j++)//求每一个的match值
	{
		i=match[j-1];
		while(i>=0&&pattern[i]!=pattern[j])//递归过程,找到第一个前缀的后一个元素与pattern[j]相等
			i=match[i];//减小长度,有DP的意思
		if(i<0)
			match[j]=-1;//如果找不到
		else
			match[j]=match[j-1]+1;
	}
}

拓展KMP

Manacher

字符串哈希

字符串哈希可以简单理解为将字符串对应成整数来处理,用函数的思想(数学中的函数)将每个字符串对应到存储空间的各个位置上,即通过哈希函数尽量创造出一个一一对应的数组,方便进行后续操作

哈希方法

对于给定的字符串str,由字母组成,设置一个字母T对应映射 i d x ( T ) = T − ′ a ′ + 1 ( i d x ( T ) = ( i n t ) T ) idx(T)=T-'a'+1(idx(T)=(int)T) idx(T)=Ta+1(idx(T)=(int)T)

自然溢出

h a s h [ i ] = h a s h [ i − 1 ] × p + i d ( s t r [ i ] ) hash[i]=hash[i-1]×p+id(str[i]) hash[i]=hash[i1]×p+id(str[i])
hash[i]设置为unsighed long long,相当于默认对 2 64 − 1 2^{64}-1 2641取模

单哈希

h a s h [ i ] = h a s h [ i − 1 ] × p + i d ( s t r [ i ] ) % m o d hash[i]=hash[i-1]×p+id(str[i])\%mod hash[i]=hash[i1]×p+id(str[i])%mod
其中p、mod为质数,mod>p,当mod、p很大时,冲突概率低

双哈希

将一个字符用不同mod进行两次哈希,哈希结果为一个二元组
h a s h 1 [ i ] = h a s h [ i − 1 ] × p + i d ( s t r [ i ] ) % m o d 1 hash_1[i]=hash[i-1]×p+id(str[i])\%mod_1 hash1[i]=hash[i1]×p+id(str[i])%mod1
h a s h 2 [ i ] = h a s h [ i − 1 ] × p + i d ( s t r [ i ] ) % m o d 2 hash_2[i]=hash[i-1]×p+id(str[i])\%mod_2 hash2[i]=hash[i1]×p+id(str[i])%mod2
结果为 < h a s h 1 [ n ] , h a s h 2 [ n ] > <hash_1[n],hash_2[n]> <hash1[n],hash2[n]>
由于进行了两次哈希,并且用的mod不同,冲突概率极低

获取子串哈希

已知一个字符串str的 h a s h [ i ] , 1 ≤ i ≤ n hash[i],1≤i≤n hash[i],1in,其子串 s t r l … s t r r ( 1 ≤ l ≤ r ≤ n ) str_l\dots str_r(1≤l≤r≤n) strlstrr(1lrn)的哈希值为
h a s h [ r ] − h a s h [ l − 1 ] × p r − l + 1 hash[r]-hash[l-1]×p^{r-l+1} hash[r]hash[l1]×prl+1
又因为需要每次取模
( h a s h [ r ] − h a s h [ l − 1 ] × p r − l + 1 ) % m o d (hash[r]-hash[l-1]×p^{r-l+1})\%mod (hash[r]hash[l1]×prl+1)%mod
再加上考虑负数的情况

难题解析

拓展

参考文献

  1. 从头到尾彻底理解KMP
  2. 本文部分图与代码来自数据结构 浙江大学 中国大学MOOC(慕课)
  3. 《C++STL基础及应用》
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值