数据结构(四):串的定义及基本操作 | 朴素模式匹配算法 | KMP算法 | next数组的手算方法

文章目录

  • 第四章 串
  • 一、串的定义
  • 二、串的基本操作
  • 三、串的存储结构
    • (一)串的顺序存储
    • (二)串的链式存储
    • (三)基本操作的实现
        • 1.求子串SubString
        • 2.比较操作StrCompare
        • 3.定位操作Index
  • 四、模式匹配算法
    • (一)朴素模式匹配算法
    • (二)KMP算法
        • ※ 如何用代码实现
        • ※ 求next数组(手算)
          • 例1."google"
          • 例2."ababaa"
          • 例3."aaaab"
    • (三)总结

第四章 串

所谓串,就是字符串。

一、串的定义

串,即字符串(String),是由零个或多个字符组成的有限序列。一般记为
S   =   ′   a 1 a 2 … … a n   ′ ( n ≥ 0 ) S\ =\ '\ a_1a_2……a_n\ '\quad(n≥0) S =  a1a2an (n0)
其中,

  • S是串名
  • 单引号括起来的字符序列是串的值;
    • 注:在不同的编程语言里,字符串也可以是用双引号括起来的字符序列(如Java、C),单引号括起来也可以(如Python)。
    • 即,单引号/双引号括起来的都可以,但是千万不要认为引号是字符串的内容,它只是字符串的边界符,表示字符串的头尾。
  • ai可以是字母、数字或其他字符;
  • 串中字符的个数n称为串的长度。n=0时的串称为空串(用∅表示)。

例如:

S = "HelloWorld!"

T = 'iPhone 11 Pro Max?'

一些术语

  • 子串:串中任意个连续的字符组成的子序列。
    • 如:‘iPhone’,'Pro M’是串T的子串。
    • 当然了,既然是任意个,当然也可以是0个。即一个空串也是一个字符串的子串。
  • 主串:包含子串的串。(也就是上面的S、T)
    • 如:T是子串'iPhone'的主串。
  • 字符在主串中的位置:字符在串中的序号。
    • 注意:字符串一般记为S = 'a1a2......an',即字符在串中的序号是从1开始的。
    • 此外,若没有特殊说明的话,我们说某个字符在主串中的位置,一般指的是其第一次出现的位置。
    • 还需要注意,空格也算一个字符。
    • 如:'1'在T中的位置是8(第一次出现的位置)。
  • 子串在主串中的位置:子串的第一个字符在主串中的位置。
    • 如:'11 Pro'在T中的位置是8。
  • 注意:空串和空格串是不同的。
    • 如:M = '',M是空串;N = ' ',N是由三个空格字符组成的空格串。

事实上,串是一种特殊的线性表,数据元素之间呈现线性关系。

它们的区别是,

  • 普通的线性表,每个数据元素可以是各种各样的数据类型,没有限制。

    而串,或者说字符串,它的数据元素,一般来说就是字符。(如中文字符、英文字符、数字字符、标点字符等)。

  • 普通的线性表,我们在进行增删改查等基本操作时,一般是对线性表中的某一个数据元素进行操作。

    而我们对串的基本操作,如增删改查等,通常以子串为操作对象。也就是一次是对一堆字符进行操作的。

二、串的基本操作

假设有串T = ""S = "iPhone 11 Pro Max?"W = "Pro"

基本操作:

  • StrAssign(&T, chars):赋值操作。把串T赋值为chars。

  • StrCopy(&T, S):复制操作。由串S复制得到串T。

  • StrEmpty(S):判空操作。若S为空串,则返回true,否则返回false。

  • StrLength(S):求串长。返回串S的元素个数。

  • ClearString(&S):清空操作。将S清为空串。

  • DestroyString(&S):销毁串。将串S销毁(回收存储空间)。

    • 注意,清空和销毁操作是不同的。
  • Concat(&T, S1, S2):串联接。用T返回由S1和S2联接而成的新串。

    • 例,执行Concat(&T, S, W)后,T = "iPhone 11 Pro Max?Pro"
    • 不难想到,串联接操作,会使一个串所占用的空间增加。因此,如果你的应用场景要经常使用串的联接操作的话,那当你在设计串的存储结构的时候,应该选取一种容易扩展的存储结构。
  • SubString(&Sub, S, pos, len):求子串。用Sub返回串S的第pos个字符起长度为len的子串。

    • 例,执行SubString(&T, S, 4, 6)后,T = "one 11"
  • Index(S, T):定位操作。若主串S中存在与串T值相同的子串,则返回它在主串S中第一次出现的位置;否则返回0。

    • 实际上就是寻找子串T主串S中的位置。
    • 例,执行Index(S, W)后,返回值为11。
  • StrCompare(S, T):比较操作。若S>T,则返回值>0;若S=T,则返回值=0;若S<T,则返回值<0。

    • 问题:这些是字符,又不是数字,怎么能比较大小?

      其实就类似于英语单词的升序排列一样。

