数据结构 串

一卷旌收千骑虏,万全身出百重围。这篇文章主要讲述数据结构 串相关的知识,希望能为你提供帮助。

一、串的定义
二、串的存储结构
1、定长顺序存储表示
2、堆分配存储表示
3、块链存储表示
三、串的基本操作
四、串的模式匹配(重点)
1、简单的模式匹配算法
2、KMP算法

一、串的定义
串( string)是由零个或多个字符组成的有限序列,又名叫字符串。

S是串名,单引号括起来的字符序列是串的值;
  可以是字母、数字或其他字符; 串中字符的个数n nn称为串的长度。
另外还有一些其它概念:
  • 空串:n = 0 时的串称为空串。
  • 空格串:是只包含空格的串。注意它与空串的区别,空格串是有内容有长度的,而且可以不止一个空格。
  • 子串与主串:串中任意个数的连续字符组成的子序列称为该串的子串,相应地,包含子串的串称为主串。
  • 子串在主串中的位置就是子串的第一个字符在主串中的序号。
串的逻辑结构和线性表极为相似,区别仅在于串的数据对象限定为字符集。在基本操作上,串和线性表有很大差别。线性表的基本操作主要以单个元素作为操作对象,如查找、插入或删除某个元素等; 而串的基本操作通常以子串作为操作对象,如查找、插入或删除一个子串等。


二、串的存储结构

1、定长顺序存储表示
类似于线性表的顺序存储结构,用一组地址连续的存储单元存储串值的字符序列。在串的定长顺序存储结构中,为每个串变量分配一个固定长度的存储区,即定长数组。
#define MAXLEN 255//预定义最大串长为255
typedef struct
char ch[MAXLEN]; //每个分量存储一个字符
int length; //串的实际长度
SString;

串的实际长度只能小于等于MAXLEN,超过预定义长度的串值会被舍去,称为截断。串长有两种表示方法: 一是如上述定义描述的那样,用一个额外的变量len来存放串的长度; 二是在串值后面加一一个不计入串长的结束标记字符“\\0”,此时的串长为隐含值。
在一些串的操作(如插入、联接等)中,若串值序列的长度超过上界MAXLEN,约定用“截断”法处理,要克服这种弊端,只能不限定串长的最大长度,即采用动态分配的方式。


2、堆分配存储表示
堆分配存储表示仍然以一组地址连续的存储单元存放串值的字符序列,但它们的存储空间是在程序执行过程中动态分配得到的。
typedef struct
char *ch; //按串长分配存储区,ch指向串的基地址
int length; //串的长度
HString;

在C语言中,存在一一个称之为“堆”的自由存储区,并用malloc()和free()函数来完成动则返回一个指向起始地址的指针,作为串的基地址,这个串由ch指针来指示; 若分配失败,则返回NULL。已分配的空间可用free()释放掉。
上述两种存储表示通常为高级程序设计语言所采用。块链存储表示仅做简单介绍。


3、块链存储表示
类似于线性表的链式存储结构,也可采用链表方式存储串值。由于串的特殊性(每个元素只有一个字符),在具体实现时,每个结点既可以存放一个字符, 也可以存放多个字符。每个结点称为块,整个链表称为块链结构。图(a)是结点大小为4 (即每个结点存放4个字符)的链表,最后一个结点占不满时通常用“#”补上; 图(b)是结点大小为1的链表。





三、串的基本操作
  1. StrAssign(& T, chars): 赋值操作。把串T赋值为 chars
  2. Strcopy(& T, S): 复制操作。由串S复制得到串T。
  3. StrEmpty(S): 判空操作。若S为空串,则返回TRUE,否则返回 FALSE
  4. StrCompare(S,T): 比较操作。若S> T,则返回值> 0; 若S=T,则返回值=0; 若S< T,则返回值< 0。
  5. StrEngth(S): 求串长。返回串S的元素个数
  6. Substring(& Sub,S,pos,1en):求子串。用Sub返回串S的第pos个字符起长度为len的子串。
  7. Concat(& T,S1,S2): 串联接。用T返回由S1和S2联接而成的新串。
  8. Index(S,T): 定位操作。若主串S中存在与串T值相同的子串,则返回它在主串S中第一次出现的位置; 否则函数值为0
  9. Clearstring(& S): 清空操作。将S清为空串
  10. Destroystring(& S): 销毁串。将串S销毁
  11. 不同的高级语言对串的基本操作集可以有不同的定义方法。在上述定义的操作中,串赋值StrAssign、串比较 StrCompare、求串长 Strength、串联接 Concat及求子串 Substring五种操作构成串类型的最小操作子集,即这些操作不可能利用其他串操作来实现; 反之,其他串操作(除串清除 Clearstring和串销毁 Destroystring外)均可在该最小操作子集上实现。
  12. 例如,可利用判等、求串长和求子串等操作实现定位函数 Index(S,T)。
