串与KMP算法

一、存储结构

字符串在计算机一般有三种表示方式:

  1. 定长顺序存储:将串定义为字符数组,串的存储空间在编译时确定,其大小不能改变。
  2. 堆分配存储:仍用一组地址连续的存储单元依次存储串中的字符序列,但串的存储空间是在程序运行时动态分配的,其使用的是程序的堆内存空间。
  3. 块链存储:使用链式存储结构。

Ⅰ.定长顺序存储
#define MAX_STRLEN 255
char str[MAX_STRLEN+1];
  • 使用数组形式存储字符序列,数组以'\0'作为串结束标志,故声明数组长度时需用MAX_STRLEN+1
  • 字符串长度可以是小于MAX_STRLEN的任何值,若超出长度限制,多余部分会被截断。

Ⅱ.堆分配存储
class HString{
	int lenght;
	char *ch;
};
  • 需使用newdelete动态分配与回收存储空间,由newdelete动态分配与回收的存储空间称为堆。
  • 堆分配存储结构的串兼具顺序存储的特点,同时对串长没有限制,更显灵活。

Ⅲ.块链存储
#define CHUNKSIZE 80
struct Chunk{			//块结构
	char ch[CHUNKSIZE];
	Chunk *next;
};
class LString{
	Chunk *head,*tail;	//头尾指针
	int curlen;			//当前长度
};

二、匹配算法

以下算法实现具基于顺序存储。

Ⅰ.蛮力算法
  1. 从主串指定位置开始,将主串与模式串的第一个字符比较。
  2. 若相等,继续逐个比较后续字符。
  3. 若不等,从主串的下一个字符起重新与模式串第一个字符比较。
#include<string.h>

int match(char *P,char *T){
	size_t n = strlen(T), i=0;	//主串长度、指定起始比对位置
	size_t m = strlen(P), j=0;	//模式串长度,指定起始比对位置
	while(i<n && j<m){
		if(T[i]==P[j])		//若匹配
			{ i++; j++; }		//继续比对下一字符
		else
			{ i-=j-1; j=0 }		//主串回到上次始配位置的下一位置,模式串回到第一个字符	
	}
}

string.h头文件中的strlen()函数可直接传入字符数值形式的串的指针,得到字符串的实际长度。

  • 在最好情况下,除比较成功的位置外,其余位置只需比较一次(模式第一个字符),其时间复杂度为 o ( m + n ) 。 o(m+n)。 o(m+n)
  • 在最坏情况下,需要迭代n-m+1轮,且每轮需比较m次,此时时间复杂度为 o ( n ∗ m ) o(n*m) o(nm)
  • 实际上,当字符表比较大时,此类算法的最好(或接近最好)情况出现的概率并不很低。

Ⅱ.KMP算法

 蛮力算法的低效来自于大量的局部匹配:每一轮的m次比对中,仅最后一次可能适配,而一旦发现失配,蛮力算法会使两个指针完全回退。而KMP算法的思想是:利用以往成功的比对所提供的信息,不仅可以避免文本串字符指针的回退,还可使模式串尽可能最大跨度地右移

KMP算法的特点

 当出现失配时( T [ i ] ≠ P [ j ] T[i]≠P[j] T[i]=P[j],其中P为模式串,T为文本串):

  1. 不需回溯指针 i i i
  2. 利用已经得到的“部分匹配”的结果
  3. 将模式上的指针尽可能短的回退一段距离后,继续进行比较

  • 当出现失配时,经过前面的比对,已经确定的匹配范围为: P [ 0 , j ] = T [ i − j , i ] P[0,j] = T[i-j,i] P[0,j]=T[ij,i]

  • 若要使模式串的索引适当右移后,能够与T的某一个真后缀子串匹配,则需满足: P [ 0 , t ) = T [ i − t , i ) = P [ j − t , j ) P[0,t)=T[i-t,i)=P[j-t,j) P[0,t)=T[it,i)=P[jt,j)

  • 亦即,在在已匹配子模式串 P [ 0 , j ] P[0,j] P[0,j]中长度为 t t t的真前缀,应与其长度为 t t t的真后缀完全匹配,故 t t t必来自集合: N = { 0 ≤ t ≤ p   ∣   P [ 0 , t ] = P [   j − t , j ) } N=\{0≤t≤p \ |\ P[0,t]=P[\ j-t,j)\} N={0tp  P[0,t]=P[ jt,j)}。一般地,该集合可能包含多个这样的 t t t,但需注意的是, t t t值仅取决于模式串P与比对时的首个失配位置 P [ j ] P[j] P[j],而与文本串无关。

  • 此时,若下一轮的比对将从 T [ i ] T[i] T[i] P [ t ] P[t] P[t]的比对开始,这等效于将 P P P右移 j − t j-t jt个单元,位移量与 t t t成反比。因此,为使 T T T的上一轮失配位置(索引 i i i)绝不倒退,同时又最大程度利用已匹配结果,应在集合 N N N中挑选最大的 t t t。也就是说,应选择其中移动距离最短者。

  • 于是若令 n e x t [ j ] = m a x ( N ) next[j]=max(N) next[j]=max(N),则一旦发现 T [ i ] ≠ P [ j ] T[i]≠P[j] T[i]=P[j]失配,即可转而将 P [ n e x t [ j ] ] P[next[j]] P[next[j]] T [ i ] T[i] T[i]对齐,并从改位置进行下一轮的匹配。

  • 由于集合 N N N t t t的值)仅取决于模式串 P P P以及失配位置 j j j,而与文本串无关。于是对于给定的任意模式串中的元素,都有着其特有的、确定的 n e x t [ j ] next[j] next[j],不妨依据模式串预先计算出所有位置 j j j对应的 n e x t [ j ] next[j] next[j]值,以便后续查询。至此,可将已匹配部分的"记忆力"转换为具有普适性的"预知力"。

