串
串的定义:
串(String)是计算机信息处理中最常见的一种数据结构,是有限的字符序列,记作
S = “a1a2a3…an”
其中S是串名,双引号之间的字符序列为串的值,双引号是串的分界符,并不是串的成分。其中“ai(1<=i<n)”是程序设计语言的字符集中的字符,称之为串元素;n表示串的长度,当n=0时表示空串(Null String)。在计算机程序语言中串是一种特殊的线性表,其存储表示和线性表类似但又不完全相同。串的存储方式取决于将要对串所进行的操作。
例如: 假设有一个字符串
str = “Hello World!”
则str为串名引号中括起来的部分为串值,串值由10个字母字符、一个空格字符、一个感叹号字符组成,串长为12。
空串、空格串和子串
空串: 由零个字符组成的串,通常为两个相邻的双引号来表示空串,例如:s = “”,这两个双引号之间没有任何字符,故s表示一个空串,空串的长度为0。
空格串: 表示仅由空格字符(ASICII编码为32)组成的串通常在两个双引号之间包含一定的空格字符,例如:s = “ ”,这两个双引号之间存在有一些空格字符,故表示空格串。
子串: 串中任意个连续的字符组成的序列称为该串的子串。
注: 若串中存在空格,空格计算在串的长度中,例如:s = “I Love Programming”,则串s的长度为18。
在C语言中,使用双引号引起来的一个和单引号引起来的单个字符是存在区别的,例如:s1 = “A” 和s2 = 'A’两者是存在区别的,s1表示字符串而s2表示单个字符。
串在计算机中的三种存储方式:
- 定长顺序存储方式: 将串定义成字符数组,利用 串名可以直接访问串值。用这种表示方式,串的存 储空间在编译时确定,其大小不能改变。
- 堆分配存储方式: 仍然用一组地址连续的存储单 元来依次存储串中的字符序列,但串的存储空间是 在程序运行时根据串的实际长度动态分配的。
- 块链存储方式: 是一种链式存储结构表示。
字符串匹配算法
字符串模式匹配(Pattern Matching)又称为子串定位运算。
在模式匹配中,一般将主串称为目标串,子串称为模式串。模式匹配成功是指在目标串中找到模式串;匹配不成功是指目标串中不存在模式串。模式匹配经常应用于各类文本编辑程序中对某一特定字符的查找,这样可以大大提高文本编辑程序的响应速度,响应性能。
一、BF算法(Brute Force)
即暴力(Brute Force)算法,是一种普通的模式匹配算法,BF算法的思想就是将目标串S的第一个字符与模式串T的第一个字符进行匹配,若相等,则继续比较S的第二个字符和 T的第二个字符;若不相等,则比较S的第二个字符和T的第一个字符,依次比较下去,直到得出最后的匹配结果。BF算法是一种蛮力算法,BF算法的时间复杂度为 O(n*m)(n是文本的长度,m是模式字符串的长度)。
BF算法实现
/****************************************
* 函数名称:BFIndex(ElemType *s, ElemType *t)
* 功能描述:BF匹配算法主体
* 传入参数:ElemType *s 传入的主串
* ElemType *t 传入的模式串
* 返回值:若匹配完成返回模式串在主串的那个位
* 置(主串中模式串首所在的下标位置)
* 若未匹配到则返回-1
*****************************************/
int BFIndex(ElemType *s, ElemType *t)
{
int i = 0;/*主串当前位置下标*/
int j = 0;/*模式串当前位置下标*/
int slen,tlen;
slen = GetStringLen(s);
tlen = GetStringLen(t);
while (i < slen && j < tlen)
{
if(s[i] == t[j])
{
i++;/*相等,主串和模式串的下标整体后移一位*/
j++;
}
else
{
i = i-j+1;/*不相等,主串后移,模式串返回串首*/
j = 0;
}
}
if(j == tlen)
{
return i - tlen;
}
else
{
return -1;
}
}
BF算法在主串和子串匹配失败时,主串进行的回溯操作会影响整体匹配的效率,回溯之后,主串与子串有些部分比较是没有必要的。这种简单的丢弃前面的匹配信息是BF算法之所以效率低下的一个重要原因。
二、KMP算法(Knuth-Morris-Pratt)
Knuth-Morris-Pratt 算法简称为 “KMP 算法”,常用于在一个文本串 S 内查找一个模式串 P 的出现位置,这个算法由 Donald Knuth、Vaughan Pratt、James H. Morris 三人于 1977 年联合发表,故取这三人的姓氏命名此算法,KMP算法的时间复杂度为 O(n+m)(n是文本的长度,m是模式字符串的长度)。
其中在KMP算法中需要认真理解的就是其中的匹配过程到程序编写的转化,只有多次试验以及思考才能知道其中的含义!
下面是KMP算法的具体实现:
1. 获取字符串长度
/*************************************
* 函数名称:GetStringLen(char *s)
* 功能描述:获取字符串长度
* 传入参数:待获取长度的字符串
* 返回值:字符串长度
*************************************/
int GetStringLen(char *s)
{
int len=0;
while (s[len] != 0)
{
len++;
}
return len;
}
KMP算法的next[ ]数组求取方法:
next[ ]数组的作用: 当模式串发生失配时,指导文本串将以哪个字符与模式串进行比较。
S | d | e | d | e | d | d | d | d | e |
---|---|---|---|---|---|---|---|---|---|
下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
next[ ] | -1 | 0 | 0 | 1 | 2 | 3 | 0 | 0 | 0 |
- 假设现在文本串S匹配到 i 位置,模式串P匹配到 j 位置,如果j = -1,或者当前字符匹配成功(即S[ i ] == P[ j ]),都令i++,j++,继续匹配下一个字符;
- 如果j != -1,且当前字符匹配失败(即S[ i ] != P[ j ]),则令 i 不变,j = next[ j ]。这就意味着失配时,模式串P相对于文本串S向右移动了j - next [ j ] 位。
- 换言之,当匹配失败时,模式串向右移动的位数为:失配字符所在位置 - 失配字符对应的next 值,即移动的实际位数为:j - next[ j ],且此值大于等于1。
2. next[ ]数组获取
/*************************************
* 函数名称:GetKMPnext(ElemType *p, int next[])
* 功能描述:求KMP算法的next[]
* 传入参数:ElemType *p 传入模式字符串
* int next[] KMP算法的next[]
* 返回值:空
*************************************/
void GetKMPnext(ElemType *p, int next[])
{
int plen = GetStringLen(p);
next[0] = 0;
int i = -1;/*固定next[0] = -1*/
int j;/*后缀*/
while (j < plen)
{
/*通过寻找最长前缀后缀的方法确定next[]*/
/*假设现在文本串S匹配到 i 位置,模式串P匹配到 j 位置,如果j = -1,或者当前字符匹配成功(即S[i] == P[j]),都令i++,j++,继续匹配下一个字符;
如果j != -1,且当前字符匹配失败(即S[i] != P[j]),则令 i 不变,j = next[j]。这就意味着失配时,模式串P相对于文本串S向右移动了j - next [j] 位。
换言之,当匹配失败时,模式串向右移动的位数为:失配字符所在位置 - 失配字符对应的next 值,即移动的实际位数为:j - next[j],且此值大于等于1。*/
if(-1 == i || p[i] == p[j])
{
++i;
++j;
next[j] = i;
}
else
{
i = next[i];
}
}
}
3. KMP算法实现
/****************************************
* 函数名称:KMPIndex(ElemType *s, ElemType *t)
* 功能描述:KMP匹配算法主体
* 传入参数:ElemType *s 传入的主串
* ElemType *t 传入的模式串
* 返回值:若匹配完成返回模式串在主串的那个位
* 置(主串中模式串首所在的下标位置)
* 若未匹配到则返回-1
*****************************************/
int KMPIndex(ElemType *s, ElemType *t)
{
int i = 0;
int j = 0;
int next[255];
int sLen = GetStringLen(s);/*获取字符串长度*/
int tLen = GetStringLen(t);
GetKMPnext(s,next);/*获取next[]数组*/
while (i < sLen && j < tLen)
{
if(-1 == i || s[i] == s[j])
{
i++;
j++;
}
else
{
j =next[j];/*找到下一次匹配的j值*/
}
}
if(j == tLen)/*匹配完成*/
{
return i-j;/*返回相匹配位置第一个字符下标*/
}
else
{
return -1;
}
}
😊
o_o …
(⊙o⊙)
🎈