例如:"abandon" < "aboard""academic" > "abuse"

即,从第一个字符开始往后依次对比,先出现更大字符的串就更大

  • 但是,又遇到了一种问题,如下。

    上面的意思,很好理解,但是当遇到如下情况时,该怎么处理呢?

例如:

"abstract" < "abstraction",它们前面部分是一样的,而后者多了一个后缀;

"abstract" < "abstract ",它们单词部分是一样的,而后者多了一个空格。

像这种情况,我们认为,长串的前缀与短串相同时,长串更大

  • 只有两个串完全相同时,才相等。

    如:"academic" == "academic"

  • 以上三点,是我们由英文字母的前后顺序来理解的。实际上,在计算机中,对于英文字母,或者一些字符,它是由一个二进制数来存储的。任何数据存到计算机中一定是二进制数。一个字符与一个二进制数有一个对应规则,这就是“编码”。(ASCII字符集编码)

    例如:

'a’对应的二进制编码为00010110,也就是十进制的97。

'c’对应的二进制编码为00110110,也就是十进制的99。

因此,计算机在比较’a’和’c’的大小时,并不是查英文字母表,而是直接对比其在计算机中存储的二进制数谁更大。显然是’c’更大。

同样的,空格也有它对应的字符集编码,是00000010,也就是十进制的32。

> 什么是字符集?

英文字符——ASCII字符集

中英文——Unicode字符集

为什么不是同一个字符集呢,因为ASCII字符集,是八位二进制数,其不足以表示所有中文需要的字符。

基于同一个字符集,可以有多种编码方案,如:UTF-8,UTF-16。

什么意思?

比如对于“王”这个汉字,我设计一个字符编码10100011来表示“王”。

但是另一个人,他可能用的是01010011来表示“王“。

这就是同一个字符集,有不同的编码方案。

字符集,就是我这一个语言体系,共需要用到哪些字符。

编码方案,就是将这些字符依次分别用什么二进制数表示。

实际上,采用不同编码方式,每个字符所占空间不同。例如ASCII编码中,每个字符占用1B,而UTF-8中,如一个汉字,它可能要占3B。但是在考研当中,一般用到的就是ASCII码,我们只需默认每个字符占1B即可。

乱码问题:

是由于软件解码方式不同导致的。

如,在原本的文件中,“王”的编码为11001100

而某软件在打开此文件时采用的是另一套编码规则,那么它所认为的11001100这个编码对应的字符,就是一个其他的字符了。

整体下来,你的文件显示的内容就都是乱码了。

三、串的存储结构

我们说过,串实际上就是一种特殊的线性表。

那么我们可以参考对线性表的实现方法,来实现串。

只不过线性表中存放的都是某一种类型(ElemType)的数据元素,但是串里面我们只能存放char型数据元素。

(一)串的顺序存储

#define MAXLEN 255	//预定义最大串长为255

//静态数组实现串(定长顺序存储) 
typedef struct {
	char ch[MAXLEN];	//每个分量存储一个字符
	int length;	//串的实际长度 
}SString; 

静态数组的缺点就是它的缺点:长度不可变。

静态数组实现串,因此它也叫串的定长顺序存储

如果我们想让它长度是可变的,我们可以用动态数组实现(堆分配存储),如下所示。

//动态数组实现(堆分配存储) 
typedef struct {
	char *ch;	//按串长分配存储区,ch指向串的基地址 
	int length;	//串的长度 
}HString;

void test(){
	HString S;
	S.ch = (char *)malloc(MAXLEN * sizeof(char));	//用完需要手动free 
	S.length = 0;
	//...
}

malloc方式申请的存储空间,在内存中是在堆区当中的,因此这种方法实现的,叫堆分配存储。同时,堆区中分配的内存空间需要手动的free释放。

