什么是串呢?我们打字输入的信息,便可称为串。串其实比较简单,类似于的线性表。我们在各种编程语言中直接使用,比如
char [],或者 int []
一、基本概念
定义:由零个或多个字符组成的有限序列。串中字符的个数称为串的长度,含有零个元素的串叫空串。
二、串的存储结构
1.定长顺序存储表示
typedef struct{
char str[maxSize+1];
int length;
}Str;
2.变长分配存储
typedef struct{
char *ch;
int length;
}Str;
三、基本操作
1.赋值操作
串的赋值操作必须对每个元素逐一进行赋值操作
使用格式strassign(str,"cur input");
int strassign(Str& str,char* ch){
if(str.ch)
str free(str.ch);//释放原串空间
int len=0;
char *c=ch;
while(*c){
++len;
++c;
}
if(len==0){
str.ch=NULL;
str.length=0;
return 1;
}
else
{
str.ch=(char*)malloc(sizeof(char)*(len+1));//len+1因为多分配了“\0”
if(str.ch==NULL)
return 0;
else{
c=ch;
for(int i=0;i<length++i,++c)
str.ch[i]=*c;
str.length=len;
return 1;
}
}
}
2.取串长
直接用str.length
在使用变长分配存储表示时
int strlength(Str str){
return str.length;
}
3.串比较操作
比较规则为,字符串A和B的待比较字符分别为a和b,(值大小根据ascll码)
若a>b,则A>B
若a<b,则A<B
若a=b,则按之前的规则继续往下比较下一对字符,一直比较,当某个串先结束则该串为较小的串。
两串同时结束,则两串相等。
int strcompare(Str str){
for(int i=0;i<s1.length&&i<s2.length;++i)
if(s1.ch[i]!=s2.ch[i])
return s1.ch[i]-s2.ch[i];
return s1.length-s2.length
}
4.串连接操作
两个串首尾相接,合并成一个字符串的操作称为串连接操作。
5.求子串
下面状态实现了求str中从pos位置开始,长度为len的子串
int substr(Str& substr,Str str,int pos,int len){
if(pos<0||pos>=str.length||len<0||len>str.length-pos)
return 0;
if(substr.ch){
free(substr.ch);
substr.ch=NULL;
}
if(len==0)
{
substr.ch=NULL;
substr.length=0;
return 1;
}
else
{
substr.ch=(char*)malloc(sizeof(char)*(len+1));
int i=pos;
int j=0;
wwhile(i<pos+len){
substr.ch[j]=str.ch[i];
++i;
++j;
}
substr.ch[j]='\0';
substr.length=len;
return 1;
}
}
6.串清空
int clearstr(Str& str){
if(str.ch){
free(str.ch);
str.ch=NULL;
}
str.length=0;
return 1;
}
四、模式匹配算法
1.简单模式匹配算法
子串的定位操作称为串的模式匹配,待定位的子串称为模式串
基本思想:从主串的第一个字符开始比较,
若相等,则继续逐一比较后续字符。
否则,从主串的第二个字符开始,再次用上一步方法左比较,
以此类推,直到比较完模式串中的所有字符。
若匹配成功,则返回模式串在主串中的位置
若匹配失败,则返回一个可区别于主串所有位置的标记,如0
下图展示了主串“ABABCABCACBAB”和“ABCAC”的六次匹配过程,黄色区域表示这次中做过比较的字符,红色代表字符不等
int index(Str,Str substr){
int i=1,j=1,k=i;
while(i<=str.length && j<=substr.length){
if(str.ch[i]==substr.ch[j])
{
++i;
++j;
}
else{
j=1;
i=++k;
}
}
if(j>substr.length)
return k;
else return 0;
}
2.KMP算法
回顾上面简单的匹配算法,会发现存某些多余的比较。
KMP算法利用比较过的信息,i指针不需要回溯,仅将子串向后滑动一个合适的位置,并从这个位置开始和主串进行比较。
这个合适的位置仅与子串本身的结构有关。
(1)先搞清楚几个小概念
- 前缀:除去最后一个字符外,字符串的所有头部子串
- 后缀:除去第一个字符外,字符串的所有尾部子串
- 部分匹配值:为字符串的前缀和后缀的最长相等前后缀长度
举例:“ababa”
'a'的前缀和后缀都为空集,最长相等前后缀长度为0
’ab‘的前缀 为{a},后缀为{b},{a}{b}=空,最长相等前后缀长度为0
aba的前缀为{a,ab},后缀为{a,ba},{a,ab}{a,ba}={a},最长相等前后缀长度为1
abab,前缀{a,ab,aba,ba}后缀{bab,ab,b}={ab},最长相等前后缀长度为2
ababa,前缀{a,ab,aba,abab}后缀{a,ba,aba,baba}={a,aba},最长相等前后缀长度为3
故部分匹配值为 00123
(2)KMP实例
主串ababcabaacbab,子串为abcac
根据(1)中方法得出子串的部分匹配值为00010,将部分匹配值写为数组形式得到next数组
编号 | 1 | 2 | 3 | 4 | 5 |
S | a | b | c | a | c |
next | 0 | 0 | 0 | 1 | 0 |
使用next数组进行字符串匹配,
第一次匹配:a与c不匹配,前面两个字符’ab‘匹配,查表可知,最后一个匹配字符b对应的部分匹配值为0,
因此按照 公式 移动位数=已匹配的字符数-对应的部分匹配值
有2-0=2,所以将子串后移2位, 进行第二次匹配
第二次匹配中,c与b不匹配,前面4个字符’abca‘是匹配的,最后一个匹配字符a对应的部分匹配值为1,
有4-1=3,将子串向后移动3位,如下进行第三次,
第三次匹配全部匹配成功
KMP时间复杂度(n+m)
(某乎的高赞解释:https://www.zhihu.com/question/21923021)