数据结构 (四) 串及其基本操作

原创
2014/12/08 22:55
阅读数 891

4.1 串及其基本运算

串(即字符串)是一种特殊的线性表,它的数据元素仅由一个字符组成,计算机非数值处理的对象经常是字符串数据,如在汇编和高级语言的编译程序中,源程序和目标程序都是字符串数据;在事物处理程序中,顾客的姓名、地址、货物的产地、名称等,一般也是作为字符串处理的。另外串还具有自身的特性,常常把一个串作为一个整体来处理,因此,在这一章我们把串作为独立结构的概念加以研究,介绍串的串的存储结构及基本运算。

4.1.1 串的基本概念


1.串的定义

串是由零个或多个任意字符组成的字符序列。一般记作:s="s1 s2 … sn""

其中s 是串名;在本书中,用双引号作为串的定界符,引号引起来的字符序列为串值,引号本身不属于串的内容;ai(1<=i<=n)是一个任意字符,它称为串的元素,是构成串的基本单位,i 是它在整个串中的序号; n 为串的长度,表示串中所包含的字符个数,当n=0 时,称为空串,通常记为Ф。

2.几个术语

子串与主串:串中任意连续的字符组成的子序列称为该串的子串。包含子串的串相应地称为主串。
子串的位置:子串的第一个字符在主串中的序号称为子串的位置。
串相等:称两个串是相等的,是指两个串的长度相等且对应字符都相等。

4.1.2 串的基本运算

串的运算有很多,下面介绍部分基本运算:

1.求串长StrLength(s)
操作条件:串s 存在
操作结果:求出串s 的长度。

2.串赋值StrAssign(s1,s2)
操作条件: s1 是一个串变量,s2 或者是一个串常量,或者是一个串变量(通常s2 是一个串常量时称为串赋值,是一个串变量称为串拷贝)。
操作结果:将s2 的串值赋值给s1, s1 原来的值被覆盖掉。

3.连接操作:StrConcat (s1,s2,s) 或StrConcat (s1,s2)
操作条件:串s1,s2 存在。
操作结果:两个串的联接就是将一个串的串值紧接着放在另一个串的后面,连接成一个串。前者是产生新串s,s1 和s2 不改变; 后者是在s1 的后面联接s2 的串值,s1 改变, s2不改变。
例如: s1="he",s2=" bei",前者操作结果是s="he bei";后者操作结果是s1="he bei"。

4.求子串SubStr (s,i,len):
操作条件:串s 存在,1≤i≤StrLength(s),0≤len≤StrLength(s)-i+1。
操作结果:返回从串s 的第i 个字符开始的长度为len 的子串。len=0 得到的是空串。
例如:SubStr("abcdefghi",3,4)= "cdef"

5.串比较StrCmp(s1,s2)
操作条件:串s1,s2 存在。
操作结果:若s1==s2,操作返回值为0;若s1<s2, 返回值<0;若s1>s2, 返回值>0。

6.子串定位StrIndex(s,t):找子串t 在主串s 中首次出现的位置
操作条件:串s,t 存在。
操作结果:若t∈s,则操作返回t 在s 中首次出现的位置,否则返回值为-1。
如:StrIndex("abcdebda","bc")=2
StrIndex("abcdebda","ba")=-1

7.串插入StrInsert(s,i,t)
操作条件:串s,t 存在,1≤i≤StrLength(s)+1。
操作结果:将串t 插入到串s 的第i 个字符位置上,s 的串值发生改变。

8.串删除StrDelete(s,i,len)
操作条件:串s 存在,1≤i≤StrLength(s),0≤len≤StrLength(s)-i+1。
操作结果:删除串s 中从第i 个字符开始的长度为len 的子串,s 的串值改变。

9.串替换StrRep(s,t,r)
操作条件:串s,t,r 存在,t 不为空。
操作结果:用串r 替换串s 中出现的所有与串t 相等的不重叠的子串,s 的串值改变。以上是串的几个基本操作。其中前5个操作是最为基本的,它们不能用其他的操作来合成,因此通常将这5个基本操作称为最小操作集。

4.2 串的定长顺序存储及基本运算

因为串是数据元素类型为字符型的线性表,所以线性表的存储方式仍适用于串,也因为字符的特殊性和字符串经常作为一个整体来处理的特点,串在存储时还有一些与一般线性表不同之处。

4.2.1 串的定长顺序存储


类似于顺序表,用一组地址连续的存储单元存储串值中的字符序列,所谓定长是指按预定义的大小,为每一个串变量分配一个固定长度的存储区,如:
#define MAXSIZE 256
char s[MAXSIZE];
则串的最大长度不能超过256。

如何标识实际长度?

1. 类似顺序表,用一个指针来指向最后一个字符,这样表示的串描述如下:
typedef struct
{ char data[MAXSIZE];
int curlen;
} SeqString;
定义一个串变量:SeqString s。这种存储方式可以直接得到串的长度:s.curlen+1。如图4.1 所示。

