前言
本专栏到这期为止,已经依次介绍了顺序表、链表、数组、栈、队列和二叉树这些数据结构的存储方式以及重要的操作实现。接下来,本期将会讲解字符串的存储方式以及应用:字符串的模式匹配算法
正文
1.字符串
字符串简称串,是由零个或多个字符组成的有限序列。一般记为:
- S = ‘A1A2A3…An’ (n>=0)
子串: 串中任意多个相连的字符组成的子序列称为该串的子串。
主串: 包含子串的串称为该字串的主串。
2.存储结构
串的存储结构可以用三种方式表示:
- 定长顺序存储表示 (类似定长数组)
- 堆分配存储表示 (动态申请)
- 块链存储表示 (类似链式存储)
3.串的模式匹配
子串的定位操作通常称为串的模式匹配,它求的是子串(也称为模式串)在主串中的位置。
//定长顺序存储结构
typedef struct SString{
int length;
char ch[255];
SString(int len,char c[]){
length = len;
int i = 0;
while(c[i] != '\0')
{
ch[i] = c[i];
i++;
}
}
};
a.简单的模式匹配算法
思想:暴力求解,通过循环比对两个串的元素。若在主串中找到完整子串,返回子串初始下标。反之,返回-1。
最坏时间复杂度为:O(mn) m,n为两个串的长度。
int MyString::IndexSubstr(SString string1, SString string2)
{
if(string1.length <=0)
{
return -1;
}
int i = 0,j = 0;
while(i<string1.length && j<string2.length)
{
char _ch1 = string1.ch[i];
char _ch2 = string2.ch[j];
if(_ch1 != _ch2)
{
i ++;
j = 0;
}
else
{
i ++;
j ++;
if(j == string2.length)
{
return i-string2.length;
}
}
}
return -1;
}
b.KMP串的模式匹配算法
思想:相对于朴素的模式匹配算法,KMP算法优化了当模式串匹配失败时的处理。
朴素的模式匹配算法中,若字串匹配失败,直接将j的指针退回字串的首个元素位置。此时,并未考虑子串中存在重复前缀的情况。
//计算模式串失配后,应当跳转比较元素下标
std::vector<int> MyString::computeNext(SString pattern) {
int m = pattern.length;
std::vector<int> next(m, 0);
int i = 1;
int j = 0;
while (i < m) {
if (pattern.ch[i] == pattern.ch[j]) {
j++;
next[i] = j;
i++;
} else {
if (j != 0) {
j = next[j - 1];
} else {
next[i] = 0;
i++;
}
}
}
return next;
}
//KMP算法,将所有与模式串匹配的下标存储动态数组
std::vector<int> MyString::KMP(SString text, SString pattern) {
std::vector<int> next = computeNext(pattern);
std::vector<int> matches;
int n = text.length;
int m = pattern.length;
int i = 0;
int j = 0;
while (i < n) {
if (pattern.ch[j] == text.ch[i]) {
i++;
j++;
}
if (j == m) {
matches.push_back(i - j);
j = next[j - 1];
} else if (i < n && pattern.ch[j] != text.ch[i]) {
if (j != 0) {
j = next[j - 1];
} else {
i++;
}
}
}
return matches;
}
c.KMP算法的优化
针对模式串中出现的连续重复元素,KMP算法中仍然出现对不必要元素的重复访问。对此,我们可以进一步继续优化KMP算法。
后续更新。。。