这两种方式的优缺点,和顺序表的不同实现方式的优缺点是一样的。

无论是用哪种方式申请空间,在进行存储时,有如下几种不同的方案:

  • 方案一:从ch[0]开始存储串的内容,再另外设一个变量length存放串的长度。
  • 方案二:ch[0]充当length,从ch[1]开始存储串的内容。
    • 方案二的优点是,字符的位序和数组下标相同。(当然,单对于这一点来讲,就有点无关紧要了,并不是一个多么大的优点)
    • 方案二的缺点:需要注意的是,由于此处length是用ch[0]来充当的,因此其类型必然为char,那么它能表示的数字范围只有0~255
  • 方案三:不会明确的存储length为多少,而是以字符'\0'表示结尾(对应ASCII码的0)。
    • 可想而知,这个方案有一个缺点,当我们想知道它有多长的时候,我们得从头到尾扫描,看看什么时候遇到'\0',停止扫描并记录其长度。所以当我们经常需要访问串的长度的话,方案三就是不太可取的。

由方案一和方案二,我们可以想到一个两者兼备的方案,如下方案四所示。

  • 方案四:将ch[0]弃置不用,从ch[1]开始存储串的内容,再另外设一个变量length存放串长。
    • 这种方案兼具方案一、二的优点,因此我们在后续的讲解中,也默认使用这种方案。

(二)串的链式存储

和线性表的链式存储的一样的,只不过我们每个结点保存的数据的类型为char

typedef struct StringNode {
	char ch;	//每个结点存1个字符
	struct StringNode *next; 
}StringNode, *String;

需要注意的是,一个char字符,只占用1B,而(在32位操作系统中)一个指针是4B。

这就意味着,用1B的空间存储实际想要的信息,用4B的空间来存储一个辅助信息。

这种情况,我们把它称为存储密度低。即实际存储的信息比例很小。

那么怎么解决这一问题呢?

我们可以让链表的每个结点存放的实际信息域为多个字符。

typedef struct StringNode {
	char ch[4];	//每个结点存多个字符
	struct StringNode *next; 
}StringNode, *String;

此处写的是4个,实际也可以更多。

那么这样一来,每个结点中,实际存放的信息所占大小就是4B,因此存储密度就会提高。

因此,若使用链式存储来实现串的话,一般推荐采用这种方式。

