1.串的定义:
是由零个或者多个字符组成的有限序列,又叫字符串
不含字符的叫空串
只含空格的串叫做空格串
2.串的抽象数据类型:
ADT 串(string)
Data
串的元素仅有一个字符组成,相邻元素具有前驱和后继的关系
Operation
StrAssign(T,*chars)//生成一个其值等于字符串常量chars的串T
StrCopy(T,S)//串S存在由串S复制得T
ClearString(S)//若串S存在则将串清空
StringEmpty(S)//若串S为空,返会true,否则返回false
StrLength(S)//返回串得长度
StrCompare(S,T)//若S>T则返回>0,若S==T,则返回0,否则返回<0;
Concat(T,S1,S2)//用T返回由S1,S2连接的新串
SubString(Sub,S,pos,len)//用sub返回串S中第pos个字符长度为len的字串
Index(S,T,pos)//串S和T存在,T是非空串,若主串S中存在和串T值相同的字串,那么返回它在主串出现的第一个位置,否则返回0
Replace(S,T,V)//若三个串同时存在,T是非空串,用V替换S串中出现的所有与T相等的且不重叠的子串
StrInsert(S,pos,T)//若串S和T存在在串S的第pos个字符之前插入串T
StrDelete(S,pos,len)//若S存在,从S串中删除第pos个字符起长度为len的字串
endADT
3.串的顺序存储结构具体实现代码:
别看就一百多行我写了一天
#include<stdio.h>
#include<bits/stdc++.h>
#include<windows.h>
#define max 100
typedef char string[max+1];
int strassign(string a,char *chars)//生成一个串
{
if(strlen(chars)>max)
{
printf("串的下标越界!\n");
return 0;
}
a[0]=strlen(chars);
for(int i=0;i<a[0];i++)
{
a[i+1]=chars[i];
}
return 1;
}
int strcopy(string t,string s)//把一个串复制给另一个串 (t复制给s)
{
s[0]=t[0];
for(int i=1;i<=s[0];i++)
{
s[i]=t[i];
}
return 0;
}
int clearstring(string s)//将串清空
{
s[0]=0;
memset(s,0,sizeof(s));
return 1;
}
int stringempty(string s)//判断串是否为空
{
if(s[0]==0)
{
printf("空\n");
return 1;
}
printf("不为空\n");
return 0;
}
int stringlen(string s)//返回串的长度
{
return s[0];
}
int stringcompare(string s,string t)//比较两个串的大小
{
for(int i=1;i<=s[0];i++)
{
if(s[i]>t[i])
{
//printf("串1比串2大\n");
return 1;
}
else if(s[i]<t[i])
{
// printf("串1比串2小\n");
return -1;
}
else if(s[i]==t[i]&&i==s[0]&&i<t[0])
{
// printf("串1比串2小\n");
return -1;
}
else if(s[i]==t[i]&&i==t[0]&&i<s[0])
{
// printf("串1比串2大\n");
return 1;
}
else if(s[i]==t[i]&&i==s[0]&&i==t[0])
{
// printf("两串相同\n");
return 0;
}
else if(s[i]==t[i])
continue;
}
}
int concat(string s,string t1,string t2)//连接两个串
{
if(t1[0]+t2[0]>max)
return 0;
s[0]=t1[0]+t2[0];
int cnt=t1[0]+1;
for(int i=1;i<strlen(t1);i++)
{
s[i]=t1[i];
}
for(int i=1;i<=t2[0];i++){
s[cnt++]=t2[i];
}
return 1;
}
int substring(string sub,string s,int pos,int len)//获得s的第pos个字符开始长为len的子串长度给sub
{
sub[0]=len;
int cnt=1;
for(int i=pos;i<pos+len;i++)
{
sub[cnt++]=s[i];
}
return 1;
}
void trav(string s)//遍历串
{
if(s[0]==0)
{
printf("此串为空\n");
return ;
}
printf("此串的长度为:%d\n",(int)s[0]);
printf("此串为:");
for(int i=1;i<=s[0];i++)
{
printf("%c",s[i]);
}
printf("\n");
}
int index(string s,string t,int pos)//判断s中是否存在子串t
{
if(t[0]>s[0])
return 0;
int m=s[0];
int n=t[0];
int j=pos;
string subb;
for(int i=j;i<=m-n+1;i++)
{
substring(subb,s,i,n);
if(stringcompare(subb,t)==0)
return i;
else
continue;
}
return 0;
}
int StrInsert(string S, int pos, string T)//在某个位置插入
{
int i;
for (i = S[0]; i >= pos; i--)
S[i + T[0]] = S[i];
for (i = pos; i<pos + T[0]; i++)
S[i] = T[i - pos + 1];
S[0] = S[0] + T[0];
return 0;
}
int stringdelete(string s,int pos,int len)//在s串中从pos位置开始删除长度为len的串
{
int cnt=s[0];
s[0]=s[0]-len;
for(int i=pos;i<cnt;i++)
{
s[i]=s[i+len];
}
return 1;
}
int stringreplace(string s,string t,string v)//把s中的t串换成v串
{
for(int i=1;i<=s[0]-t[0]+1;i++)
{
string sub;
substring(sub,s,i,t[0]);
if(stringcompare(sub,t)==0)
{
stringdelete(s,i,t[0]);
StrInsert(s,i,v);
i=i+t[0]-1;
}
}
}
int main()
{
char arr1[20];
char crr1[20];
char brr1[20];
scanf("%s%s%s",arr1,brr1,crr1);
string arr;
string crr;
string brr;
strassign(arr,arr1);
strassign(brr,brr1);
strassign(crr,crr1);
stringreplace(arr,brr,crr);
trav(arr);
}
4.串的链式存储结构:
和线性表的链式存储结构表相差不大,总的来说没有顺序结构那么灵活。
5.KMP模式匹配算法:
(1) 首先看一下朴素的匹配方法:
可以看出来时间复杂度为O(m*n),非常低效。
(2)KMP模式匹配算法(这玩意我看光看书真是让我脑阔疼):
目的:KMP算法的作用是在主串中匹配是否有字串和模式串相同
其优点:是比普通的做法时间复杂度更小
首先看例子:
朴素算法中,P的第j位失配,默认的把P串后移一位。
但在前一轮的比较中,我们已经知道了P的前(j-1)位与S中间对应的某(j-1)个元素已经匹配成功了。这就意味着,在一轮的尝试匹配中,我们get到了主串的部分内容,利用这些内容,我们可以让P多移几位(我认为这就是KMP算法最根本的东西)
(3)接下来介绍前缀后缀最大公共长度:
前缀:就是包含第一个字符但不包含最后一个字符的连续子串
后缀:就是包含最后一个字符但不包含第一个字符的连续子串
对于串abcab
前缀有
1)a
2)ab
3)abc
4)abca
后缀有
1)b
2)ab
3)cab
4)bcab
对于这个例子最长公共前后缀那必然是ab
(4)next数组:
**next[j]就是第j个元素前j-1个元素所构成的字符串的最大前缀后缀最大公共长度加1;**那么next数组的元素个数就是所匹配串的长度,规定next[1]=0,
若最大前后缀最大公共长度为0,那么数组元素就是1
那么我们手写一个串的next数组:
(5)
具体实现代码:
#include<stdio.h>
#include<string.h>
#define max 100
typedef char string[max];
int strassign(string a,char *chars)//生成一个串
{
if(strlen(chars)>max)
{
printf("串的下标越界!\n");
return 0;
}
a[0]=strlen(chars);
for(int i=0;i<a[0];i++)
{
a[i+1]=chars[i];
}
return 1;
}
void get_next(string T,int *next)
{
int i;
int j;
i=1;
j=0;
next[1]=0;
while(i<T[0])
{
if(j==0||T[i]==T[j])
{
next[++i]=++j;
}
else
j=next[j];
}
}
int index_kmp(string s,string t,int pos)
{
int i=pos;
int j=1;
int next[20];
get_next(t,next);
while(i<=s[0]&&j<=t[0])
{
if(j==0||s[i]==t[j])
{
i++;
j++;
}
else
j=next[j];
}
if(j>t[0])
{
return i-t[0];
}
else
return 0;
}
int main()
{
char arr1[20];
char brr1[20];
printf("请输入主串:\n");
scanf("%s",arr1);
printf("请输入匹配串:\n");
scanf("%s",brr1);
string brr;
string arr;
strassign(arr,arr1);
strassign(brr,brr1);
printf("此匹配串在主串中的位置:\n");
printf("%d",index_kmp(arr,brr,1));
return 0;
}
(6)首先讲next数组是如何代码实现:
在已知next前k个元素的值之后求:next[k+1]
1)要求next[k+1] 其中k+1=17
2)已知next[16]=8,则元素有以下关系:
3)如果P8=P16,则明显next[17]=8+1=9(是next[k+1]最大的值)
4)如果不相等,又若next[8]=4,则有以下关系
主要是为了证明:
5)现在在判断,如果P16=P4则next[17]=4+1=5,否则,在继续递推(这就是get_next里的就j=next[j]的意义)
6)若next[4]=2,则有以下关系
7)若P16=P2,则next[17]=2+1=3;否则继续取next[2]=1、next[1]=0;遇到0时还没出结果,则递推结束,此时next[17]=1;
8)那么如何使程序找到并返回对应下标
还要提的是i值是没有变小的(这里就叫主串指针没有回朔),只有j的值是一直在变,,终止条件未啥是**j>t[0]**呢,因为一旦有一个匹配到模式串的最后一个字符相等时进入if里边进行++,所有一旦大于t[0]+1=j的时候就是匹配到相同子串的时候。
KMP算法的改进(卧槽还这么。。。。的算法还要改进?我tm:"。。。。"(虎狼之词))
对如主串: aaaabcde
模式串: aaaaax
第一次匹配时模式串的第五个a与主串b不匹配,那么会根据next数组进行重新匹配,结果是会和朴素的算法一样如图:
其实自从模式串的第五个a不与b匹配后,前面的a就不需要进行匹配,这里就出现了低效的一面。
先上代码:
void get_nextval(string T,int *nextval)
{
int i;
int j;
i=1;
j=0;
nextval[1]=0;
while(i<T[0])
{
if(j==0||T[i]==T[j])
{
i++;
j++;
if(T[i]!=T[j])
nextval[i]=j;
else
nextval[i]=nextval[j];//******//
}
else
j=nextval[j];
}
}
标注星号的地方就是所作出的改进,这个地方的意思是(从开始的主串和模式串举例子):第一次主串的b和模式串的第五个a不匹配那么那一步代表如果第五个a就的位置将会被第五个a的nextal取代,这时候如果取代的那个值和第五个a相等的话,那么就不用进行比较了,肯定不匹配,那么这时候怎么办呢,这时候nextval的值就会等于第五个a的nextval的netval值*这么绕口的说法。。。
那么还拿这张图解释一下:
这时候假设匹配到16失败(第16位元素的nextval存的值是8,第8位存的是4),那么比较第16位元素的nextval值对应的元素(由图可以看出是第八位)与第16位元素进行对比,如果不相等那么nextval的值就会和此处的next值一样,否则此处(16位)的nextval值就会等于第8位的nextval的值。这里你可能还会有想法,就是你怎么确定第4位(就是第8位nexttval的下标对应得值)的元素和第16一定不等呢?
那么就会有一个递推的思想,就是,我们nextval的值是从前往后推的,那么第8位的netival既然存的是4,那么第8位的元素一定不会和第4位相等,回到第8位与第16位,既然开始说的第8位与第16位的元素相等,那么第16位元素肯定不会和第4位相等。。。
讲到这我就尽力了,理解起来真是让我脑壳疼。。。。