数据结构与算法系列研究三——字符串
字符串的研究和KMP算法分析和实现
一、串的定义
串是计算机非数值处理的基本对象。串是一种特殊的线性表,它的每个结点仅由一个字符组成,并且单个元素是无意义的。
1、串(string):是由0个或多个字符组成的有限序列,记作:
S=“a1a2...an” (n>=0)
其中:S是串名,两个双引号括起来的字符序列为串的值。双引号不属于串。
ai(1<=i<=n)为字母、数字或其它符号。
空格符是一个有效字符。
2、串中所有字符的个数n为串的长度。长度为0的串称为空串。
3、空格串:全部由空格符组成的字符序列。
4、子串:串中任意连续个字符的序列称为该串的子串。
5、主串:包含该子串的串称为主串。
6、字符在串中的序号称为该字符在串中的位置。
7、子串在串中的位置:用子串第一个字符在主串中的位置来表示。空串是任意串的子串。任意串是自身的子串。
8、串常量:不能改变其值的量为常量,串常量一般用直接量表示。
串变量:用于存放字符串值,并且其值可以改变的量。
相关运算:(黄色为基本子集,有这几种操作可以产生其他复杂操作)
Strassign (&S,chars):将常数串赋值给S,对应于strcopy。
Strlength(S):求串S的长度,对应于strlen(s)。
Strcompare(S1,S2):比较串S1和S2,对应于strcmp(s1,s2)。
Concat(&S,S1,S2):将串S1和S2联接成一个串,并赋给S。
Substring(&Sub,S,pos,len):在S串中求pos开始的长度为len的子串。
Clearstring(&S):将串S置成空串。
Strcopy (&S,S1):将串S1复制到变量S
Strempty(&S):判断串S是否为空串。
Index(S,Sub,pos):求子串sub在串S的pos开始后出现的位置。
Replace(&S,Sub,T);用Sub替换S中所有与T相等的不重叠的子串。
Strinsert(&S,pos,Sub):在串S的pos位置前插入子串Sub。
Strdelete(&S,pos,len):在串S中删除pos位置开始长度为len的子串。
Destroystring(&S):撤消串S。
串的表示与实现—堆分配存储:
分配一组地址连续的与串长一致的存储单元存放串值字符序列。与定长顺序表示的区别:采用动态分配;串空间与串长一致,串长变化将引起串空间的重新分配。
堆存储表示:
typedef struct{
char *ch;
int length;
}HString
串的表示与实现—块链表示
块:一组连续的字符。
块链存储表示:把串分成指定等长的块,每一块用一个结点表示,把各块链成一个链表。
当一个结点不满时,用特殊字符(如‘#’)填充。
若块的长度为1,就是以单字符为结点的链表结构。
块的大小与存储密度有关:存储密度=串值所占存储位/实际分配存储位。
单字符结点:插入、删除方便;存储密度小,存储占用量大。
多字符结点:存储密度大;插入、删除存在结点的分离,降低存储密度。
二、字符串的KMP算法
2.1、串的模式匹配算法
串的模式匹配:求子串P在主串T中的位置的定位操作称为串的模式匹配。
模式串:子串。
2.2、简单的模式匹配算法:
从主串T的第一个字符起,与模式串P的第一个字符比较,若相等,则继续逐个比较后继字符,否则从主串T的第二个字符起再重新和模式串P的第一个字符比较。依次类推,直到模式串P中的每个字符依次和主串T中的一个连续的字符序列相等,则称匹配成功,返回与P匹配的主串T的字符序列的第一个字符序号。否则称匹配失败,函数值为0(或-1)。简单模式匹配算法存在的问题:在模式匹配过程中存在已比较的字符重复比较。实际上已比较过的字符不必重复比较。
1 int Index(SString T,SString P,int pos)
2 {
3 int i,j,k;
4 int m=strlen(P);
5 int n=strlen(T);
6 for(i=pos-1;i<n-m;i++)
7 {
8 for(j=0,k=i;j<m&&T[k]==P[j]; k++; j++);
9 if(j==m) reurn i;
10 }
11 return -1;
12 }
2.3、KMP算法实现
2.3.1、输入和输出
输入:输入主串和模式串,以及开始比较的位置
输出:输出模式串和主串开始匹配的起始位置,若不匹配则返回0,若匹配则返回匹配的位置。
2.3.2、关键数据结构与算法描述
数据结构:字符数组和整形数组
算法描述:使用KMP算法,进行字符串匹配,最大的特点和优点就是i指针不回溯。首先要找到模式串对应的next数组的值。由于找到next数组是为了更好的进行匹配,因此再进行模式串与模式串的匹配求next数组时,可以对next数组进行优化,亦即如果t[i]==t[j],在进行i++,j++后如果t[i]==t[j],则next[i]=next[j];如果不相等,next[i]=j。求出next数组之后,就是KMP算法的主体了,要是j==-1或者s[i]==t[j],i,j都要加一,要不然j=next[j];继续进行比较,直至子串或主串结束。
2.3.2.1、求next的算法
1 void get_nextval(char *t,int next[])
2 {
3 int i=0,j=-1;
4 int size=strlen(t);
5 next[0]=-1;
6 while(i<size-1)
7 {
8 if(j==-1||t[i]==t[j])
9 {
10 i++;
11 j++;
12
13 if(t[i]==t[j])
14 {
15 next[i]=next[j];
16 }
17 else
18 next[i]=j;
19 }
20 else
21 j=next[j];
22 }
23 }
2.3.2.2、KMP算法主体
1 int KMP(char *s,char *p,int position,int next[])
2 {
3 int i=position-2;//position 为比较位置,从1开始读
4 int j=-1;
5 int S_SIZE=strlen(s),P_SIZE=strlen(p);
6 while(i<S_SIZE&&j<P_SIZE)
7 {
8 if(j==-1||s[i]==p[j])
9 {
10 i++;
11 j++;
12 }
13 else
14 j=next[j];
15 }
16 if(j<P_SIZE)
17 return 0;
18 else
19 return i-j+1;
20 }
2.3.3、测试与理论
1.主串:abcabdsfegabcdsdfg
模式串:abdsfe
起始位置:2,5
理论结果:4,0
2.主串:qqwerttryuriopazgdjcvjkfn
模式串:ttryu
起始位置:3,7
理论结果:6,0
2.3.4、所有代码
1 #include"stdio.h"
2 #include"string.h"
3 void get_nextval(char *t,int next[])
4 {
5 int i=0,j=-1;
6 int size=strlen(t);
7 next[0]=-1;
8 while(i<size-1)
9 {
10 if(j==-1||t[i]==t[j])
11 {
12 i++;
13 j++;
14
15 if(t[i]==t[j])
16 {
17 next[i]=next[j];
18 }
19 else
20 next[i]=j;
21 }
22 else
23 j=next[j];
24 }
25 }
26
27 int KMP(char *s,char *p,int position,int next[])
28 {
29 int i=position-2;//position 为比较位置,从1开始读
30 int j=-1;
31 int S_SIZE=strlen(s),P_SIZE=strlen(p);
32 while(i<S_SIZE&&j<P_SIZE)
33 {
34 if(j==-1||s[i]==p[j])
35 {
36 i++;
37 j++;
38 }
39 else
40 j=next[j];
41 }
42 if(j<P_SIZE)
43 return 0;
44 else
45 return i-j+1;
46 }
47
48 int main()
49 {
50 char s[100];
51 char t[100];
52 int next[100],pos=1;
53 while(1)
54 {
55
56 printf("请输入主串:\n");
57 scanf("%s",s);
58 printf("请输入模式串:\n");
59 scanf("%s",t);
60 printf("请输入开始比较的位数(默认为一)\n");
61 scanf("%d",&pos);
62
63 get_nextval(t,next);
64 printf("开始匹配的起始位置为(若为0则不匹配)\n");
65
66 printf("%d\n",KMP(s,t,pos,next));
67 }
68 return 0;
69 }