int Index(Sring S, String T)
int i = 1, n = StrLength(S), m = StrLength(T);
String sub;
while(i < = n-m+1)
SubString(sub, S, i, m); //取主串第i个位置,长度为m的串给sub
if(StrCompare(sub, T) != 0)
++i;
else
return i; //返回子串在主串中的位置


return 0; //S中不存在与T相等的子串


四、串的模式匹配(重点)1、简单的模式匹配算法
子串的定位操作通常称为串的模式匹配,它求的是子串(常称模式串)在主串中的位置。这里采用定长顺序存储结构,给出一种不依赖于其他串操作的暴力匹配算法。
int Index(SString S, SString T)
int i = 1, j = 1;
while(i < = S.length & & j < = T.length)
if(S.ch[i] == T.ch[j])
++i; ++j; //继续比较后继字符
else
//指针后退重新开始匹配
i = i-j+2;
j = 1;


if(j > T.length)
return i - T.length;
else
return 0;






2、KMP算法
在上面的简单匹配中,每趟匹配失败都是模式后移一位再从头开始比较。而某趟已匹配相等的字符序列是模式的某个前缀,这种频繁的重复比较相当于模式串在不断地进行自我比较,这就是其低效率的根源。
因此,可以从分析模式本身的结构着手,如果已匹配相等的前缀序列中有某个后缀正好是模式的前缀,那么就可以将模式向后滑动到与这些相等字符对齐的位置,主串i指针无须回溯,并继续从该位置开始进行比较。而模式向后滑动位数的计算仅与模式本身的结构有关,与主串无关。
KMP算法的特点就是:仅仅后移模式串,比较指针不回溯。很是牛掰。
????字符串的前缀、后缀和最大公共前后缀长度要了解子串的结构,首先要弄清楚几个概念:前缀、后缀和部分匹配值。前缀指除最后一个字符以外,字符串的所有头部子串; 后缀指除第一个字符外,字符串的所有尾部子串; 部分匹配值则为字符串的前缀和后缀的最大公共前后缀长度。下面以′ a b a b a ′ababa





对算法的改进方法使用部分匹配值时,每当匹配失败,就去找它前一个元素的部分匹配值,这样使用起来有些不方便,所以将PM表右移一位,这样哪个元素匹配失败,直接看它自己的部分匹配值即可。
void get_next(String T, int *next)
int i = 1, j = 0;
next[1] = 0;
while (i < T.length)
if(j==0 || T.ch[i]==T.ch[j]) //ch[i]表示后缀的单个字符,ch[j]表示前缀的单个字符
++i; ++j;
next[i] = j; //若pi = pj, 则next[j+1] = next[j] + 1
else
j = next[j]; //否则令j = next[j],j值回溯,循环继续




与next数组的求解相比,KMP算法就简单许多,和简单模式匹配算法很相似:
int Index_KMP(String S, String T)
int i=1, j=1;
int next[255]; //定义next数组
get_next(T, next); //得到next数组
while(i< =S.length & & j< =T.length)
if(j==0 || S.ch[i] == T.ch[j]) //字符相等则继续
++i; ++j;
else
j = next[j]; //模式串向右移动,i不变


if(j> T.length)
return i-T.length; //匹配成功
else
return 0;



KMP算法的进一步优化
void get_nextval(String T, int *nextval)
int i = 1, j = 0;
nextval[1] = 0;
while (i < T.length)
if(j==0 || T.ch[i]==T.ch[j]) //ch[i]表示后缀的单个字符,ch[j]表示前缀的单个字符
++i; ++j;

if(T.ch[i] != T.ch[j]) //若当前字符与前缀字符不同
nextval[i] = j; //则当前的j为nextval在i位置的值
else
//如果与前缀字符相同
//则将前缀字符的nextval值给nextval在i位置上的值
nextval[i] = nextval[j];

else
j = nextval[j]; //否则令j = next[j],j值回溯,循环继续




总结改进过的KMP算法,它是在计算出next值的同时,如果a位字符与它next值指向的b位字符相等,则该a位的nextval就指向b位的nextval值,如果不等,则该a位的nextval值就是它自己a位的next的值。这块逻辑很简单,有了next数组,我们很容易就能推导出它的nextval数组了。






【数据结构 串】


    推荐阅读