2. 在串尾存储一个不会在串中出现的特殊字符作为串的终结符,以此表示串的结尾。比如C 语言中处理定长串的方法就是这样的,它是用’\0’来表示串的结束。这种存储方法不能直接得到串的长度,是用判断当前字符是否是’\0’来确定串是否结束,从而求得串的长度。

3. 设定长串存储空间:char s[MAXSIZE+1]; 用s[0]存放串的实际长度,串值存放在s[1]~s[MAXSIZE],字符的序号和存储位置一致,应用更为方便。

4.2.2 定长顺序串的基本运算


本小节主要讨论定长串联接、求子串、串比较算法,顺序串的插入和删除等运算基本与顺序表相同,在此不在赘述。串定位在下一小节讨论,设串结束用'\0'来标识。

1.串联接:把两个串s1 和s2 首尾连接成一个新串s ,即:s<=s1+s2。
int StrConcat1(s1,s2,s)
char s1[],s2[],s[];
{ int i=0 , j, len1, len2;
len1= StrLength(s1); len2= StrLength(s2)
if (len1+ len2>MAXSIZE-1) return 0 ; /* s 长度不够*/
j=0;
while(s1[j]!=’\0’) { s[i]=s1[j];i++; j++; }
j=0;
while(s2[j]!=’\0’) { s[i]=s2[j];i++; j++; }
s[i]=’\0’; return 1;
}
算法4.1

2.求子串
int StrSub (char *t, char *s, int i, int len)
/* 用t 返回串s 中第个i 字符开始的长度为len 的子串1≤i≤串长*/
{ int slen;
slen=StrLength(s);
if ( i<1 || i>slen || len<0 || len>slen-i+1)
{ printf("参数不对"); return 0; }
for (j=0; j<len; j++)
t[j]=s[i+j-1];
t[j]=’\0’;
return 1;
}
算法4.2

3.串比较
int StrComp(char *s1, char *s2)
{ int i=0;
while (s1[i]==s2[i] && s1[i]!=’\0’) i++;
return (s1[i]-s2[i]);
}
算法4.3

4.2.3 模式匹配


串的模式匹配即子串定位是一种重要的串运算。设s 和t 是给定的两个串,在主串s中找到等于子串t 的过程称为模式匹配,如果在s 中找到等于t 的子串,则称匹配成功,函数返回t 在s 中的首次出现的存储位置(或序号),否则匹配失败,返回-1。t 也称为模式。为了运算方便,设字符串的长度存放在0 号单元,串值从1 号单元存放,这样字符序号与存储位置一致。

1.简单的模式匹配算法
算法思想如下:首先将s1 与t1 进行比较,若不同,就将s2 与t1 进行比较,...,直到s的某一个字符si 和t1 相同,再将它们之后的字符进行比较,若也相同,则如此继续往下比较,当s 的某一个字符si 与t 的字符tj 不同时,则s 返回到本趟开始字符的下一个字符,即si-j+2,t 返回到t1,继续开始下一趟的比较,重复上述过程。若t 中的字符全部比完,则说明本趟匹配成功,本趟的起始位置是i-j+1 或i-t[0],否则,匹配失败。设主串s="ababcabcacbab",模式t="abcac",匹配过程如图4.3 所示。

依据这个思想,算法描述如下:
int StrIndex_BF (char *s,char *t)
/*从串s 的第一个字符开始找首次与串t 相等的子串*/
{ int i=1,j=1;
while (i<=s[0] && j<=t[0] ) /*都没遇到结束符*/
if (s[i]==t[j])
{ i++;j++; } /*继续*/
else
{i=i-j+2; j=1; } /*回溯*/
if (j>t[0]) return (i-t[0]); /*匹配成功,返回存储位置*/
else return –1;
}
算法4.4

该算法简称为BF 算法。下面分析它的时间复杂度,设串s 长度为n,串t 长度为m。匹配成功的情况下,考虑两种极端情况:在最好情况下,每趟不成功的匹配都发生在第一对字符比较时:
例如:s="aaaaaaaaaabc"
t="bc"
设匹配成功发生在si 处,则字符比较次数在前面i-1 趟匹配中共比较了i-1 次,第i 趟成功的匹配共比较了m 次,所以总共比较了i-1+m 次,所有匹配成功的可能共有n-m+1种,设从si 开始与t 串匹配成功的概率为pi,在等概率情况下pi=1/(n-m+1),因此最好情况下平均比较的次数是:

即最好情况下的时间复杂度是O(n+m)。在最坏情况下,每趟不成功的匹配都发生在t 的最后一个字符:
例如:s="aaaaaaaaaaab"
t="aaab"
设匹配成功发生在si 处,则在前面i-1 趟匹配中共比较了(i-1)*m 次,第i 趟成功的匹配共比较了m 次,所以总共比较了i*m 次,因此最坏好情况下平均比较的次数是:

