前言
由于计算机的发展,人们发现对于非数值类型的处理越来越多,进而衍生出来串。
串也就是我们所使用的字符串。
如下所有代码需要做如下引用:
1 2 3 4 5 6
| #include <stdio.h> #include <stdbool.h> #include <string.h>
#define _CRT_SECURE_NO_WARNINGS
|
关于上述引用的说明,原因参考关于C语言的一些零碎思考
串的定义和基本操作
串的定义
串,即字符串(String)是由零个或者多个字符组成的有限序列,一般记作S='a1,a2,...an'
($n \geqslant 0 $)。
其中S
是串的名称,单引号括起来的字符序列是串的值,其中串字符的个数称为串的长度。当 $n =0$ 的时候称为空串。代码示例:
子串:串中任意连续个字符组成的子序列,例如:上面的子串Hello
。
串的数据对象限定为:字符集(如中文字符,英文字符)。
串的基本操作
函数 |
说明 |
StrAssign() |
赋值操作,给字符串赋值 |
StrCopy() |
复制操作,将两个字符串进行复制 |
StrEmpty() |
判断字符串是否为空 |
StrLength() |
获取字符串长度,返回串元素的个数 |
ClearString() |
清空操作,清空串为空串 |
DestoryString() |
销毁串 |
Concat() |
串链接,链接两个字符串返回新串 |
SubString() |
获取子串,根据元素位置和长度返回其子串 |
Index() |
定位子串的位置(第一次出现的) |
StrCompare() |
串比较操作 |
串的物理结构
串的存储结构
顺序存储
静态顺序存储
1 2 3 4 5
| #define MaxLen 255 typedef struct { char ch[MaxLen]; int length; }SString;
|
动态顺序存储
1 2 3 4
| typedef struct { char *ch; int length; }DString;
|
链式存储
1 2 3 4
| typedef struct stringNode { char ch; struct stringNode* next; }StrNode;
|
由于char
类型只占一个字节,而struct stringNode*
类型指针占四个字节,所以采用上述链式存储的存储密度很低。如果想要使用链式存储,可以尝试使用如下改进:
1 2 3 4
| typedef struct stringNode { char ch[4]; struct stringNode* next; }StrNode;
|
这样每个节点存储四个字节的字符和四个字节的指针。
基本操作的实现
注:本示例基本操作的实现是基于静态顺序的存储结构
截取(获取)子串
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| bool SubString(SString* s, SString* sub, int pos, int len) { if (pos + len > s->length) { printf("子串索引超出主串\n"); return false; } printf("截取的字符串为:"); for (int i = 0; i < len; i++) { sub->ch[i] = s->ch[pos + i]; printf("%c", sub->ch[i]); } printf("\n"); sub->length = len; return true; }
|
比较字符串
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| int StrCompare(SString* s1, SString* s2) { if (s1->length==0 || s2->length==0) { printf("存在空串"); return -99; } printf("要匹配的字符串为:"); for (int j = 0; j < s2->length; j++) { printf("%c",s2->ch[j]); } printf("\n"); for (int i = 0; i < s1->length &&i<s2->length; i++) { if (s1->ch[i]!=s2->ch[i]) { return s1->ch[i] - s2->ch[i]; } } return s1->length - s2->length; }
|
定位子串的位置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| int Index(SString* s, SString* sub) { if (s->length == 0 || sub->length == 0) { printf("存在空串"); return -99; } SString temp; for (int i = 0; i < s->length; i++) { SubString(s, &temp, i, sub->length); if (StrCompare(&temp, sub) == 0) { printf("子串匹配成功,位置为:%d", i+1); return i+1; } } printf("未匹配到字符串"); return 0; }
|
测试代码
1 2 3 4 5
| int main() { SString s = { { 'h','e', 'l', 'l', 'o' },5 }; SString sub = { { 'l', 'o' },2 }; Index(&s, &sub); }
|
运行结果:
朴素模式匹配算法
通俗来说就是上面所实现的字符串的操作的“定位子串的位置”,俗称“暴力解”。
现在来通过数组下标来实现朴素模式匹配算法,也就是上面的定位操作,代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| int NaiveMatch(char* s[],char* m[]) { int i = 0; char temp[MaxLen]; for (int j = 0; j < (strlen(s)-strlen(m)+1); j++) { SubString(s, temp, i, strlen(m)); if (StrCompare(temp, s)==0) { i++; } else { i = 0; j = j - i + 2; } if (i>strlen(m)) { printf("已匹配到字符串,位置:%d", strlen(s) - strlen(m)+1); return strlen(s) - strlen(m)+1; } } printf("匹配失败"); return -1; }
int main() { char s[MaxLen] = { 'h','e', 'l', 'l', 'o' }; char sub[MaxLen] = { 'l', 'o' }; NaiveMatch(&s, &sub); }
|
运行结果:
KMP算法
KMP算法简介
KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt提出的,因此人们称它为克努特—莫里斯—普拉特操作(简称KMP算法)。KMP算法的核心是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现就是通过一个next()函数实现,函数本身包含了模式串的局部匹配信息。KMP算法的时间复杂度O(m+n)
如上来源:百度百科kmp算法
KMP算法是在上面的朴素模式匹配算法的基础上改进而来
KMP算法实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| bool GetNext(char* m[],char* next[]) { int i, k; i = 1; k = 0; next[1] = 0; while (i<strlen(m)) { if (k==0 || m[i]==m[k]) { ++i; ++k; next[i] = k; } else { k = next[k]; } } }
|
改进KMP算法
不想写,欠……
这部分内容有点繁杂,我在尝试使用更加直观的方法来说明。如上也同理,仅贴代码示例;
End
救命越来越不想写了,属实太难用文字解释了,如果用视频的话要比文字快很多。