通过这种方式实现,若最后一个结点存不满字符,那么你可以用一些特殊字符(如#,也可以用我们上面提到的'\0')将其填充进去。

(三)基本操作的实现

1.求子串SubString

SubString(&Sub, S, pos, len):求子串。用Sub返回串S的第pos个字符起,长度为len的子串。

#define MAXLEN 255	//预定义最大串长为255 

typedef struct {
	char ch[MAXLEN];	//每个分量存储一个字符
	int length;		//串的实际长度 
}SString;

//求子串
bool SubString(SString &Sub, SString S, int pos, int len) {
	//子串范围越界
	if(pos+len-1 > S.length) return false;
	for(int i = pos; i < pos+len; i++) {
		Sub.ch[i-pos+1] = S.ch[i];
	}
	Sub.length = len;
	return true;
}
2.比较操作StrCompare

StrCompare(S, T):比较操作。若S>T,则返回值>0;若S=T,则返回值=0;若S<T,则返回值<0。

//比较操作
int StrCompare(SString S, SString T) {
	for(int i=1; i<=S.length && i<=T.length; i++){
		if(S.ch[i] != T.ch[i]){
			return S.ch[i] - T.ch[i];
		}
	}
	//扫描过的所有字符都相同,则长度长的串更大
	return S.length - T.length; 
}
3.定位操作Index

Index(S, T):定位操作。若主串S中存在与串T值相同的子串,则返回它在主串S中第一次出现的位置;否则函数值为0。

其实在此处,我们可以通过使用之前实现的求子串操作SubString(&Sub, S, pos, len)来帮助我们完成,将要检查的子串T,和主串S的所有子串依次对比即可,而且,比较两个串是否相等,也可以使用我们之前已经实现的比较操作(StrCompare(S, T))来完成。

int Index(SString S, SString T) {
	int i = 1;
	int n = StrLength(S);
	int m = StrLength(T);
	SString sub;	//用于暂存子串
	while(i <= n-m+1) {
		SubString(sub, S, i, m);
		if(StrCompare(sub, T) != 0) i++;
		else return i;	//返回子串在主串中的位置 
	}
	return 0;	//S中不存在于T相等的子串 
}

四、模式匹配算法

实际上,在408考研当中,我们需要掌握两种字符串的模式匹配算法

  • 朴素模式匹配算法
  • KMP算法(较高级)

什么叫字符串的模式匹配?

类似于Word中的查找功能、百度的搜索功能,你输入一段文字,之后会匹配出相应结果。

说白了就是根据你输入的字符串,去匹配相应内容。

统一一些术语

  • 从哪个字符串里面进行搜索,那个字符串就叫主串
  • 你输入的内容,叫模式串
    • 为什么叫模式串,不叫子串?因为子串必定是能够在主串中找到的一个串。而模式串只是我们试图去搜索的一个串,并不一定能够找到,因此叫模式串而不能叫子串。
  • 字符串模式匹配:在主串中找到与模式串相同的子串,并返回其所在位置。

(一)朴素模式匹配算法

其核心思想就是:暴力求解。

在主串当中找出所有有可能与模式串相匹配的子串,然后将每个子串与模式串一一进行对比。这样肯定就没有遗漏地进行一遍对比。

因此,如果主串长度为n,模式串长度为m,则

朴素模式匹配算法:将主串中所有长度为m的子串依次与模式串对比,直到找到一个完全匹配的,或直到所有的子串都不匹配为止。

问题:在长度为n的主串当中,长度为m的子串共有多少个?

答:共有n-m+1个。

到这里,事实上我们已经发现了,这一系列操作,是和之前我们学过的串的定位操作Index(S, T)是一致的,只是换了个马甲。

因此,我们所谓的朴素模式匹配算法,就可以使用之前的串定位操作来进行实现,如下。

//和上面写过的那个是一模一样的内容
int Index(SString S, SString T){
	int i=1;
	int n = StrLength(S);
	int m = StrLength(T);
	SString sub;	//用于暂存子串
	while(i <= n-m+1) {
		SubString(sub, S, i, m);
		if(StrCompare(sub, T) != 0) i++;
		else return i;	//返回子串在主串中的位置 
	}
	return 0;	//没有匹配到 
}

接下来,我们不借助字符串的基本操作,而是直接通过数组下标来实现朴素模式匹配算法

设置两个扫描指针,i和j。

  • 令i指向主串S的第一个字符,j指向模式串T的第一个字符,依次进行匹配(若其指向字符相等,则后移;若其指向的字符不相等,则匹配失败)

  • 若当前子串匹配失败,则主串指针i指向下一个子串的第一个位置,模式串指针回到模式串j的第一个位置。

    可以这样实现:

    当后移到某个位置处,发现匹配失败,则

    i = i-j+2;

    //由于j能够表示“此时匹配到了第几个”,因此i-j+1可以让i回到起始位置,再加1可以到下一位置,不难理解。

    j = 1;

    //并且令j置为1

  • j > T.length,则当前子串匹配成功,返回当前子串第一个字符的位置——i - T.length

int Index(SString S, SString T){
	int i = 1;
	int j = 1;
	while(i<=S.length && j<=T.length){
		if(S.ch[i] == T.ch[j]){
			i++;
			j++;	//游标后移 
		} else{
			i = i-j+2;
			j = 1;	//指针归位重新匹配下一个子串 
		}
	}
	if(j > T.length) return i - T.length;
	else return 0;
}

分析一下此算法的时间复杂度。

设主串长度为n,模式串长度为m,则

  • 最坏时间复杂度 = O(nm)

    • 例如:主串为aaaaaaaaaaaaaaaaaaaaab;模式串为aaaaab。每次都要对比m个字符,且总遍历次数为n-m+1次,时间复杂度为O((n-m+1)m)

    • 即O(nm-m²+m),那么它为什么能等价于O(nm)呢?

      这是因为,在模式匹配的场景当中,n是主串的长度,m是模式串的长度。n一般远远大于m。

因此,nm的数量级,要比m²的数量级大得多,更比m的数量级大得多。

因此可以认为其时间复杂度为O(nm)。

  • 最好时间复杂度 = O(n)
    • 例如:主串为aaaaaaaaaaaaaaab;模式串为caaaab。即每次对比时,在第一个字符处就匹配失败,总遍历次数为n-m+1次。复杂度O(n-m+1) = O(n)。
    • 我个人认为,还有比O(n)更乐观的情况,即第一次匹配就匹配到了模式串,那么此种情况下,时间复杂度为O(m)。(只是个人看法,且由于时间复杂度默认是按最坏时间复杂度,所以此处无所谓吧)

(二)KMP算法

由D.E.Knuth,J.H.Morris和V.R.Pratt提出,因此称为KMP算法。

它的名称是由人名得来的,而它本质上就是基于朴素模式匹配算法,进行了一个优化的算法而已。

回顾一下朴素模式匹配算法。

在每轮循环当中,一旦发现当前这个子串中某个字符不匹配,就只能转而匹配下一个子串,而且是要从头开始。

这是为什么呢。因为我们在进行匹配之前,并不知道主串里面有什么,我们只能根据下标、模式串串长,进行一个一个的对比。

即使是上一轮中对比过的相等的字符,我们也并没有记录。总之每次循环前,主串中有什么内容,都是我们不知道的,我们只知道开始匹配的下标位置,与要匹配的长度

但是实际上,由于上一轮循环,对于字符的逐个对比,到某个字符匹配失败时结束,这一过程当中,我们必然能够通过一部分“i指向的字符与j指向的字符相等”,来得知主串当中有哪些字符。即,在遇到不匹配的字符之前的字符,一定是和模式串一致的字符。

因此,对于主串中的信息,虽然刚开始我们一无所知。但是通过模式串的部分匹配,我们可以确定主串里面前边一小部分到底是什么内容。内容就是模式串失配位置前的所有字符。

那么,根据模式串失配位置前的内容,映射到主串相应位置,之后去执行朴素模式匹配的话,它会寻找到某个位置之后再进行真正的匹配。

而且从逻辑上来讲,这一操作过程与主串是什么无关,只是与模式串本身的信息内容、失配位置相关。

例如,对于模式串abaabc来说,若第6个元素为失配字符,则主串对应已知内容为abaab,对于已知的这五个字符,模式串要去进行匹配的话。其最终情况为,模式串的前两个字符ab,与主串已知部分的后两个字符ab对照上,同时,由于前两个字符已形成对照,所以游标从这两个字符后的第三个字符开始即可。因此,主串中的游标i不动,模式串中的游标j也不必从模式串的头部开始,而是从第三位开始,就可以了。

第6个元素失配,可以得知下一轮循环只需执行:i不动,令j=3即可。其他位置元素失配,也可以用相同逻辑分析。

那么,对于模式串T = 'abaabc',我们分析得到以下结论:

  • 若第6个元素失配,则令j=3,i不动。
  • 若第5个元素失配,则令j=2,i不动。
  • 若第4个元素失配,则令j=2,i不动。
  • 若第3个元素失配,则令j=1,i不动。
  • 若第2个元素失配,则令j=1,i不动。
  • 若第1个元素失配,则匹配下一个子串即可。可以令j=0,然后执行i++,j++。

可以看到,从逻辑上分析,确实效率快了一些。

因为,第一轮循环,i=1,j=1之后,如果第6个元素失配,在朴素模式匹配算法中,i必须回溯到i=2处。

而利用这个算法,i不用动,且j从某位置开始,显然可以节省大量操作。

总之,指针i从头到尾均不需要进行回溯。

但是从代码的角度该怎么实现我们的这种想法呢?

根据以上分析,可知,不论是第几个元素失配,首先i的值是不需要动的。但是问题是j应该从何处开始。

由于实际场景当中,主串往往是很长的,而模式串一般是较短的。所以我们可以先按照上述方法分析模式串的这些信息,分析完成之后,再将其进入主串去匹配,这样就会非常高效。

※ 如何用代码实现

首先,我们刚才分析模式串得到的这一系列结论,显然可以用数组来很方便的对应表示。

我们把这个数组称为next数组

对于模式串T = 'abaabc'

  • 若第6个元素失配,则令j=3,i不动。
  • 若第5个元素失配,则令j=2,i不动。
  • 若第4个元素失配,则令j=2,i不动。
  • 若第3个元素失配,则令j=1,i不动。
  • 若第2个元素失配,则令j=1,i不动。
  • 若第1个元素失配,令j=0,然后执行i++,j++。(为了让代码更整齐)

next数组为:

next[0] = NULL;
next[1] = 0;
next[2] = 1;
next[3] = 1;
next[4] = 2;
next[5] = 2;
next[6] = 3;

next[i]的下标表示第i个元素失配时,其对应的值为j应该改为几之后继续后移。

代码逻辑实现如下:

if(S[i] != T[j]){
	j = next[j];
}

if(j == 0){
	i++;
	j++;
}

此处再次说明一下,next数组存放什么数据,只是由模式串本身携带的信息内容的特质所决定的,和主串毫无关联。

即,若模式串已知为T = 'abaabc',则无需关注主串的任何角度的信息,只需分析模式串,next数组就已经编写完毕了。

即,KMP算法的主要步骤是:

  • 根据模式串T,求出next数组。(进行匹配前要进行的一个预处理)
  • 利用next数组进行匹配。(主串指针i不回溯)

此处,我们暂时不关心next数组怎样用代码来求出。我们暂时通过手算的方式人工将其求出。

我们先来关注,得到next数组之后,利用next数组进行匹配的代码该如何实现。如下所示:

int Index_KMP(SString S, SString T, int next[]) {
	int i = 1;
	int j = 1;
	while(i<=S.length && j<=T.length){
		if(j==0 || S.ch[i] == T.ch[j]]){
			i++;
			j++;	//继续比较后继字符 
		} else {
			j = next[j];	//模式串j按照next数组进行移动 
		}
	}
	if(j > T.length) return i - T.length;	//匹配成功
	else return 0; 
}

