目录
一、串
1.基础概念
串本质是字符串,是一种特殊形式的线性表,主要包括串名S、长度n和空串三个概念(空格串 != 空串)。
子串:主串中任意多个连续的字符组成的字符串,子串在串中的位置 = 子串首个字符在主串中的位置(串的基本操作通常以子串作为操作对象)。
字符在串中的位置:与位序相同,从1开始计算。
字符集:类似字符串的定义域,主要包括英文集ASCII码和中文集Unicode。
编码:函数映射规则f
y:对应的二进制数(编码结果)
解码:f-1,反解结果
乱码:软件对应的解码方式不对。
2.存储结构
1.定长顺序存储:使用定长数组。
王道课本中使用的是舍去首个数组空间,再另开辟一块空间存放数组长度的方法,这样的好处是可以让数组下标与字符在串中的位置一一对应(数组从1开始,位置也是从1开始),其次,另外开辟的空间容量由人为规定,可存放较大数据,即数组可以较长(如果在数组第一个空间存放的话,空间仅1B,8位,仅能记录长度小于255的数组)。
#define MaxLen 255
typedef struct{
char data[MaxLen];
int length;
}SString;
2.堆分配存储:使用动态数组,使用后需手动回收。
#define MaxLen 255
typedef struct
{
char *ch; //按串长分配存储区,ch指向串的基地址
int length;
}HString;
HString S;
S.ch = (char *)malloc(MaxLen * sizeof(char));
S.length = 0;
3.块链存储:链表存储
当一个节点仅存放一个元素时,元素占用1B空间,而指针将占用4B空间,导致存储密度较低,因此在使用块链存储时可在一个节点存放多个元素,提高存储密度。
#define MaxLen 255
typedef struct StringNode{
char ch[4]; //每个节点存放多个字符,提高存储密度
struct StringNode * next;
}StringNode, *String;
3.基本操作
StrAssign(&T, chars):赋值操作,将串T赋值为chars。
StrCopy(&T, S):复制操作,将串S复制给串T。
StrEmpty(S):判空操作。若S为空串,则返回true,反之返回false。
StrLength(S):求串长,返回S的元素个数。
ClearString(&S):清空操作,但仍保留串S申请的空间。
DestroyString(&S):销毁串,回收存储空间。
Concat(&T, S1, S2):串连接,将串S1、S2链接成为新的串T。
SubString(&Sub, S, pos, len):求子串,用Sub返回串S的第pos个字符起长度为len的子串。
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数组下标从1开始
Sub.length = len;
return true;
}
StrCompare(S, T) :比较操作,若S>T返回值>0,S=T返回值=0,S<T返回值<0。
StrCompare(SString S, SString T)
{
for(int i=0; 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;
}
Index(S, T):定位操作,若主串S中存在与串T值相同的子串,则返回它在主串S中第一次出现的位置,否则返回0
int Index(SString S, SString T)
{
int i=1, n=StrLength(S), m=StrLength(T);
SString sub;
while(i<=n-m+1)
{
SubString(sub, S, i, m);
if(StrCompare(sub, T) != 0) ++i;
else retrun i;
}
return 0;
}
二、串的模式匹配
字符串模式匹配:在主串中找到与模式串相同的子串,并返回其所在位置。
1.朴素模式匹配算法
字符串下的朴素模式匹配:暴力解决,利用 Index(S, T) 函数依次比对主串S中每个长为 T.length 的子串,直到找到或全部遍历。
数组下标下的朴素模式匹配:若当前子串匹配失败,则主串指针 i 指向下一个子串的第一个位置,模式串指针 j 回到模式串的第一个位置,若 j>T.length 则当前子串匹配成功,返回当前子串第一个字符位置 i-T.length。
最坏时间复杂度:O(mn) = O((n-m+1)m)
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) return i-T.length;
else return 0;
}
2.KMP算法
1.KMP算法
原理:每次进行匹配时,可知道失配元素前的子串都与主串对应部分匹配,利用该信息配合模式串的内容特点,在每次失配时无需让主串指针回溯,同时让模式串指针减少回溯范围,可很大程度减少不必要的匹配次数。
如下图中的情况,在模式串第六个元素不匹配时,由于模式串45位与12位匹配,因此无需将 j 置1 ,可直接让 j=3,这样的对应 j 操作总结成的数组叫next数组(该数组仅与模式串相关,与主串无关,即一个模式串对应唯一固定的next数组)。
下图模式串的next数组为:
next[0] | next[1] | next[2] | next[3] | next[4] | next[5] | next[6] |
0 | 1 | 1 | 2 | 2 | 3 |
操作:设置一个next数组,每当出现失配情况都去查询对应 j 下的跳转情况。
最坏时间复杂度:O(m+n)
int Index_KMP(SString S, SString T, int next[])
{
int i=1, j=1;
while(i<=S.length && j<=T.length)
{
if(j==0 || S.ch[i] == T.ch[j])
{
i++;
j++;
}
else
j = next[j];
}
if(j>T,length) return i-T.length; //匹配成功
else return 0; //匹配失败
}
2.next数组
作用:当模式串第 j 个字符失配时,从模式串的第next[j]继续往后匹配。
手算方法:
i next[1] = 0; next[2] = 1; 无脑写,固定的
ii 其他部分:在不匹配的位置前边,划一条分界线,让模式串一步步往后退,直到分界线前部分完全匹配,写下该情况下分界线后第一个元素对应的下标 j 就是该失配时的next[j]值(如下图 ),若模式串完全跨过分界线则next[j] = 1(全跨过去后 j 指向1)。
3.优化的KMP算法
本质:优化了next数组为nextval数组。
原理:在KMP算法原理基础上我们直到的不仅是失配元素前子串的信息,还有当前失配元素“一定不是”哪个元素,因此利用这个我们可以进一步修改next数组,进一步减少匹配次数。
如下图,原本next数组中next[3] = 1,但因为我们直到主串中 i = 3时一定不是a,因此 S.ch[3]与T.ch[1]一定不匹配,所以可以直接将 j 指针置0,让 i,j 指针直接查询下一元素的情况,无需再浪费时间进行 S.ch[3] 与 T.ch[1] 的匹配了,因此next数组可修改 next[3] = 0
优化方法:在得出next数组基础上再一遍进行判断修改next数组。
nextval[1] = 0; //这里将优化后的next数组设为nextval数组
for(int j=2; j<=T.length; j++)
{
if(T.ch[next[j]] == T.ch[j])
nextval[j] = nextval[next[j]]; //如果失配元素与next指向元素相同则更新nextval数组
else
nextval[j] = next[j]; //如果未出现这种情况直接将next对应赋值给nextval
}
总结:字符串匹配在考研中并非重点,很少会考大题,因此做好小题的计算和概念认知足矣。
三、课后题目
解答:
1)当模式串中的第一个字符与主串的当前字符比较不相等时,next[1] = 0,表示模式串应后移一位,主串当前指针后移一位,再和模式串的第一个字符进行比较。
2)当主串的第 i 个字符与模式串中第 j 个字符失配时,主串 i 不回溯,则假定模式串中的第k个字符与主串的第 i 个字符比较,k值应满足条件1<k<j 且'P1,...,Pk-1' = 'Pj-k+1,...,Pj-1',即k为模式串的下次比较位置。k值可能有很多个,为了不使向右移动丢失可能的匹配,右移距离应该取最小,由于 j-k 表示右移的距离,所以取max{k}。k的最大值为 j-1。
3)除上述两种情况,发生失配时,主串指针 i 不回溯,在最坏情况下,模式串从第1个字符开始与主串的第 i 个字符比较。
2.设有字符串S='aabaabaabaac',P='aabaac' .
1)求出P的next数组
2)若S作主串,P作模式串,试给出KMP算法的匹配过程。
解答:
1)012122 (注意next数组除了next[1]不会出现0)
2)看书P119