一、串的定义
1、串的定义:串(string)是由零个或多个字符组成的有限序列,又名叫字符串。一般记为s="a1a2.......an"(n>=0),其中s是串的名称,用双引号(有些书中是用单引号)括起来的字符序列是串的值,注意引号不属于串的内容。ai(1<=i<=n)可以是字母、数字或其他字符,i就是该字符在串中的位置。串中的字符数目n称为串的长度,定义中的“有限”指的是n是一个有限的数值。零个字符的串称为空串,它的长度为0,可以直接用两双引号表示,也可以用希腊字母“Φ”来表示。所谓的序列说明串的相邻字符之间具有前驱和后继的关系。
2、空格串:是只包含空格的串,注意与空串的区别,空格串是有内容有长度的,而且可以不止一个空格。
3、子串与主串:串中任意个数的连续字符组成的子序列称为该串的子串,相应地,包含子串的串称为主串。
4、子串在主串中的位置就是子串的第一个字符在主串中的序号。
5、串的逻辑结构与线性表类似,但是不同之处在于串中元素仅由一个字符组成。
二、串的比较
1、串在计算机中的大小取决于它们挨个字母的前后顺序,事实上,串的比较是通过组成串的字符之间的编码来进行的,而字符的编码指的是字符在对应字符集中的序号。
2、常用的字符是使用标准的ASCⅡ编码,是由7位二进制数表示一个字符,总共可以表示128个字符;后来扩展ASCⅡ码由8位二进制数表示一个字符,总共可以表示256个字符;后来有了Unicode编码,常用的是由16位的二进制数表示一个字符,总共可以表示约6.5万多个字符;为了和ASCⅡ码兼容,Unicode的前256个字符与ASCⅡ码完全相同。
3、两个串相等必须是它们串的长度以及它们各个对应位置的字符都相等时才算是相等。
2、判断两个串不相等的定义:给定两个串:s="a1a2......an",t="b1b2......bm",当满足以下条件之一时,s<t:
1)n<m,且ai=bi(i=1,2,......,n)。比如hap<happy。
2)存在某个k<=min(m, n),使得ai=bi(i=1,2,.......,k-1),ak<bk。比如s="happen",t="happy",两串的前4个字母都相同,而两串第5个字母(K值),字母e<y,所以s<t。
三、串的顺序存储结构
1、串的顺序存储结构是用一组地址连续的存储单元来存储串中的字符序列的。按照预定义的大小,为每个定义的串变量分配一个固定长度的存储区,一般是用定长数组来定义,所以存在一个预定义的最大串长度,一般可以将实际的串长度值保存在数组的0下标位置,也有的会定义存储在数组的最后一个下标位置,也有的会规定在串值后面加一个不计入串长度的结束标记字符,比如C语言的“\0”来表示串值的终结。如下图,此时要知道串的长度就需要遍历计算一下了。
2、上面的串的顺序存储方式有点问题就是某些字符串的操作,比如新串的插入等操作都有可能使得串序列的长度超过了数组的长度MaxSize。所以对于串的顺序存储,串值的存储空间可在程序执行过程中动态分配而得。
四、串的链式存储结构
1、串的链式存储结构与线性表的链式存储结构类似,但是由于串的数据元素是一个字符,如果应用链表存储串值,一个结点对应一个字符就会造成很大的空间浪费,所以一个结点可以存放一个字符,也可以考虑存放多个字符,最后一个结点若是未被占满时,可以用“#”或其他非串值字符补全,如下图。
2、每个结点存放多少个字符才合适是很重要的,因为这直接影响着串处理的效率,应该根据实际情况做出选择。
3、串的链式存储结构除了在连接串与串操作时有一定的方便,总的来说不如顺序存储灵活,性能也没顺序存储好。
五、朴素的模式匹配算法
1、子串在主串中的定位操作通常称做串的模式匹配。
2、算法思路:对主串的每一个字符作为子串开头,与要匹配的字符串进行匹配。对主串做大循环,每个字符开头做T的长度的小循环,直到匹配成功或全部遍历完成位置。假设主串S和要匹配的子串T的长度存在S[0]和T[0]中,实现代码如下:
/* 返回子串T在主串S中第pos个字符开始首次出现的位置。若不存在,则函数返回值为0。 */
/* 其中,T非空,1≤pos≤StrLength(S)。 */
int Index(String S, String T, int pos)
{
if(pos < 1 || pos > S[0])
{
return ERROR;
}
int i = pos; /* i用于主串S中当前位置下标值,若pos不为1,则从pos位置开始匹配 */
int j = 1; /* j用于子串T中当前位置下标值 */
while (i <= S[0] && j <= T[0]) /* 若i小于S的长度并且j小于T的长度时,循环继续 */
{
if (S[i] == T[j]) /* 两字母相等则继续 */
{
++i;
++j;
}
else /* 指针后退重新开始匹配 */
{
i = i - j + 2; /* i退回到上次匹配首位的下一位 */
j = 1; /* j退回到子串T的首位 */
}
}
if (j > T[0])
{
return i - T[0];
}
else
{
return 0;
}
}
3、时间复杂度分析:最好的情况是一开始就匹配成功,如“goodabc”中去匹配“good”,时间复杂度为O(1);稍差一些的情况是每次都是首字母就不匹配,则对T串的循环就必须进行了,如“abcdegood”中去匹配“good”,则时间复杂度为O(n+m),其中n为主串长度,m为要匹配的子串长度。根据等概率原则,所以这两种情况下的平均时间复杂度为O(n+m)。最坏的情况就是每次不成功的匹配都发生在串T的最后一个字符,在匹配时,每次都得将T中的字符循环到最后一位才发现根本不匹配,如下图。
所以此时的时间复杂度为O((n-m+1)*m)。如下图。
4、此算法太低效。
六、KMP模式匹配算法
1、KMP模式匹配算法原理: