目录
1. 串
1.1 定义
串用数组表示,由 0 或 多个字符组成
1.2 串的长度 VS 数组长度(易混淆!)
strlen(str) = 串str包含字符的个数
串的长度 = strlen(str)
数组长度 = strlen(str) + 1,1 指数组结尾 - 看不见的 ‘\0’
空格也是单个字符
多个空格组成的串 叫 ‘空格串’ ,不等于‘空串’
逻辑上:
串 是限定元素为字符的线性表
操作上不同:
串:针对串内的子串
线性表:针对某一元素
1.2 存储结构
定长 与 变长:
变长用的更广泛
typedef struct //定长
{
char str[max + 1];
int length;
}Str1;
typedef struct // 变长
{
char *ch;
int length;
}Str2;
1.3 基本操作
1赋值 + 2串长 + 3串比较 + 4串连接 + 5求子串
以变长存储结构为例
#include<bits/stdc++.h>
#define max 100
using namespace std;
typedef struct // 变长
{
char *ch;
int length;
}Str;
void strsign(Str& str, char *ch)
{
if(str.ch)
str.ch = NULL;
int len = 0;
int i;
char *c = ch;
while(*c)
{
++len;
c++;
}
if(len == 0)
{
str.ch = NULL;
str.length = 0;
cout << "为空串!" << endl << endl;
}
else
{
str.ch = (char*)malloc(sizeof(char)*(len + 1));
if(str.ch == NULL)
{
cout << "分配空间失败!" << endl <<endl;
}
else
{
c = ch;
for(i = 0; i <= len; i++, c++)
str.ch[i] = *c;
str.length = len;
cout << "赋值完成!" << endl;
}
}
}
int strcompare(Str s1, Str s2)
{
int i;
for(i = 0; i < s1.length && i < s2.length; i++)
{
if(s1.ch[i] != s2.ch[i])
{
cout << s1.ch[i] - s2.ch[i] << endl << endl;
return 0;
}
}
cout << s1.length - s2.length << endl << endl;
return 1;
}
void concat(Str &s, Str s1, Str s2)
{
int i, j;
if(s.ch)
s.ch = NULL;
s.ch = (char*)malloc(sizeof(char) * (s1.length + s2.length+ 1));
if(s.ch == NULL)
cout << "空间分配失败" << endl << endl;
else
{
for(i = 0; i < s1.length; i++)
{
s.ch[i] = s1.ch[i];
}
for(j = 0; j <= s2.length; j++)
{
s.ch[i+j] = s2.ch[j];
}
s.length = s1.length + s2.length + 1;
cout << "串连接完成!" << endl;
}
}
void substring(Str& str, Str str2, int pos, int len)
{
int i;
if(pos < 0 || pos >= str2.length || len > (str2.length - pos))
cout << "位置参数有误!" << endl << endl;
else
{
if(str.ch)
str.ch = NULL;
if(len == 0)
{
str.length = 0;
cout << "为空串!" << endl << endl;
}
else
{
str.ch = (char*)malloc(sizeof(char)*(len + 1));
for(i = 0; i < len; i++)
{
str.ch[i] = str2.ch[i + pos];
}
str.ch[i] = '\0';
str.length = len;
}
}
}
int main()
{
Str str, str2, str3;
int pos, len;
char *ch = "asd" ; // <=> char ch[max];
strsign(str, ch);
cout << endl;
cout << "str=" << str.ch << endl << endl;
//trace(str);
cout << "串长:" << str.length << endl << endl;
cout << "串比较:" << endl;
char *ch2 = "sdf";
strsign(str2, ch2);
cout << "str = " << str.ch << endl;
cout << "str2 = " << str2.ch << endl;
strcompare(str, str2);
cout << "串连接:" << endl;
concat(str3, str, str2);
cout << "str3 = str + str2 = " << str3.ch << endl << endl;
cout << "求子串:" << endl;
pos = 0;
len = 1;
substring(str3, str, pos, len);
cout << "str = " << str.ch << endl;
cout << "pos = " << pos << ", " << "len = " << len << endl;
cout << "str3 = " << str3.ch << endl << endl;
return 0;
}
2. 串匹配算法
2.1 简单匹配
#include<bits/stdc++.h>
#define max 100
using namespace std;
typedef struct // 变长
{
char *ch;
int length;
}Str;
void strsign(Str& str, char *ch)
{
if(str.ch)
str.ch = NULL;
int len = 0;
int i;
char *c = ch;
while(*c)
{
++len;
c++;
}
if(len == 0)
{
str.ch = NULL;
str.length = 0;
cout << "为空串!" << endl << endl;
}
else
{
str.ch = (char*)malloc(sizeof(char)*(len + 1));
if(str.ch == NULL)
{
cout << "分配空间失败!" << endl <<endl;
}
else
{
c = ch;
for(i = 0; i <= len; i++, c++)
str.ch[i] = *c;
str.length = len;
cout << "赋值完成!" << endl;
}
}
}
void index(Str s1, Str s2)
{
int i = 0, j = 0, k = i;
for(;i < s1.length && j < s2.length;)
{
if(s1.ch[i] == s2.ch[j])
{
++i;
++j;
}
else
{
j = 0;
i = ++k;
}
}
if(j >= s2.length)
cout << "位置:第 " << k+1 << " 个" << endl << endl;
else
cout << "不匹配!" << endl << endl;
}
int main()
{
Str str1, str2, str3;
char *ch = "ABABCABCACBAB" ; // <=> char ch[max];
strsign(str1, ch);
cout << endl;
cout << "str1 = " << str1.ch << endl << endl;
char *ch2 = "ABCAB";
strsign(str2, ch2);
cout << endl;
cout << "str2 = " << str2.ch << endl << endl;
cout << "串简单匹配:" << endl;
index(str1, str2);
return 0;
}
2.2 KMP
神仙 - 手算教程:https://www.bilibili.com/video/BV1tW41157tv
数组下标从 1 开始!
next[1] = 0 永恒 不变!
next[i] 就是匹配串位置 1 - (i - 1) 的 最长公共前后缀 长度
所以 next[2] = 1 也是肯定的!
2.2.1 傻瓜式求 next[] 数组
数组下标从 1 开始!
next[1] = 0,next[2] = 1 永恒 不变!
求 next[i] 原则 ( i >= 3 )
看位置 (i - 1) 的字符 和 位置 next[i - 1] 的字符是否相等
若相等,next[i] = next[i - 1] + 1
否则,
看位置 (i - 1) 和 位置为【 next[i - 1] 对应的next[] 】= next[next[i - 1]] 的字符是否相等,
若相等,next [i] = next[next[i - 1]] + 1
否则,
位置 (i - 1) 和 位置为【next[next[i - 1]] 对应的next[]】= next[next[next[i - 1]]] 的字符是否相等
直到,回溯到 位置为 1 的时候
位置 (i - 1) 和 位置为 1 的字母是否相等
若相等,next[i] = 1 + 1 = 2
否则 next[i] = 1
例子
求 ababaaababaa 的 next[] 数组
a | b | a | |
---|---|---|---|
下标位置 | 1 | 2 | 3 |
next[] | 0 | 1 |
求 第 3 个位置字符 ‘a’ 的 next[3] = ?
看它前一个字符是 ‘b’,对应的 next[2] = 1
位置是 1 的字符是 ‘a’,a ≠ b,且 j = 1,next[3] = 1
a | b | a | b | |
---|---|---|---|---|
下标位置 | 1 | 2 | 3 | 4 |
next[] | 0 | 1 | 1 |
求 第 4 个位置字符 ‘b’ 的 next[4] = ?
看它前一个字符是 ‘a’,对应的 next[3] = 1
位置是 1 的字符是 ‘a’,a = a,next[4] = 1 + 1 = 2
a | b | a | b | a | |
---|---|---|---|---|---|
下标位置 | 1 | 2 | 3 | 4 | 5 |
next[] | 0 | 1 | 1 | 2 |
求 第 5 个位置字符 ‘a’ 的 next[5] = ?
看它前一个字符是 ‘b’,对应的 next[4] = 2
位置是 2 的字符是 ‘b’,b = b,next[5] = 2 + 1 = 3
a | b | a | b | a | a | a | |
---|---|---|---|---|---|---|---|
下标位置 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
next[] | 0 | 1 | 1 | 2 | 3 | 4 |
next[6] = 4 同理,略
求 第 7 个位置字符 ‘a’ 的 next[7] = ?
看它前一个字符是 ‘a’,对应的 next[6] = 4
位置是 4 的字符是 ‘b’,a ≠ b,j ≠ 1
next[4] = 2
位置是 2 的字符是 ‘b’,a ≠ b,j ≠ 1
next[2] = 1
位置是 1 的字符是 ‘a’,a = a,next[7] = 1 + 1 = 2
以此类推…
2.2.2 KMP简析
简单匹配中,1个1个字符移动匹配,成功还好,若不匹配,又要从匹配串的头部开始,感觉太慢太麻烦!
当匹配失败,我不从头开始匹配,而是从匹配串的 某个位置开始,将这个位置用数组存起来,这就是 next[]!
例子
源自:https://www.bilibili.com/video/BV1Px411z7Yo?from=search&seid=11232141016422342714
跳位置后:
2.2.3 与简单匹配的区别
- 有 next[] 数组
- 主串的 i 不需要回溯,解释:
2.2.4 (kmp / kmp改进) 与 (下标0、1不同) next[]代码对比
kmp - 小标从 1 开始:
void getnext(Str str, int next[])
{
int length = str.length;
int i = 1, j = 0;
next[1] = 0;
while(i < length-1)
{
if(j == 0 || str.ch[i] == str.ch[j] )
{
i++;j++;
next[i] = j;
}
else
j = next[j];
}
cout << endl;
}
从 0 开始
void getnext(Str str, int next[])
{
int length = str.length;
int i = 0, j = -1;
next[0] = -1;
while(i < length)
{
if( j == -1 || str.ch[i] == str.ch[j] )
{
i++;j++;
next[i] = j;
}
else
j = next[j];
}
cout << endl;
}
kmp - 改进
从 1 起:
void getnextval(Str str, int nextval[])
{
int length = str.length;
int i = 1, j = 0;
int next[length];
nextval[1] = 0;
while(i < length-1)
{
if(j == 0 || str.ch[i] == str.ch[j] )
{
i++;j++;
if(str.ch[i] != str.ch[j])
nextval[i] = j;
else
nextval[i] = nextval[j];
}
else
j = nextval[j];
}
cout << endl;
}
从 0 起:
void getnextval(Str str, int nextval[])
{
int length = str.length;
int i = 0, j = -1;
nextval[0] = -1;
while(i < length)
{
if( j == -1 || str.ch[i] == str.ch[j] )
{
i++;j++;
if(str.ch[i] != str.ch[j])
nextval[i] = j;
else
nextval[i] = nextval[j];
}
else
j = nextval[j];
}
cout << endl;
}
去除 next[] 改进一下:
void getnextval(Str str, int nextval[])
{
int length = str.length;
int i = 0, j = 0;
nextval[0] = 0;
while(i < length)
{
if(i == 0 || ( j == 0 && str.ch[i] != str.ch[j]) )
{
i++;
if(str.ch[i] != str.ch[j])
nextval[i] = j;
else
nextval[i] = nextval[j];
}
else if( str.ch[i] == str.ch[j] )
{
i++;j++;
if(str.ch[i] != str.ch[j])
nextval[i] = j;
else
nextval[i] = nextval[j];
}
else
j = nextval[j];
}
cout << endl;
}
2.2.5 下标起始不同
从 0 起 和 从 1 起,一些细节不同:
- 字符串创建,分配的长度多1
- 判断字符串结尾不同
- j 起始不同
- 其他的就很类似了!
2.2.6 代码
下标从 1 开始!
#include<bits/stdc++.h>
#define max 100
using namespace std;
typedef struct // 变长
{
char *ch;
int length;
}Str;
void strsign(Str& str, char *ch)
{
if(str.ch)
str.ch = NULL;
int len = 0;
int i;
char *c = ch;
while(*c)
{
++len;
c++;
}
if(len == 0)
{
str.ch = NULL;
str.length = 0;
cout << "为空串!" << endl << endl;
}
else
{
str.ch = (char*)calloc((len + 2), sizeof(char));
if(str.ch == NULL)
{
cout << "分配空间失败!" << endl <<endl;
}
else
{
c = ch;
for(i = 1; i < len+2; i++, c++)
str.ch[i] = *c;
str.ch[i]='\0';
str.length = len+2;
cout << "赋值完成!" << endl;
}
}
}
void getnext(Str str, int next[])
{
int length = str.length;
int i = 1, j = 0;
next[1] = 0;
while(i < length-1)
{
if(j == 0 || str.ch[i] == str.ch[j] )
{
i++;j++;
next[i] = j;
}
else
j = next[j];
}
cout << endl;
}
void kmp(Str s1, Str s2, int next[])
{
int i = 1, j = 1;
for(;i < s1.length-1 && j < s2.length-1;)
{
if(j == 0 ||s1.ch[i] == s2.ch[j])
{
++i;
++j;
}
else
j = next[j];
}
if(j > s2.length-2)
cout << "位置下标(从1起):" << i - (s2.length-2) << endl << endl;
else
cout << "不匹配!" << endl << endl;
}
int main()
{
Str str1, str2, str3;
int next[max]={0}, i;
char *ch = "ABABCABCACBAB" ; // <=> char ch[max]; 主串
strsign(str1, ch);
cout << endl;
cout << "str1 = " << str1.ch+1 << endl << endl;
char *ch2 = "ABCAB"; // 匹配串
strsign(str2, ch2);
cout << endl;
cout << "str2 = " << str2.ch+1 << endl << endl;
cout << "KMP匹配:" << endl;
getnext(str2, next);
kmp(str1, str2, next);
system("pause");
return 0;
}
![](https://i-blog.csdnimg.cn/blog_migrate/ef941601b1dd78c78bf676acd77477cf.png)
下标从 0 开始!
#include<bits/stdc++.h>
#define max 100
using namespace std;
typedef struct // 变长
{
char *ch;
int length;
}Str;
void strsign(Str& str, char *ch)
{
if(str.ch)
str.ch = NULL;
int len = 0;
int i;
char *c = ch;
while(*c)
{
++len;
c++;
}
if(len == 0)
{
str.ch = NULL;
str.length = 0;
cout << "为空串!" << endl << endl;
}
else
{
str.ch = (char*)malloc(sizeof(char)*(len));
if(str.ch == NULL)
{
cout << "分配空间失败!" << endl <<endl;
}
else
{
c = ch;
for(i = 0; i < len; i++, c++)
str.ch[i] = *c;
str.length = len;
cout << "赋值完成!" << endl;
}
}
}
void getnext(Str str, int next[])
{
int length = str.length;
int i = 0, j = -1;
next[0] = -1;
while(i < length)
{
if( j == -1 || str.ch[i] == str.ch[j] )
{
i++;j++;
next[i] = j;
}
else
j = next[j];
}
cout << endl;
}
void kmp(Str s1, Str s2, int next[])
{
int i = 0, j = 0;
for(;i < s1.length && j < s2.length;)
{
if(j == 0 ||s1.ch[i] == s2.ch[j])
{
++i;
++j;
}
else
j = next[j];
}
if(j >= s2.length)
cout << "位置下标(从0起): " << i - s2.length << endl << endl;
else
cout << "不匹配!" << endl << endl;
}
int main()
{
Str str1, str2, str3;
int next[max]={0}, i;
char *ch = "ABABABCABBAB" ; // <=> char ch[max]; 主串 ABABCABCACBAB
strsign(str1, ch);
cout << endl;
cout << "str1 = " << str1.ch << endl << endl;
char *ch2 = "ABCAB"; // 匹配串
strsign(str2, ch2);
cout << endl;
cout << "str2 = " << str2.ch << endl << endl;
cout << "KMP匹配:" << endl;
getnext(str2, next);
kmp(str1, str2, next);
system("pause");
return 0;
}
![](https://i-blog.csdnimg.cn/blog_migrate/f3ffe60a70915b12667ffd4c479fa36d.png)
2.3 KMP改进
下标从 1 开始
在 KMP 算法中,当主串 和 匹配串 某位置不相等的时候,匹配串就往前跳到 next[i] 的字符位置,但有可能又不相等,又要往前跳到 next[ next[i] ] 的字符位置,我想一下子就跳对!
这里可以改进到 一下子就找到 相等的字符 的位置!
用 nextval[] 保存这些改进的位置
思路
边求 next[],边求 nextval[]
对于 (i-1) 每次回溯的位置,不是 next[next[i - 1]] 了,而是 nextval[next[i - 1]]了
现在求 nextval[2] = ?
位置 2 和 位置 next[2] 字符相比较
若相等,nextval[2] = nextval[1]
若不等,nextval[2] = next[2]
现在求 nextval[i] = ?(i > 1)
位置 i 和 位置 next[i] 字符相比较
若相等,nextval[i] = nextval[next[i]]
若不等,nextval[i] = next[i]
以 nextval[] 为我,next[] 是你
不相等是你,相等是我
2.3.1 代码
下标从 1 开始!
#include<bits/stdc++.h>
#define max 100
using namespace std;
typedef struct // 变长
{
char *ch;
int length;
}Str;
void strsign(Str& str, char *ch)
{
if(str.ch)
str.ch = NULL;
int len = 0;
int i;
char *c = ch;
while(*c)
{
++len;
c++;
}
if(len == 0)
{
str.ch = NULL;
str.length = 0;
cout << "为空串!" << endl << endl;
}
else
{
str.ch = (char*)calloc((len + 2), sizeof(char));
if(str.ch == NULL)
{
cout << "分配空间失败!" << endl <<endl;
}
else
{
c = ch;
for(i = 1; i < len+2; i++, c++)
str.ch[i] = *c;
str.ch[i] = '\0';
str.length = len+2;
cout << "赋值完成!" << endl;
}
}
}
void getnextval(Str str, int nextval[])
{
int length = str.length;
int i = 1, j = 0;
int next[length];
nextval[1] = 0;
while(i < length-1)
{
if(j == 0 || str.ch[i] == str.ch[j] )
{
i++;j++;
if(str.ch[i] != str.ch[j])
nextval[i] = j;
else
nextval[i] = nextval[j];
}
else
j = nextval[j];
}
cout << endl;
}
void kmp(Str s1, Str s2, int nextval[])
{
int i = 1, j = 1;
for(;i < s1.length-1 && j < s2.length-1;)
{
if(j == 0 || s1.ch[i] == s2.ch[j])
{
++i;
++j;
}
else
j = nextval[j];
}
if(j >= s2.length-1)
cout << "位置下标(从1起): " << i - (s2.length-2) << endl << endl;
else
cout << "不匹配!" << endl << endl;
}
int main()
{
Str str1, str2, str3;
int nextval[max]={0}, i;
char *ch = "ABABCABCACBAB" ; // <=> char ch[max]; 主串
strsign(str1, ch);
cout << endl;
cout << "str1 = " << str1.ch << endl << endl;
char *ch2 = "ABCAB"; // 匹配串
strsign(str2, ch2);
cout << endl;
cout << "str2 = " << str2.ch << endl << endl;
cout << "KMP匹配 - 改进:" << endl;
getnextval(str2, nextval);
kmp(str1, str2, nextval);
system("pause");
return 0;
}
![](https://i-blog.csdnimg.cn/blog_migrate/5bcd27bab1df851db65b930fbacdc47d.png)
下标从 0 开始!
#include<bits/stdc++.h>
#define max 100
using namespace std;
typedef struct // 变长
{
char *ch;
int length;
}Str;
void strsign(Str& str, char *ch)
{
if(str.ch)
str.ch = NULL;
int len = 0;
int i;
char *c = ch;
while(*c)
{
++len;
c++;
}
if(len == 0)
{
str.ch = NULL;
str.length = 0;
cout << "为空串!" << endl << endl;
}
else
{
str.ch = (char*)malloc(sizeof(char)*(len + 1));
if(str.ch == NULL)
{
cout << "分配空间失败!" << endl <<endl;
}
else
{
c = ch;
for(i = 0; i <= len; i++, c++)
str.ch[i] = *c;
str.length = len;
cout << "赋值完成!" << endl;
}
}
}
void getnextval(Str str, int nextval[])
{
int length = str.length;
int i = 0, j = -1;
nextval[0] = -1;
while(i < length)
{
if( j == -1 || str.ch[i] == str.ch[j] )
{
i++;j++;
if(str.ch[i] != str.ch[j])
nextval[i] = j;
else
nextval[i] = nextval[j];
}
else
j = nextval[j];
}
cout << endl;
}
void kmp(Str s1, Str s2, int nextval[])
{
int i = 0, j = 0;
for(;i < s1.length && j < s2.length;)
{
if(j ==0 || s1.ch[i] == s2.ch[j])
{
++i;
++j;
}
else
j = nextval[j];
}
if(j >= s2.length)
cout << "位置下标(从0起): " << i - s2.length << endl << endl;
else
cout << "不匹配!" << endl << endl;
}
int main()
{
Str str1, str2, str3;
int nextval[max]={0}, i;
char *ch = "ABABCABCACBAB" ; // <=> char ch[max]; 主串
strsign(str1, ch);
cout << endl;
cout << "str1 = " << str1.ch << endl << endl;
char *ch2 = "ABCAB"; // 匹配串
strsign(str2, ch2);
cout << endl;
cout << "str2 = " << str2.ch << endl << endl;
cout << "KMP匹配 - 改进:" << endl;
getnextval(str2, nextval);
kmp(str1, str2, nextval);
system("pause");
return 0;
}
![](https://i-blog.csdnimg.cn/blog_migrate/0f969eb2cef65cc536f25581d9c75355.png)