之前我们分析过,朴素模式匹配算法的最坏时间复杂度为O(mn)。

而KMP算法,最坏时间复杂度为O(m + n),其中包含:求next数组时间复杂度O(m)、模式匹配过程最坏时间复杂度O(n)。(因为主串指针i是不回溯的,最坏遍历n次)。

在408考研当中,只需要学会如何手动地求next数组就可以。

※ 求next数组(手算)
例1.“google”

分析:

首先不要弄错了,next数组下标和字符串下标是一一对应的。(字符串是弃置了0号位,next数组也弃置了0号位)。首先这一点不要弄错。

所以字符串下标是16,next数组下标为next[1]next[6]。

  • 首先分析next[1]

    next[1]的含义是,当模式串的第一个字符发生失配时,模式串指针j应该指向什么位置?

    应该是让j=0,然后执行i++,j++。

    因此,对于任何一个模式串都是一样的,第一个字符不匹配时,只能匹配下一个子串。因此,next[1]均直接写0即可。

  • 分析next[2]

    next[2]的含义是,如果第二个字符发生失配,接下来指针j应该指向哪?

    对于这个例子来说,我们应该让j=2。

    事实上,对于任何一个模式串都一样,第2个字符不匹配时,应该常识匹配模式串的第1个字符。因此,next[2]均直接写1即可。

  • 分析next[3]

    在失配位置前面,画一个分界线。然后,让模式串一步一步往后退,直到在分界线之前“能对上”,或模式串完全跨过分界线。

    如下图所示,

