文章目录
- 串 - 顺序
- 一、串的表示及实现
- 1.1 串的概念
- 1.2 串的初始化
- 1.3 复制串
- 1.4 串的判空
- 1.5 串的比较
- 1.6 串的长度
- 1.7 清空串
- 1.8 串的拼接
- 1.9 串的截取
- 1.10 插入子串
- 1.11 串的打印
- 二、串的应用
- 2.1 串的模式匹配 - 基本操作
- 2.2 串的模式匹配 - BF
- 2.3 串的模式匹配 - KMP
- next数组
- 测试
串 - 顺序
一、串的表示及实现
1.1 串的概念
(1)串(String
):是零个或多个字符组成的有限序列。一般记为: S='a1a2…an' (n≥0)
。
- 其中S为串名,用单引号括起来的为串值, n为串的长度。
(2)子串
:串中任意个连续的字符组成的子序列称为该串的子串。
(3)主串
:包含子串的串相应地称为主串。
- 子串在主串中的位置通常将字符在串中的序号称为该字符在串中的位置。子串在主串中的位置则以子串的第一个字符在主串中的位置来表示。
(5)空格串
:由一个或多个称为空格的特殊字符组成的串,其长度为串中空格字符的个数。
(6)空串
:无任何字符组成的串,其串长度为零。
(7)串相等
:只有当两个串的长度相等,并且每个对应位置的字符都相等时才相等。
串的基本操作:
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<string.h>
#include<stdbool.h>
// 动态存储串
typedef struct {
char *str; // 基址
int maxLength;
int length; // 串的大小
} DString;
// 初始化操作
void StrAssign(DString *S, int max, char *string);
// 复制串
void StrCopy(DString *S, DString T);
// 串的判空
bool StrEmpty(DString S);
// 串比较
int StrCompare(DString S, DString T);
// 串的长度
int StrLength(DString S);
// 清空串
void ClearString(DString *S);
// 串的拼接
void StrConcact(DString *N, DString S, DString T);
// 串的截取
void StrSub(DString* sub, DString S, int pos, int len);
// 串的匹配
int StrIndex(DString S, DString T, int pos);
int Index(DString S, DString T, int pos);
// 插入子串操作
void StrInsert(DString *S, int pos, DString T);
// 串的打印
void StrPrint(DString *S);
返回顶部
1.2 串的初始化
/* 初始化操作
* DString *S 串
* int max 串的最大长度
* char *string 待生成的串
*/
void StrAssign(DString *S, int max, char *string) {
assert(S); // 断言
int i, m;
S->length = strlen(string); // 获取串的长度
// 获取串的允许最大长度
if (S->length > max) {
m = S->length;
} else {
m = max;
}
S->maxLength = m;
S->str = (char *) malloc(sizeof(char) * (m)); // 分配串的存储空间
if (S->str == NULL) exit(-1);
// 创建串
for (i = 0; i < S->length; i++) {
S->str[i] = string[i];
}
}
返回顶部
1.3 复制串
/* 复制串
* DString* S 新串
* DString T 待复制的串
*/
void StrCopy(DString *S, DString T) {
assert(S); // 断言
// 获取字串的长度
S->length = T.length;
// 分配串的存储空间
S->str = (char *) malloc(sizeof(char) * (S->length));
if (S->str == NULL) exit(-1);
// 复制串
for (int i = 0; i < S->length; i++) {
S->str[i] = T.str[i];
}
}
返回顶部
1.4 串的判空
/* 串的判空
* DString S 待判串
*/
bool StrEmpty(DString S) {
assert(&S); // 断言
return S.length == 0;
}
返回顶部
1.5 串的比较
/* 串比较
* DString S 串1
* DString T 串2
*/
int StrCompare(DString S, DString T) {
// 断言
assert(&S);
assert(&T);
int i = 0;
for (; i < S.length && i < T.length; i++) {
if (S.str[i] != T.str[i]) {
return S.str[i] - T.str[i];
}
}
return S.length - T.length;
}
返回顶部
1.6 串的长度
/* 串的长度
* DString S 判长字串
*/
int StrLength(DString S) {
assert(&S);
return S.length;
}
返回顶部
1.7 清空串
/* 清空串
* DString* S 待清空串
*/
void ClearString(DString *S) {
assert(S); // 断言
free(S->str); // 释放空间
S->str = NULL; // 置空
S->length = 0; // 置零
}
返回顶部
1.8 串的拼接
/* 串的拼接
* DString *N 合并的新串
* DString S 串1
* DString T 串2
*/
void StrConcact(DString *N, DString S, DString T) {
assert(&S);
assert(&T);
N->length = S.length + T.length; // 新串的长度
N->str = (char *) malloc(sizeof(char) * (N->length));// 分配串的存储空间
if (N->str == NULL) exit(-1);
// 串1
for (int i = 0; i < S.length; i++) {
N->str[i] = S.str[i];
}
// 串2
for (int i = 0; i < N->length; i++) {
N->str[i + S.length] = T.str[i];
}
}
返回顶部
1.9 串的截取
/* 串的截取
* DString *sub 截取的新串
* DString S 被截取的母串
* int pos 截取子串的开始位置
* int len 截取子串的长度
*/
void StrSub(DString *sub, DString S, int pos, int len) {
assert(&S); // 断言
sub->length = len; // 新串的长度
sub->str = (char *) malloc(sizeof(char) * (sub->length + 1)); //分配串的存储空间
if (sub->str == NULL) exit(-1);
if (pos >= 0 && pos < S.length && len >= 0 && len <= S.length - pos + 1) {
for (int i = 0; i < len; i++) { // 遍历
sub->str[i] = S.str[pos + i]; // 从pos位置开始取字符
}
} else {
printf("参数有问题,请核实!");
exit(-1);
}
}
返回顶部
1.10 插入子串
/* 插入子串操作
* DString *S 串
* int pos 插入子串的起始位置
* DString T 待插入的串
*/
void StrInsert(DString *S, int pos, DString T) {
assert(S); // 断言
int i; // 变量i
char *p; // 新的基址
if (pos < 0) {
printf("参数pos出错!");
exit(-1);
} else {
// 如果插入的串和原串的长度大于初始定义的最大串长
if (S->length + T.length > S->maxLength) {
// 重新分配空间
p = (char *) realloc(S->str, (S->length + T.length) * sizeof(char));
}
// 原串从插入位置开始的串向后移动新串的长度个位置
for (i = S->length - 1; i >= pos; i--) {
S->str[i + T.length] = S->str[i];
}
// 子串插入
for (i = 0; i < T.length; i++) {
S->str[pos + i] = T.str[i];
}
// 新串的长度 = 原串 + 子串
S->length = S->length + T.length;
}
}
返回顶部
1.11 串的打印
/* 串的打印
* DString *S 串
*/
void StrPrint(DString *S) {
assert(S); // 断言
int i;
for (i = 0; i < S->length; i++) {
printf("%c", S->str[i]);
}
printf("\n");
}
返回顶部
二、串的应用
模式匹配:求子串(模式串)在主串(目标串)中的位置,若子串出现在主串中,则匹配成功,否则匹配不成功。常应用于文章中关键字的查找。
2.1 串的模式匹配 - 基本操作
/* 串的匹配
* DString S 母串
* DString T 字串
* int pos 位置
*/
int StrIndex(DString S, DString T, int pos) {
int n, m, i;
DString sub;
if (pos > 0) {
n = StrLength(S);// 得到主串S的长度 17
m = StrLength(T);// 得到子串T的长度 8
i = pos;
while (i <= n - m + 1) { // 17-8+1 = 10
StrSub(&sub, S, i, m); // 取主串第i个位置,长度与T相等子串给sub
StrPrint(&sub);
if (StrCompare(sub, T) != 0) ++i;// 如果两串不相等
else return i; // 如果两串相等
}
}
return 0; // 若无子串与T相等,返回0
}
返回顶部
2.2 串的模式匹配 - BF
简单的模式匹配: i i i 和 j j j 分别指向主串 S S S 中的字符 和 子串 T T T 中的字符,如果 S i = T j S_i=T_j Si=Tj, i 4 i4 i4 和 j j j 分别加 1 1 1,指向下一个字符,否则, i i i 退回到本趟匹配起始位置的下一个位置, j j j 重新指向子串的第一个字符,开始下一趟匹配。
/*
* BF 算法
* DString S 母串
* DString T 子串
*/
int Index(DString S, DString T, int pos) {
if (pos < 0 || pos > S.length - T.length) {
printf("pos参数值有误,请校验!");
exit(-1);
}
int i = pos; // i用于主串S中当前位置下标值
int j = 0; // j用于子串T中当前位置下标值
while (i < S.length && j < T.length) { // 若i小于S的长度并且j小于T的长度时,循环继续
if (S.str[i] == T.str[j]) { // 两字母相等则继续
i++;
j++;
} else { // 指针后退重新开始匹配
i = i - j + 1; // i退回到上次匹配首位的下一位,即i=i-j+1
j = 0; // j退回到子串T的首位
}
}
//子串所有字符都匹配完毕
if (j >= T.length) {
return (i - j); // 返回匹配的第一个字符的下标
} else {
return 0; // 匹配失败
}
}
缺点:存在回溯的问题!
返回顶部
2.3 串的模式匹配 - KMP
KMP算法核心是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数,从而达到快速匹配的目的。
KMP算法与BF算法(暴力算法)区别在于,主串的 i i i 不会回退,并且模式串的 j j j 不会每次都回到 0 位置。
假设此时 i 指向 g,j 指向 c,匹配失败。
- 此时 i 不进行回退,因为在这个地方前,两个字符串是有一部分相同的。
- 我们观察,当我们保持 i 不动, j 退回到第三个位置也就是 c 时,不难发现两个字符串前 a b 是一样的。
- 因此,我们需要借助 next 数组,帮助我们找到 j 需要回退的位置。
next数组
-
KMP算法的精髓就是next数组,next[j]=k,表示不同的 j 对应一个 k 值;k 表示模式串下标为 j 的元素,匹配失败时,要退回的位置。
-
最长前后相等子串
next[j] = 匹配字符个数 + 1
参考视频:三分钟搞定 数据结构 串 KMP算法next数组求值
/*
* KPM
*/
void Next(char *T, int *next) {
next[1] = 0; // 初始化默认值
int i = 1;
int j = 0;
while (i < strlen(T)) {
if (j == 0 || T[i - 1] == T[j - 1]) {
i++;
j++;
next[i] = j;
} else {
j = next[j];
}
}
}
int KMP(char *S, char *T) {
int next[10];
Next(T, next);//根据模式串T,初始化next数组
int i = 1;
int j = 1;
while (i <= strlen(S) && j <= strlen(T)) {
//j==0:代表模式串的第一个字符就和当前测试的字符不相等;
// S[i-1]==T[j-1],如果对应位置字符相等,两种情况下,指向当前测试的两个指针下标i和j都向后移
if (j == 0 || S[i - 1] == T[j - 1]) {
i++;
j++;
} else {
j = next[j];//如果测试的两个字符不相等,i不动,j变为当前测试字符串的next值
}
}
if (j > strlen(T)) {//如果条件为真,说明匹配成功
return i - (int) strlen(T);
}
return -1;
}
返回顶部
测试
#include "Strand.c"
void TestDString() {
// 创建串S
printf("*******************\n");
printf("S串的创建:");
DString S;
StrAssign(&S, 5, "abc");
// 打印串S
StrPrint(&S); // abc
// 新的串T
printf("*******************\n");
printf("S串的插入 => S+T:");
DString T;
StrAssign(&T, 3, "abc");
// 将新串T插入S,从下标2开始
StrInsert(&S, 3, T);
// 打印串S
StrPrint(&S); // abcabc
// 复制串
printf("*******************\n");
printf("S串的复制 => P:");
DString P;
StrCopy(&P, S);
StrPrint(&P); // abcabc
// 串的比较
printf("*******************\n");
printf("T、P串的比较:");
int res = StrCompare(T, P);
printf("%d\n", res); // -1
// 串的长度
printf("*******************\n");
printf("S串的长度是:%d\n", StrLength(S)); // 6
// 合并串
printf("*******************\n");
printf("S、P串的合并:");
DString *newStr;
StrConcact(&newStr, S, P);
StrPrint(&newStr); // abcabc abcabc
// 串的截取
printf("*******************\n");
printf("S串的截取:");
DString newStr1;
StrSub(&newStr1, S, 1, 5); // abcabc
StrPrint(&newStr1);
// 串的匹配
printf("*******************\n");
printf("串的匹配:\n");
char a1[] = "acabaabcaabaabcac";
char a2[] = "abaabcac";
DString a;
StrAssign(&a, 17, a1);
DString b;
StrAssign(&b, 8, a2);
// 打印串S
StrPrint(&a);
StrPrint(&b);
int result1 = StrIndex(a, b, 1);
int result2 = Index(a, b, 1);
printf("BF算法匹配字串的位置:%d\n", result1);
printf("BF算法匹配字串的位置:%d\n", result2);
}
int main() {
TestDString();
int next[100];
char s1[] = "acabaabcaabaabcac";
char s2[] = "abaabcac";
int i = KMP(s1, s2);
printf("%d\n", i);
system("pause");
return 0;
}
返回顶部