本章节主要掌握字符串的模式匹配,主要包括朴素、KMP、KMP再优化,nextarr、nextValarr的手算
目录
一、定义
即字符串
二、存储结构
1.顺序存储
(1)静态arr
typedef struct {
ELemType ch[MaxLen];
int length;
}SString;
(2)动态arr(堆存储)
typedef struct {
ELemType *ch;
int length;
}HString;
2.链式存储
(1)单链表实现
#define SIZE 20
typedef struct LSNode{
ELemType data[SIZE];//多存几个ele
struct LinkString *next;
}LSNode;
3.王道教材中设置
舍弃第一个元素,再使用length变量
优点:arr中ele的index==ele的pos
4.基本操作(使用王道教材中的模式展示)
(1)定义(在上述二中详细展示)
typedef char ELemType;
#define MaxLen 255
typedef struct {
ELemType ch[MaxLen];
int length;
}SString;
(2)初始化
void InitSString(SString &SS){
SS.length = 0;
}
(3)创建串
第一个pos的ele为null
bool CreateSString(SString &SS,ELemType* arr){
int len = strlen(arr);
for (int i = 0; i < len; ++i) {
SS.ch[i+1]=arr[i];
}
SS.length=len+1;//此处+1是因为第一个下标ele为空
return true;
}
attn:在使用函数时,需要传入char*类型的ele,不建议直接传入字符串常量,因为字符串常量在编译的时候会转为char*类型,IDE会waring
CreateSString(T,(ELemType *)"abaabc");、//recommend CreateSString(T,"abaabc");
(4)赋值串
//赋值 把串SS赋值为arr
bool StringAssign(SString SS, SString &chars) {
if (SS.length == 0) return false;
chars = SS;
return true;
}
(5)复制串
//复制 将ST每个单个字符复制给chars
bool StrCopy(SString ST, SString &chars) {
if (ST.length == 0) return false;
for (int i = 1; i < ST.length; ++i) {
chars.ch[i] = ST.ch[i];
}
chars.length = ST.length;
return true;
}
(6)求子串
//求子串 返回从pos位置开始长度为len的子串
bool SubString(SString &arr, SString SS, int pos, int len) {
if (pos + len > SS.length) return false;
for (int i = pos; i < pos + len; ++i) {
arr.ch[i - pos + 1] = SS.ch[pos];
}
arr.length = len;
return true;
}
(7)串的比较
application:英语单词书(顺序版)
//比较 S>T,返回1 相等0 小于-1
int StrCompare(SString SS, SString T) {
int i = 1;
while (i < SS.length && i < T.length) {//比较前i位
if (SS.ch[i] == T.ch[i]) i++;
else if (SS.ch[i] > T.ch[i]) return 1;
else if (SS.ch[i] < T.ch[i]) return -1;
}
//有一个到头了,表示前i位相同
if (SS.length == T.length) return 0;
else if (SS.length < T.length) return -1;
else return 1;
}
(8)求串长
//求ele不为空的长度
int StrLength(SString SS) {
return SS.length - 1;
}
(9)求子串在主串中的位置
int Index(SString S, SString T) {
int i = 1, 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-1) {
return i - j + 1;
} else return 0;//未找到
}
(10)将串清空
void ClearString(SString &S){
while(S.length!= 0){
S.ch[S.length--] = NULL;
}
}
(11)判空
bool StrEmpty(SString SS){
return SS.length == 0;
}
(12)拼接串
bool Concat(SString &T, SString S1, SString S2) {
if (MaxLen < S1.length + S2.length) return false;
for (int i = 1; i < S1.length; ++i) {//复制s1
T.ch[i] = S1.ch[i];
}
for (int i = 1; i < S2.length; ++i) {//复制s2
T.ch[i + S1.length] = S2.ch[i];
}
T.length = S1.length+S2.length;
return true;
}
但是我连接的两个之间有一个空元素,未解决,∵不是考点,so没有浪费时间
(13)打印
//打印
void print(SString SS){
for (int i = 1; i < SS.length; ++i) {
printf("%c",SS.ch[i]);
}
printf("\n");
}
三、字符串的模式匹配(重点,考点)
1.术语
(1)主串
已给的一长串字符串
(2)子串
从主串中截取的字符串
(3)模式串
用于在主串中寻找的子串
(4)前缀
不包括第一个元素,能将主串拆解的所有的子串
(5)后缀
不包括最后一个ele,能将主串拆解的所有的子串
(6)部分匹配值
前缀和后缀中所有相同ele的个数
2.算法
(1)朴素模式匹配算法
1)思路:暴力破解,挨个遍历。
i从主串第一个ele开始,j从匹配串第一个ele开始,依次比对,当失配后i从主串第二个ele开始,j从匹配串第一个ele开始
2)代码实现
//定位操作 找到主串S中子串T第一次出现的位置
int Index(SString S,SString T){
int i = 1 ,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-1){
return i - j + 1;
}else return 0;//未找到
}
3)Tn分析
设主串长度为n,匹配串长度为m
最多要进行n-m+1次遍历,而每次遍历匹配串的长度m
Tn = O((n-m+1)m) = O(nm-m^2-m)
因为 n>> m => Tn = O(nm)
(2)KMP算法
优化朴素
1)思路
在朴素中,失配的ele前面的说明是已经匹配上的,so我们拿匹配串失配ele之前的ele,依次与刚才匹配子串的第二个ele开始依次匹配,从而可以提高效率
2)手算
step1:先根据模式串进行nextarr的计算
step2:设置ij遍历主串和模式串,根据nextarr进行跳转
3)eg
模式串 a b a a b c
nextarr 0 1 1 2 2 3
4)代码实现
最后此处j>T.length - 1是因为我把空元素也算在了长度内。
if 不算空元素,最后判断是否找到的条件为j>T.length
int KMP_Index(SString SS,SString T,int next[]){
//SS是主串,T是模式串,next是nextarr
int i =1, j =1;
while(i<SS.length && j <T.length){
if(j==0||SS.ch[i] == T.ch[j] ){
i++,j++;
} else{
j = next[j];
}
}
if(j>T.length - 1) return i-j+1;
else return 0 ;
}
5)Tn分析
主串i不会往前走,j进行跳转,soT(n)max = O(n+m)
求nextarrTn=O(m) 最多移动匹配串长度,即m
模式匹配过程 Tn = O(n)
(3)KMP再优化
1)思路
假设模式串为abaabc,if第3个字符(a)失配,则需要跳到第1个字符(a)并在之后继续配比,但因为这个失配的字符是a,so跳到第一个字符a一定失配,so直接跳到第一个字符的nextarr即可,则新生成的arr称作nextval
结论:if失配的字符跳到的位置上的字符依然还是失配的字符,则直接让其nextval = 该位置上的字符的nextval
2)手算
模式串 a b a a b c
nextarr 0 1 1 2 2 3
nextval 0 1 0 2 1 3
3)代码实现
int KMP_better(SString SS, SString T, int nextval[]) {
//SS 主串 T 匹配串 nextval是nextvalarr
int i = 1, j = 1;
while (i < SS.length && j < T.length) {
if (j==0||SS.ch[i] == T.ch[j]) {
i++, j++;
} else {
j = nextval[j];
}
}
if (j > T.length-1) return i - j + 1;
else return 0;
}