串
基本定义
-
由0个或者多个字符组成的有限序列
-
串中的字符长度称为
串长
-
串中任意连续个字符组成的序列叫做该串的
子串
-
注意区分空串和空格串
串的顺序存储结构
即用一段地址连续的存储单元来存储串中的字符序列
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define Maxsize 256
//串的结构定义
typedef struct {
char ch[Maxsize];
int length; //串的实际长度
}SString;
void InitString(SString *a) {
a->length = 0;
a->ch[0] = '\0';
}
int Strassign(SString* s, const char* str) {
if (strlen(str) > Maxsize) {
printf("赋值失败,超过最大串长");
return 0;
}
strcpy(s->ch + 1, str); //vs2022中可能不安全,按照要求更换
s->length = strlen(str);
return 1;
}
int Strlength(SString s) {
return s.length;
}
int Concat(SString* s, SString a, SString b) {
if (a.length + b.length > Maxsize) {
printf("超过长度限制");
return 0;
}
strcpy(s->ch + 1, a.ch + 1);
strcpy(s->ch + 1 + a.length, b.ch + 1);
return 1;
}
int Index(SString a, SString b, int pos) {
int i = pos;
while (i <= a.length - b.length) {
if (strncmp(a.ch + i, b.ch + 1, b.length)) {
return i;
}
i++;
}
return 0;
}
int Strcompare(SString a, SString b) {
return strncmp(a.ch + 1, b.ch + 1, a.length > b.length ? b.length : a.length);
}
int main() {
SString S1, S2, S3;
InitString(&S1);
InitString(&S2);
InitString(&S3);
Strassign(&S1, "abcdef");
Strassign(&S2, "def");
printf("S1的长度:%d\n", Strlength(S1));
Concat(&S3, S1, S2);
printf("连接后的S3:%s\n", S3.ch + 1);
int pos = Index(S1, S2, 1);
if (pos) {
printf("S2在S1中的位置:%d\n", pos);
}
else {
printf("S2不是S1的子串\n");
}
int cmp = Strcompare(S1, S2);
if (cmp > 0) {
printf("S1 > S2\n");
}
else if (cmp < 0) {
printf("S1 < S2\n");
}
else {
printf("S1 = S2\n");
}
return 0;
}
串的链式存储结构
如果简单的应用链表存储字符序列,一个结点对应一个字符,这样每个结点的空间利用率低。因此一个结点可以存放一个字符,也可以存放多个字符,若是最后一个结点未被占满,可以考虑用其他字符如“#”填充
- 串的链式存储结构除了在连接串和串操作时有一定方便,总的来说不如顺粗存储灵活,性能也不如顺序存储结构好,因此这里不给出代码
模块匹配算法
BF算法(朴素的模块匹配算法)
简单来讲,就是暴力搜索,一个个匹配下去,直到找到匹配的为止
这里的代码是直接以数组为例写的
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define Maxsize 256
void bf(char* text, char* pattern) {
int textlen = strlen(text);
int patternlen = strlen(pattern);
int i = 0, j = 0;
while (i <= textlen && j < patternlen) {
if (text[i] == pattern[j]) {
i++; j++;
}
else {
i = i - j + 1; //回溯到最开始匹配的下一位
j = 0; //重新开始
}
}
if (j == patternlen) {
printf("匹配到的位置为:%d\n", i - patternlen + 1);
}
else {
printf("匹配失败");
}
}
int main() {
char a[Maxsize], b[Maxsize];
printf("请输入主串:");
scanf_s("%s", a, Maxsize);
printf("请输入模式串:");
scanf_s("%s", b, Maxsize);
bf(a, b);
system("pause");
return 0;
}
KMP模式匹配算法
-
KMP算法的核心思路是通过预处理模式串,构建一个部分匹配表(Partial Match Table,简称PMT),也称为最长前缀后缀数组(Longest Prefix Suffix,简称LPS)。这个表用于在匹配过程中,当出现不匹配的情况时,能够快速确定模式串的下一个匹配位置,从而避免不必要的回溯比较。
-
为此定义了next[j]函数,表明当模式中第j个字符与主串中的相应字符“失配“时,在模式中需要重新和主串中该字符进行比较的字符的位置
-
利用部分匹配的结果加快模式快的滑动速度,且主串的指针i不必回溯,可提速到O(n+m)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define Maxsize 256
typedef struct {
char ch[Maxsize];
int length;
} SString;
void get_next(SString s, int *next) {
int i = 1, k = 0;
next[1] = 0;
while (i < s.length) {
if (k == 0 || s.ch[i] == s.ch[k]) {
++i;
++k;
next[i] = k;
} else {
k = next[k];
}
}
}
void kmp(SString text, SString pattern) {
int textlen = text.length;
int patternlen = pattern.length;
int i = 1, j = 1;
int next[Maxsize]; // 确保 next 数组的大小为 Maxsize
get_next(pattern, next);
while (i <= textlen && j <= patternlen) {
if (j == 0 || text.ch[i] == pattern.ch[j]) {
i++;
j++;
} else {
j = next[j];
}
}
if (j > patternlen) {
printf("匹配到的位置为:%d\n", i - patternlen);
} else {
printf("匹配失败\n");
}
}
int main() {
char a[Maxsize], b[Maxsize];
printf("请输入主串: ");
scanf("%s", a);
printf("请输入模式串: ");
scanf("%s", b);
SString text, pattern;
strcpy(text.ch + 1, a); // 从下标 1 开始复制
strcpy(pattern.ch + 1, b); // 从下标 1 开始复制
text.length = strlen(a);
pattern.length = strlen(b);
kmp(text, pattern);
system("pause");
return 0;
}
kmp算法的进一步改进:
当模式串为一些特殊的字符如aaaa
时,当前字符不匹配时,需要回溯到 next[k]
,但有时 next[k]
也可能是不匹配的。因此,需要继续回溯,直到找到一个匹配的前缀或 k
为 0。
void get_nextval(SString s, int *nextval) {
int i = 1, k = 0;
nextval[1] = 0;
while (i < s.length) {
if (k == 0 || s.ch[i] == s.ch[k]) {
++i;
++k;
if(s.ch[i]!=s.ch[k])
nextval[i]=k;
else
nextval[i]=nextval[k];
} else {
k = nextval[k];
}
}
}