1.2 串的表示和实现
如果在程序设计语言中,串只是作为输入或输出的常量出现,则只需存储此串的串值,即字符序列即可。但在多数非数值处理的程序中,串也以变量的形式出现。
一、串的定长顺序存储表示
二、串的堆分配存储表示
三、串的块链存储表示
一、串的定长顺序存储表示
#define MAXSTRLEN 255 //用户可在255以内定义最大串长
typedef unsigned char Sstring[MAXSTRLEN+1];//0号单元存放串的长度
串的实际长度可在这个预定义长度的范围内随意设定,超过预定义长度的串值则被舍去,称之为“截断”。
例如:串的联接算法中需要三种情况处理:
10_002 |
Status Concat(SString S1, SString S2, SString &T) { //用T返回由S1和S2联接而成的串,若未截断,则返回True,否则返回False if(S1[0] + S2[0] <= MAXSTRLEN) { //未截断 T[1..S1[0]] = S1[1..S1[0]]; T[S1[0]+1..S1[0]+S1[0]] = S2[1..S2[0]]; T[0] = S1[0]+S2[0]; uncut = TRUE; } else if(S1[0] < MAXSTRLEN) { //截断 T[1..S1[0]] = S1[1..S1[0]]; T[S1[0]+1..MAXSTRLEN] = S2[1..MAXSTRLEN-S1[0]]; T[0] = MAXSTRLEN; uncut = FALSE; } else { //截断(仅取S1) T[0..MAXSTRLEN] = S1[0..MAXSTRLEN]; //T[0] == S1[0] == MAXSTRLEN uncut = FALSE; } return uncut; } |
按这种串的表示方法实现的串的运算时,其基本操作为“字符序列的复制”。
二、串的堆分配存储表示
typedef struct
{
char *ch; //若是非空串,则按照串长分配存储区,否则ch为NULL
int length; //串长度
} HString;
通常,C语言中提供的串类型就是以这种存储方式实现的。系统利用函数malloc()和free()进行串值空间的动态管理,为每一个新产生的串分配一个存储区,称串值共享的存储空间为“堆”C语言中的串以一个空字符为结束符,串长是一个隐含值。
这类串操作的实现算法为:先为新生成的串分配一个存储空间,然后进行串值的复制。
三、串的块链存储表示
11_001 |
#define CHUNKSIZE 80 //可由用户定义块大小 typedef struct Chunk { //结点机构体 char ch[CHUNKSIZE]; struct Chunk *next; }Chunk; typedef struct { //传递链表结构 Chunk *head, *tail; //串的头和尾指针 int curlen; //串的当前长度 }LString; |
数据域为字符占1字节,指针域为4字节。存储结构的存储密度为1/5。
串值也可用链表来存储,由于串的数据元素是一个字符,它只有8位二进制数,因此用链表存储时,通常一个结点中存放的不是一个字符,而是一个子串,例如:在编辑器系统中,整个文本编辑区可以看成是一个串,每一行是一个子串,构成一个结点。即:同一行的串定长结构(80个字符),行和行之间用指针相联接。
4.3串的模式匹配算法
这是串的一种重要操作,很多软件,若有“编辑”菜单选项的话,则其中必有“查找”子菜单项。
首先,回忆一下串匹配(查找)的定义:
INDEX(S,T,pos)
初始条件:串S和T存在,T是非空串,1<=pos<=StrLength(S)。
操作结果:若主串S中存在和串T值相同的子串返回它在主串S中第pos个字符之后第一次出现的位置;否则函数值为0。
下面讨论以定长顺序结构表示串时的几种算法。这里有三种算法:
1. 简单算法
2. 首尾匹配算法
3. KMP(D.E.Knuth,V.R.Pratt,J.H.Morris)算法
1. 简单算法
11_002 |
int Index(SString S, SString T, int pos) { //返回子串T在主串S中第pos个字符之后的位置,若不存在,则函数值为0 //其中,T为非空,1<=pos<=StrLength(S). i = pos; j = 1; while(i <= S[0] && j <= T[0]) { if(S[i] == T[j]) { ++ i; ++ j; //陆续比较后继字符 } else { i = i - j + 2; j = 1; //指针后退重新开始 } if(i > T[0]) return i - T[0]; else return 0; } } |
二、首尾匹配算法
先比较模式串的第一个字符,在比较模式串中的最后一个字符,最后比较模式串中从第二个到第n-1个字符。
11_003 |
int Index_FL(SString S, SString T, int pos) { sLength = S[0]; tLength = T[0]; i = pos; patStartChar = T[1]; patEndChar = T[tLength]; while(i <= sLength - tLength + 1) { if(S[i] != patStartChar) { ++ i; //重新匹配起始点 } else if(S[i + tLength - 1] != patEndChar) { ++ i; } else { //检查中同字符的匹配情况 //... k = 1; j = 2; while(j < tLength && S[i+k] == T[j]) { ++ k; ++ j; } if(j == tLength) { return i; } else { return ++ i; //重新开始下一次的匹配检测 } } } return 0; } |
四、KMP(D.E.Knuth,V.R.Pratt, J.H.Morris)算法
KMP算法的时间复杂度可以达到O(m+n)
当S[i]<>T[j]时,
已经得到的结果:S[i-j+1…i-1] == T[1…j-1]
若已知 T[1…k-1] == T[j-k+1 … j-1]
则有 S[i-k+1…i-1] == T[1…k-1]
定义模式串next函数
12_001 |
int Index_KMP(SString S, SString T, int pos) { //1 <= pos <= StrLength(S) i = pos; j = 1; 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; } }//Index_KMP |
求next函数值的过程是一个递推过程,分析如下:
已知:next[1]=0;
假设:next[j]=k;又T[j]=T[k];则next[j+1]=k+1;
若:T[j] <> T[k] 则需往前回朔,检查T[j]==T[?]
这实际上也是一个匹配的过程,不同在于:主串和模式串是一同一个串。
10_002 |
void get_next(SString &T, int &next[]) { //求模式串T的next函数值并存入数组next i = 1; next[1] = 0; j = 0; while(i < T[0]) { if(j == 0 || T[i] == T[j]) { ++ i; ++ j; next[i] = j; } else { j = next[j]; } } }//get_next |
还有一种特殊情况需要考虑:
例如:S=’aaabaaabaaabaaabaaab’
T=’aaaab’
Next[j]=01234
Nextval[j]=00004
12_003 |
void get_nextval(SString &T, int &nextval[]) { i = 1; nextval[1] = 0; j = 0; while(i < T[0]) { ++ i; ++ j; if(T[i] != T[j]) { next[i] = j; //少一行语句 } else { j = nextval[j]; } } }//get_nextval |
1. 熟悉串的七种基本操作的定义,并能利用这些基本操作来实现串的其它各种操作方法。
2. 熟练掌握在串的定长顺序存储结构上实现串的各种操作方法。
3. 掌握串的堆存储结构以及在其上实现串的基本方法。
4. 理解串匹配的KMP算法,熟悉NEXT函数的定义,学会手工计算给定模式串的NEXT函数值和改进的NEXT函数值。
5. 了解串操作的例子