在这里插入图片描述

​ 可见,这种情况,应该将模式串移动到

在这里插入图片描述

​ 即此模式串对应的next[3] = 1

  • 分析next[4]

    同理,可知,next[4] = 1

  • 分析next[5]

在这里插入图片描述

同理可知,next[5] = 2

  • 分析next[6]

    同理可知,next[6] = 1

例2.“ababaa”
  • 第一步:
    • next[1]直接写0
    • next[2]直接写1
  • 第二步:
    • 依次分析next[3]至next[6]
    • 分析结果如下:
      • next[3] = 1
      • next[4] = 2
      • next[5] = 3
      • next[6] = 4
例3.“aaaab”

分析结果如下:

  • next[1] = 0
  • next[2] = 1
  • next[3] = 2
  • next[4] = 3
  • next[5] = 4

(三)总结

手算求next数组步骤:

  • next[1]都写0,next[2]都写1
  • 之后的next:在不匹配的位置前,划一根分界线。将模式串一步一步往后退,直到分界线“能对上”,或模式串完全跨过分界线为止。此时j指向哪,next数组值就是多少。

KMP算法的最坏时间复杂度为O(m + n)。其中m是求next数组造成的时间复杂度,n是利用next数组去匹配的时间复杂度。

而朴素模式匹配算法最坏时间复杂度为O(mn)。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

秋秋秋叶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值