此时将串匹配算法的问题转换为求模式串的 n e x t next next数组,若模式串的 n e x t next next求得,则可进行高效的串匹配。

next[j]的求取
  1. n e x t [ 1 ] = 0 , n e x t [ 2 ] = 1 next[1]=0,next[2]=1 next[1]=0,next[2]=1
     若在模式串第一个位置处失配,则将文本串的指针右移,直到出现与模式串第一个字符相匹配的字符,用 n e x t = 0 next=0 next=0来标识这样的情况;而若在第二个字符失配,则将模式串指针退回至首个字符。

  2. n e x t [ j + 1 ] next[j+1] next[j+1]
     即若已知 n e x t [ 0 , j ] = t next[0,j]=t next[0,j]=t,要求取 n e x t [ j + 1 ] next[j+1] next[j+1]
    n e x t [ j ] = t next[j]=t next[j]=t,则意味着在 P [ 0 , j ] P[0,j] P[0,j]中,自匹配的真前缀与真后缀的最大长度为 t t t,此时 P [ j ] = P [ t ] P[j]=P[t] P[j]=P[t],而若 P [ j + 1 ] = P [ t + 1 ] P[j+1]=P[t+1] P[j+1]=P[t+1],则 n e x t [ j + 1 ] = t + 1 next[j+1]=t+1 next[j+1]=t+1,即自匹配的最大长度加一。

    ② 若 P [ j + 1 ] ≠ P [ t + 1 ] P[j+1]≠P[t+1] P[j+1]=P[t+1],则 n e x t [ j + 1 ] next[j+1] next[j+1]的候选者应该是: n e x t [ n e x t [ j ] ] + 1 , n e x t [ n e x t [ n e x t [ j ] ] ] + 1 , . . . next[next[j]]+1,next[next[next[j]]]+1,... next[next[j]]+1,next[next[next[j]]]+1,...,只需反复用 n e x t [ t ] next[t] next[t]替换 t t t t = n e x t [ t ] t=next[t] t=next[t]),直到 P [ j ] = P [ t ] P[j]=P[t] P[j]=P[t],即可按优先次序遍历以上候选者,从而选出最大的 n e x t next next值。

以上第②点的原理:
 由于在探寻 n e x t [   ] next[\ ] next[ ]数组的过程中,模式串本身即作文本串也作模式串,当在求取 n e x t next next若出现失配的情况( P [ j ] ≠ ] P [ t ] P[j]≠]P[t] P[j]=]P[t])时,根据KMP算法的原理,模式串 P [ 0 , t ] P[0,t] P[0,t]的指针 t t t也必然要根据已求得的 n e x t next next进行回溯,于是有 t = n e x t [ t ] t=next[t] t=next[t]

求取给定串的 next数组算法:

void GetKmpNext(char *pString,int *Next){
	//求取模式串的pString的next函数值并存入数组Next
	//其中Next数组的第0号元素放置串长度
	
	int j,t;
	Next[0] = len(pString);		//len()函数需自行实现
	j=2;
	Next[1]=0;
	t=Next[1];
	
	while(t<=Next[0]){
		if(t==0||pString[j]==pString[t-1]){
			Next[j]=t+1;
			j++;
			t=Next[j-1];
		}
		else{
			t=Next[t];
		}
	}
}

 next数组第一个元素的索引index及其存放的值val不能一样,当q=index时,表示与第一个元素匹配,自匹配的最大真前后缀为1;当q=val时,表示无相同前后缀(完全失配),主串指针后移。

 且一般val=index-1,以便在完全失配时,通过next[p]=q+1的方式,将该位的next值设为第一个元素的索引,即完全失配时跳到第一个元素。

 一般next[]数组有两种形式:
第一个next值从索引0开始,此时next[0]=-1
第一个next值从索引1开始,此时next[1]=0,而next[0]存放无意义的值(如模式串的长度)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值