串是由零个或多个字符组成的有限序列,又名字符串。一般记为s="a1a2......an"(n≥0),其中,s是串的名称,用双引号括起来的字符序列是串的值。串中的字符数目n称为串的长度。零个字符的串称为空串,其长度为0。所谓的序列,说明串的相邻字符之间具有前驱和后继的关系。
字符串匹配即查找待匹配字符串(模式串)p在主串s中的位置。一般处理这种问题往往采用简单粗暴的方法——暴力匹配法。所谓暴力匹配法,就是对主串s的每一个字符与要匹配的字符串p的每个字符进行逐一匹配。
i控制主串s的循环,j控制模式串p的循环。返回模式串p在主串s中的位置。
如果当前字符匹配成功时,即s[i]==p[j]时,执行i++和j++操作,进行下一个字符的比较;
如果当前字符匹配失败,即s[i]!=p[j],执行i=i-j+1操作,并将j置为0。通俗来讲就是当匹配失败时,让i回溯到匹配成功时的下一个,让j回复到模式串p的开始位置。
例:主串s="abcdeabcdxabc",模式串p="abcdx"。结果应该返回5。
过程如下:首先,i=0,j=0,很显然s[i]与p[j]相等,则i++,j++,即i=1,j=1,此时s[i]仍然与p[j]相等,则继续执行i++,j++,直到当i=4,j=4时,s[i]与p[j]不再相等,即当前字符匹配失败,此时执行i=i-j+1操作,即此时i回复到s[1]的位置,并把j置为0,因为当i=4时,匹配失败,此时模式串需要从头匹配。然后接下来继续判断s[i]与p[j]是否相等,直到匹配成功或全部遍历完成。可以发现,当i=5时,s[i]与p[j]相等,即从i=5开始,模式串p与主串s开始匹配,直到i=9,j=4时,主串与模式串完全匹配成功,此时返回i-j,即5。
相关代码如下:
int ViolentMatch(char s[], char p[], int pos)
{
int lens = strlen(s);
int lenp = strlen(p);
int i = pos; //pos为用户自定义的从主串pos位置开始匹配
int j = 0;
while (i<lens && j<lenp)
{
//如果匹配成功
if (s[i] == p[j])
{
i++;
j++;
}
//如果匹配失败
else
{
i = i - j + 1;
j = 0;
}
}
if (j < lenp)
{
return -1;
}
else
{
return i-j;
}
}
主函数为:
int main()
{
char* s = "abcdeabcdxabcd";
char* p = "abcdx";
int pos = ViolentMatch(s, p, 0);
//匹配到所有子串的位置
while (pos != -1)
{
printf("%d\n", pos);
pos = ViolentMatch(s, p, pos+1);
}
return 0;
}
除此之外,还有另一种匹配字符串的经典算法——KMP。
此算法可以大大避免重复遍历的情况。操作方法和暴力匹配法的操作方法大致相同:
当主串匹配到i位置,模式串匹配到j位置时,当s[i]==p[j]时,执行i++,j++操作,当s[i]!=p[j]时,i不用回溯到i-j+1的位置,i保持不变,令j=next[j],意味着匹配失败时,模式串p相对于主串s向右移动了j - next[j] 位。而数组next[]是什么呢?数组next[]中各个值的含义是:模式串中当前字符之前的字符串中,有多大长度的相同前缀后缀。要知道next[]数组中存储内容,需要先求出模式串p中各子串的最大前缀后缀公共元素长度,将其存放在数组prefixtable[](前缀匹配表)中。
例:主串s="abcdeabcdxabc",模式串p="abcdx"。
模式串p的最大前缀后缀公共元素长度表为:
模式串中各个子串 | 前缀 | 后缀 | 最大公共元素长度 |
a | 无 | 无 | 0 |
ab | a | b | 0 |
abc | a,ab | c,bc | 0 |
abcd | a,ab,abc | d,cd,bcd | 0 |
abcdx | a,ab,abc,abcd | x,dx,cdx,bcdx |
|
此操作之后可知道prefixtable数组中的内容是{0,0,0,0,0}。而由于next[]中存放的是当前字符之前的字符串的最大前缀后缀公共元素长度,所以需要将数组prefixtable[]中各值向后移动一位,然后将prefixtable[0]强置为-1。此时的数组则为最终的next[]数组。相关函数操作如下:
//求最大前缀后缀公共元素长度
void PreFixTable(char p[], int lenp, int prefixtable[])
{
prefixtable[0] = 0;
int len = 0;
int i = 1;
while (i < lenp)
{
if (p[i] == p[len])
{
len++;
prefixtable[i] = len;
i++;
}
else
{
if (len>0)
{
len = prefixtable[len - 1];
}
else
{
prefixtable[i] = len;
i++;
}
}
}
}
//后移,首元素置为-1
void MovePreFixTable(int prefixtable[], int n)
{
for (int i = n - 1; i > 0; i--)
{
prefixtable[i] = prefixtable[i - 1];
}
prefixtable[0] = -1;
}
关于prefixtable[]数组的求解思想及算法,请访问https://blog.csdn.net/v_july_v/article/details/7041827/
里面有关于KMP算法的更详尽的介绍。
总结一下:要采用KMP算法来进行字符串的匹配,则首先需要得到模式串中各个子串的最大前缀后缀公共元素长度,存在数组prefixtable[]中,其次需要将该数组的各个元素后移一位,将首元素强置为0,之后得到的新数组即next[]数组。然后就可以调用下面的函数进行匹配:
//KMP匹配算法
void KMPMatch(char s[], char p[], int lens, int lenp)
{
int* prefixtable = (int*)malloc(lenp*sizeof(int));
PreFixTable(p, lenp, prefixtable);
MovePreFixTable(prefixtable, lenp);
int i = 0;
int j = 0;
while (i < lens)
{
if (j == lenp - 1 && s[i] == p[j])
{
printf("%d\n", i - j);
j = prefixtable[j];
}
if (s[i] == p[j])
{
i++;
j++;
}
else
{
j = prefixtable[j];
if (j == -1)
{
i++;
j++;
}
}
}
}
主函数:
int main()
{
char* s = "abcdeabcdxabcd";
char* p = "abcdx";
int lens = strlen(s);
int lenp = strlen(p);
KMPMatch(s, p, lens, lenp);
return 0;
}
以上即为我对字符串匹配问题两种方法的理解~~~