370 likes | 579 Views
第 4 章 串. 4.1 串的基本概念 4.2 串的存储结构 4.3 串的运算. 4.1 串的基本概念. 1. 串的定义 串是由 n(n≥0) 个字符组成的有限序列。 S=’a 1 a 2 a 3 ….a n ’ S 是串名; a 1 a 2 a 3 ….a n 是串值;单引号(或双引号)是串的分界符; a i (1≤i≤n) 称为串的元素, 可以是任一字母、数字、汉字或其他字符。 2. 串的术语 串长 :串中字符的个数 n ; 空串 :串长为 0 ; 空白串 (空格串):串的元素都是空格符;串 S 的 子串 : S 中任意个连续.
E N D
第4章 串 4.1 串的基本概念 4.2 串的存储结构 4.3 串的运算
4.1 串的基本概念 1. 串的定义 串是由n(n≥0)个字符组成的有限序列。 S=’a1a2a3….an’ S是串名;a1a2a3….an是串值;单引号(或双引号)是串的分界符;ai(1≤i≤n)称为串的元素, 可以是任一字母、数字、汉字或其他字符。 2. 串的术语 串长:串中字符的个数n;空串:串长为0;空白串(空格串):串的元素都是空格符;串S的子串:S中任意个连续
4.1 串的基本概念 字符组成的子序列,包含子串的串叫该子串的主串。 S1=’World’;(长度n=5的字符串) S2=’’;(空串) S3=’ ’ 或者S3=’ φφ’ ;(空白串) S4=’Wo’;( S4是S1的子串, S1是S4的主串) S1的子串共有16个: 空串、’W’、 ’o’、 ’r’、 ’l’、 ’d’、 ’Wo’、 ’or’、 ’rl’、 ’ld’、 ’Wor’、 ’orl’、 ’rld’、 ’Worl’、 ’orld’、 ’World’。
4.1 串的基本概念 3. 串相等 当两个串的长度相等且对应字符也相等时,称两个串相等。 S1=’Kunming’ S2=’Kunming’ S3=’KunMing’ S1=S2,S1≠S3,S2≠S3。
4.2 串的存储结构 用一组地址连续的存储单元存储字符串叫串的顺序存储。一般用数组来描述串的顺序结构,称为顺序串。 定义 4.1 #define MAXSIZE 100 typedef char DataType typedef struct {DataType ch[MAXSIZE]; int length; }sqstr; ch是一个DataType类型的数组,长度MAXSIZE,用于存储串值;整型变量length用于存储串的实际长度。
4.2.1 串的顺序存储结构 例4.1 用a[100]作为串的存储的一维数组,则S=’Programming’在a中的存储状态为:
4.2.1 串的顺序存储结构 顺序串中,分为:单字节存储,紧缩存储,紧缩存储等几种方式 (1)单字节存储方式 一个字符占一个字节(byte)。当计算机系统以字节为单位编址时,一个存储单元正好存一个字符,对于串S=’Programming’的存储情况如图4.2。 当计算机系统以字(word)为单位编址时,例如 1个字为32位,一个存储单元包含4个字节,此时有紧缩存储和非紧缩存储两种存储方式。
4.2.1 串的顺序存储结构 (2)紧缩存储 一个存储单元存4个字符,串S=’Programming’在内存中存储如图4.3. (3)非紧缩存储 以存储单元为单位依次存放字符,一个存储单元存一个字符,串S=’Programming’的存储情况如图4.4。
4.2.2 串的链表存储结构 串也可以使用链表来表示,此时串叫链串。 链串的逻辑定义: #define MAXSIZE 4 typedef char DataType typedef struct node {DataType ch[MAXSIZE]; struct node *next; }lstring; 这种结构称为多字符域结点链表结构。 链表中每个结点的ch域的可存储MAXSIZE个字符,使用这个结构存储串S=’Programming’时,链串结构如图4.5。
4.2.2 串的链表存储结构 单字符域结构,即每个结点的ch域只存储一个字符的结构,此时串S=’Programming’的存储如图4.6。
4.3 串的运算4.3.1 串的基本运算 串的链表结构便于字符的插入和删除操作,但一般很少使用。关于串的运算,如果没有特殊的要求,一般用顺序存储结构进行讨论。 设S和T都是串,下面是串的基本运算。 (1)求串长length(S):求串S的字符个数。 (2)串连接concat(S,T):将S串后接T串。 (3)串比较strcmp(S,T): (4)子串查询index(S,T):求子串T在S串中的位置。
4.3.1 串的基本运算 (5)求子串substr(S,i,j): 求S串中从第i个字符开始的长度为j的子串。 (6)插入子串insert(S,T,i): 在S串中第i个字符的位置处插入T串。 (7)删除子串delete(S,i,j): 在S串中删除第i字符开始的j个字符。 (8)串的复制strcpy(S,T):将T串复制给S串。
4.3.1 串的基本运算 1. 求串长算法 int Length(sqstr *s) { int i=0; while(s->ch[i]!= ’\0’)i++; return i; }
4.3.1 串的基本运算 2. 串连接算法 讨论S1串在前,S2串接在S1串的后面。 concat(sqstr *s1,sqstr *s2) {i=0; if (s1->length+s2->length>MAXSIZE) return(error); while(i<s1->length) i++; /*i指向S1的尾部*/ for(j=0;j<=s2->length-1;j++) s1->ch[j+i]=s2->ch[j]; /*S2的字符并入S1中*/ s1->length=s1->length+s2->length; /*修改s1的length*/ s1->ch[s1->length]=’\0’; /*给S1串末尾加上结束符标志*/ }
4.3.1 串的基本运算 3. 串比较算法 strcmp(sqstr *s1,sqstr *s2) {int i; if (s1->length>s2->length) return 1; /* S1的长度大于S2,返回1*/ else if (s1->length==s2->length) /* S1的长度等于S2,进行比较操作*/ { log=1;i=0; while (log && i<=s1->length) if (s1->ch[i]== s2->ch[i]) i++; else log=0; } else return -1; /* S1的长度小于S2,返回-1*/ if (log==1) printf(“S1 is same as S2.”); else printf(“S1 isn’t same as S2.”); }
4.3.1 串的基本运算 4. 求子串算法 设S为串,把S中从第i个位置开始到第j个位置结束的子串取出,放到串T中。 substr(sqstr *s,sqstr *t,int i,int j) {int n=length(s); if (i<0 || i>n-1 || i>j){printf(“i and j is invalid.”); return;} /*判断i,j的有效性*/ else {for(k=i;k<=j;k++) /*从s中取出第i位置到第j位置的子串放到t中*/ t->ch[k-i]=s->ch[k]; t->length=j-i+1; /*修改t的长度*/ t->ch[t->length]=’\0’; /*标记t的结束符*/ } }
4.3.1 串的基本运算 5. 插入子串 设S1,S2为串,现在把S2插在S1的第i个位置上
4.3.1 串的基本运算 插入子串算法 insert(sqstr *s1,sqstr *s2,int i) { int n,m; n=length(s1); m=length(s2); if (i<0 || i>n-1 || m+n>MAXSIZE) return(error); /*判断插入位置i是否合法以及插入后S1串长度是否超界*/ else {for(j=n;j>=i;j--) /*在S1中第i个位置开始向后移动m个字符*/ s1->ch[j+m]=s1->ch[j]; for(j=i;j<i+m;j++) /*把S2插入到s1中第i个位置处*/ s1->ch[j]=s2->ch[j-i]; } s1->length=n+m; /*修改S1串的长度*/ }
4.3.1 串的基本运算 6. 删除子串 设S为串,现在把S的第i个位置开始的j个字符组成的子串从S中删除。
4.3.1 串的基本运算 删除子串算法 delete(sqstr *s,int i,int j) {int n=length(s); if (i<0 || i>n-1 || i+j-1>n) {printf(“i is invalid.”); return;} /*判断删除位置i是否合法以及删除子串长度是否超界*/ else for(k=i;k<=n-(i+j);k++) /*在s中第i+j后的字符顺序向前移动到i位置*/ s->ch[k]=s->ch[k+j]; s->length=n-j; /*修改串s的长度*/ s->ch[s->length]=’\0’; /*标记s的结束符*/ }
4.3.1 串的基本运算 7. 串复制算法 strcpy(sqstr *s1,sqstr *s2) { int n=length(s1); for(i=0;i<=n-1;i++) /*在s1的所有字符依次赋给s2*/ s2->ch[i]=s1->ch[i]; s2->ch[i]=’\0’ /*标记s2的结束符*/ s2->length=n; /*修改s2的长度*/ }
4.3.2 串的简单模式匹配 串的模式匹配就是在一个串中查找另一个串的问题。 设t和p是两个指定的串,在t中查找与p相同的子串的过程称为模式匹配。t称为正文(text),p称为模式(pattern)。t和p的长度分别为m,n,且m>>n。如果在t中找到p,则返回p在t中的最初位置,如果未找到,则返回0。 简单模式匹配的算法 使用C语言时,数组的下标从0开始,在下面的算法中丢弃t[0]和p[0]不用,以便与上述讨论一致。
4.3.2 串的简单模式匹配 简单模式匹配算法一 int index(sqstr *t, sqstr *p) {/*t为正文串,p为模式串。若t中存在p,返回t中p的第一个位置,否则返回0*/ m=length(t);n=length(p); pos=1; /*pos为正文起点*/ i=j=1; /*i为正文指针,j为模式指针*/ while(i<=m-n+1 && j<=n) if (t->ch[i]==p->ch[j]) {i++;j++;} else /*t->ch[i]≠p->ch[j]*/ {pos++;i=pos;j=1;} /*重新设置正文起点,模式从1开始*/ if(j>n) return pos; /*若模式指针大于p的长度,匹配成功*/ else return 0; /*匹配失败*/ }
4.3.3 Knuth-Morris-Pratt算法 简单模式匹配中,在进行试配的过程中进行过很多次比较,而在正文串中某个字符与模式串中的某个字符不匹配时,将从下一个位置进行试配,从而不得不丢弃前面多次比较得到的所有信息,这是导致简单模式匹配算法效率低的重要原因。是否可以利用前面多次进行比较的信息来改善模式匹配的效率? 一种改进的算法是由D.E. Knuth、J.H. Morris和V.R. Pratt三人同时发现的,称为Knuth-Morris-Pratt算法,简称KMP算法。下面来讨论这个算法。
4.3.3 KMP算法 1. KMP算法的基本思想 仍然设t=’ t1 t2……ti ……tm’(1≤i≤m),p= ‘p1 p2 …… pj ……pn’(1≤j≤n)。在进行第i+1趟匹配时,匹配从t的第i+1个位置开始,模式串p从第1个位置开始。假设此趟比较在模式串p的第j个位置失配,如图4.10,此时,ti+j≠ pj。
4.3.3 KMP算法 虽然,t i+j≠ pj,但模式与正文串的前j-1个字符全都匹配,所以有: ti+1 ti+2……ti+j-1= p1 p2 …… pj-1(4.1) 是否需要让模式p向右移动一个字符进行第i+2 次比较?此时用ti+2 ti+3……ti+j与p1 p2 …… pj-1试配,如果试配成功,则隐含: p1 p2 …… pj-2= p2 p3 …… pj-1 否则,若上式不成立,根据(4.1)式,有 ti+2 ti+3……ti+j-1= p2 p3 …… pj-1 ≠ p1 p2 …… pj-2
4.3.3 KMP算法 也就是说,第i+2次试配不需要进行同时还能够确定即使进行了比较也是不匹配的。因此可以跳过下次试配。那么到底可以跳过多少字符进行试配而又能保证正确找到匹配的子串? 假设存在这样的一个整数k,使得在模式的子串p1p2…pj-1中存在p1p2 …pk= pj-k-1 pj-k+1 … pj-1,p1p2 …pk称为p1p2…pj-1的前缀,pj-k-1 pj-k+1 … pj-1称为p1p2…pj-1的后缀(如图4.11的阴影区域)。那么如果存在这样的k,可以断言,下次试配,模式串p的位置可以移动到pj-k-1,主串t的位置移动到pi+j-k-1的位置开始(如图4.11),跳过j-k-2个字符。
4.3.3 KMP算法 这样,在第i+1次比较发现t i+j≠ pj时(正文指针i已经移动到i+j-1),正文的指针i不必返回到i+2的位置,而是回溯到i+j-k-1的位置。此外,在第i+j-k-1次试配时,由于
4.3.3 KMP算法 ti+j-k-1… ti+j-1与p1 … pk已经对应相等,所以进行第i+2次试配时,只需要从t i+j和 pk+1开始,此时,正文指针i根本就不用回溯,只需要移动到原位置的下一个位置。模式串指针j也不需要“一退到底”,而只需要退到k+1的位置。因此,在正文中寻找子串的问题就转化成在模式中寻找最大相同子串的问题,这就是KMP算法的基本思想。 KMP算法的描述: 在进行模式匹配时,如果执行t i (1≤i≤m)与 pj(1≤ j≤n)的试配,可能出现的情况如下:
4.3.3 KMP算法 (1)如果ti = pj,则继续进行试配,进行t i+1与pj+1的试配; (2)如果ti ≠ pj,则 ① 如果j=1,把模式p右移一位再从头开始进行试配。 ② 如果2≤j≤n,选择一个适当的位置f(j),进行ti与pf(j) 的试配,把模式串的位置移动到j-f(j)。f(j)称为“失效函数”。
4.3.3 KMP算法 KMP算法 int kmpmatch(sqstr *t, sqstr *p) {/*t为正文串,p为模式串。若t中存在p,返回t中p的第一个位置,否则返回0*/ m=length(t);n=length(p); i=j=1; /*i为正文指针,j为模式指针*/ while(i<=m && j<=n) if(j==0 && t->ch[i]==p->ch[j]) /*j已经退到头*/ {i++;j++;} /*继续进行比较*/ else j=f(j); /*j从f(j)开始*/ if(j>n) return i; /*若模式指针大于p的长度,匹配成功*/ else return 0; /*匹配失败*/ }
4.3.3 KMP算法 2. 失效函数的计算 失效函数的计算与正文t无关,只与模式p自身有关。在进行失效函数计算时需要满足下面两个条件: (1)k<j,找到p1 p2……pj-1的前缀子串与后缀子串,且前缀和后缀相等; (2)k最大,即这种子串必须是最长的。 “失效函数”的计算。在计算f[j]之前,f[1] f[2]…… f[j-1]都已经计算出来,f[j]的计算可能会使用f[1]…… f[j-1]的值。
4.3.3 KMP算法 例如,p=’abcabcacab’,可以求得下表f(j)的取值
4.3.3 KMP算法 KMP算法中失效函数的计算 int f(sqstr *p) {/*在模式串p中计算最大相同子串*/ n=length(p); f[1]=0; j=1;k=0; while(j<=n) if(k==0 && p->ch[j]==p->ch[k]){k++;f[++j]=k; else k=f[k]; }
本章小结 字符串是计算机处理的重要对象之一。本章讨论了串的定义、存储结构、串的运算、串的模式匹配等问题。 串是由n(n≥0)个字符组成的有限序列,是一种单个字符组成的线性表,是表结构的一种特殊结构。 串可以使用顺序结构和链表结构进行存储和表示,但是一般情况下主要使用串的顺序结构来讨论串的各种运算。 串的运算有求串长、串连接、串比较、子串查询、求子串、插入子串、删除子串、串的复制。子串查询又称为串的模式匹配。 串的模式匹配有简单匹配、KMP、BM和KR等几种。