即最坏情况下的时间复杂度是O(n*m)。

上述算法中匹配是从s 串的第一个字符开始的,有时算法要求从指定位置开始,这时算法的参数表中要加一个位置参数pos:StrIndex(shar *s,int pos,char *t),比较的初始位置定位在pos 处。算法4.4 是pos=1 的情况。

4.3 串的堆存储结构—串名的存储映象

串名的存储映象是串名-串值内存分配对照表,也称为索引表。表的形式有多种表示,如设s1="abcdef",s2="hij",常见的串名-串值存储映象索引表有如下几种:

1. 带串长度的索引表


如图4.4 所示,索引项的结点类型为:
typedef struct
{ char name[MAXNAME]; /*串名*/
int length; /*串长*/
char *stradr; /*起始地址*/
} LNode;

2. 末尾指针的索引表


如图4.5 所示,索引项的结点类型为:
typedef struct
{ char name[MAXNAME]; /*串名*/
char *stradr,*enadr; /*起始地址,末尾地址*/
} ENode;

3. 带特征位的索引表


当一个串的存储空间不超过一个指针的存储空间时,可以直接将该串存在索引项的指针域,这样即节约了存储空间,又提高查找速度,但这时要加一个特征位tag 以指出指针域存放的是指针还是串。
如图4.6 所示,索引项的结点类型为:
typedef struct
{ char name[MAXNAME];
int tag; /*特征位*/
union /*起始地址或串值*/
{char *stradr;
char value[4];
}uval;
} TNode;

4.4 串的堆存储结构—堆存储结构

在应用程序中,参与运算的串变量之间的长度相差较大,并且操作中串值的长度变化也较大,因此为串变量预分配固定大小的空间不尽合理。堆存储结构的基本思想是:在内存中开辟能存储足够多的串、地址连续的存储空间作为应用程序中所有串的可利用存储空间,称为堆空间,如设store[SMAX+1]; 根据每个串的长度,动态的为每个串在堆空间里申请相应大小的存储区域,这个串顺序存储在所申请的存储区域中,当操作过程中若原空间不够了,可以根据串的实际长度重新申请,拷贝原串值后再释放原空间。

如图4.8 所示,是一个堆结构示意图。阴影部分是已经为存在的串分配过的,free 为未分配部分的起始地址,每当向store 中存放一个串时,要填上该串的索引项。

图4.8 堆结构示意图

4.5 串的堆存储结构—基于堆结构的基本运算

 堆结构上的串运算仍然基于字符序列的复制进行,基本思想是:当需要产生一个新串时,要判断堆空间中是否还有存储空间,若有,则从free 指针开始划出相应大小的区域为该串的存储区,然后根据运算求出串值,最后建立该串存储映象索引信息,并修改free 指针。

设堆空间为: char store[SMAX+1];
自由区指针:int free;

串的存储映象类型如下:
typedef struct
{ int length; /*串长*/
int stradr; /*起始地址*/
} HString;

1. 串常量赋值

void StrAssign(HString *s1,char *s2)
/*将一个字符型数组s2 中的字符串送入堆store 中,free 是自由区的指针*/
{ int i=0,len;
len=StrLength(s2);
if (len<0||free+len-1>SMAX)
return 0;
else {for (i=0;i<len;i++)
store[frre+i] =s2[i];
s1.stradr=free;
s1.len.=len;
free=free+len;
}
}
算法4.7

2. 赋值一个串

void StrCopy(Hstring *s1,Hstring s2)
/*该运算将堆store 中的一个串s2 复制到一个新串s1 中*/
{ int i;
if (free+s2.lengt-1>SMAX) return error ;
else { for(i=0; i<s2.length;i++)
store[free+i]=store[s2.atradr+i];
s1->length=s2.length;
s1->stradr=free;
free=free+s2.length;
}
}
算法4.8

3. 求子串

void StrSub(Hstring *t, Hstring s,int i,int len)
/*该运算将串s 中第i 个字符开始的长度为len 的子串送到一个新串t 中*/
{ int i;
if (i<0 || len<0 || len>s.len-i+1) return error ;
else { t->length=len;
t->stradr=s.stradr+i-1;
}
}
算法4.9

3. 串联接

void Concat(s1,s2,s)
HString s1,s2;
HString *s;
{ HString t;
StrCopy (s,s1);
StrCopy (&t,s2);
s->length=s1.length+s2.length;
}
算法4.10

以上堆空间和算法是由算法编写者自己设计和编写来实现的,在这里,重点介这种存储的处理思想,很多问题及细节尚未涉及,比如,废弃串的回归、自由区的管理问题等等。在常用的高级语言及开发环境中,大多系统本身都提供了串的类型及大量的库函数,用户可直接使用,这样会使算法的设计和调试更方便容易,可靠性更高。

展开阅读全文
打赏
0
0 收藏
分享
加载中
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部