前言

由于计算机的发展,人们发现对于非数值类型的处理越来越多,进而衍生出来串。

串也就是我们所使用的字符串。

如下所有代码需要做如下引用:

#include <stdio.h>
#include <stdbool.h>
#include <string.h>

//如果你使用的是Visual Studio,则还需要添加如下一条引用
#define _CRT_SECURE_NO_WARNINGS

关于上述引用的说明,原因参考关于C语言的一些零碎思考

串的定义和基本操作

串的定义

串,即字符串(String)是由零个或者多个字符组成的有限序列,一般记作S='a1,a2,...an'($n \geqslant 0 $)。

其中S是串的名称,单引号括起来的字符序列是串的值,其中串字符的个数称为串的长度。当 $n =0$ 的时候称为空串。代码示例:

S = "Hello,World"

子串:串中任意连续个字符组成的子序列,例如:上面的子串Hello

串的数据对象限定为:字符集(如中文字符,英文字符)

串的基本操作

函数说明
StrAssign()赋值操作,给字符串赋值
StrCopy()复制操作,将两个字符串进行复制
StrEmpty()判断字符串是否为空
StrLength()获取字符串长度,返回串元素的个数
ClearString()清空操作,清空串为空串
DestoryString()销毁串
Concat()串链接,链接两个字符串返回新串
SubString()获取子串,根据元素位置和长度返回其子串
Index()定位子串的位置(第一次出现的)
StrCompare()串比较操作

串的物理结构

串的存储结构

顺序存储

静态顺序存储

#define MaxLen 255	//定义串的最大长度为255
typedef struct {
	char ch[MaxLen];
	int length;	//串的长度
}SString;

动态顺序存储

typedef struct {
	char *ch;	//用malloc函数申请一片空间,指针指向头地址
	int length;	//串的长度
}DString;

链式存储

typedef struct stringNode {
	char ch;	//每个节点存储一个字符
	struct stringNode* next;
}StrNode;

由于char类型只占一个字节,而struct stringNode*类型指针占四个字节,所以采用上述链式存储的存储密度很低。如果想要使用链式存储,可以尝试使用如下改进:

typedef struct stringNode {
	char ch[4];	//每个节点存储一个字符
	struct stringNode* next;
}StrNode;

这样每个节点存储四个字节的字符和四个字节的指针。

基本操作的实现

注:本示例基本操作的实现是基于静态顺序的存储结构

截取(获取)子串

//求子串(获取子串)
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;
}

比较字符串

//比较操作
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;
}

定位子串的位置

//定位操作
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;
}

测试代码

int main() {
	SString s = { { 'h','e', 'l', 'l', 'o' },5 };
	SString sub = { { 'l', 'o' },2 };
	Index(&s, &sub);
}

运行结果:

image-20220813171339807

朴素模式匹配算法

通俗来说就是上面所实现的字符串的操作的“定位子串的位置”,俗称“暴力解”。

现在来通过数组下标来实现朴素模式匹配算法,也就是上面的定位操作,代码示例:

//朴素模式匹配算法——利用数组下标
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++;	//模式串指针+1
		}
		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);
}

运行结果:

image-20220817021229084

KMP算法

KMP算法简介

KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt提出的,因此人们称它为克努特—莫里斯—普拉特操作(简称KMP算法)。KMP算法的核心是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现就是通过一个next()函数实现,函数本身包含了模式串的局部匹配信息。KMP算法的时间复杂度O(m+n)

如上来源:百度百科kmp算法

KMP算法是在上面的朴素模式匹配算法的基础上改进而来

KMP算法实例

//求Next数组
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算法

不想写,欠......

这部分内容有点繁杂,我在尝试使用更加直观的方法来说明。如上也同理,仅贴代码示例;

image-20220818020541675

End

救命越来越不想写了,属实太难用文字解释了,如果用视频的话要比文字快很多。

Q.E.D.


赤脚踩在明媚的沙滩上,我看见了你闪耀的双眼,柔软的头发,我便心有所属