本文介绍: kmp算法字符匹配算法,核心是利用匹配失败后的信息,尽量减少子串的匹配次数达到快速匹配的目的。具体通过next()的函数实现,本身包含模式串的局部匹配信息,时间复杂度为O(m+n)KMP的i值不会回退,只回退j,j也不是一定回退到0位置

目录

  1. 字符分类函数
  2. 字符转换函数
  3. strlen的使用和模拟实现
  4. strcpy的使用和模拟实现
  5. strcat的使用和模拟实现
  6. strcmp的使用和模拟实现
  7. strncpy的使用
  8. strncat的使用
  9. strncmp的使用
  10. strstr的使用和模拟实现
  11. strtok的使用
  12. strerror的使用
  13. 字符串匹配优化-KMP算法

编程过程中,经常要处理字符字符串,为了方便操作字符字符串,c语言标准库提供了一系列库函数,接下来学习一下这些函数

1. 字符分类函数

c语言有一系列函数专门做字符分类的,也就是一个字符属于什么类型字符
这些函数都需要一个头文件ctype.h

函数 如果它的参数符合下列条件返回
iscntrl 任何控制字符
isspace 空白字符: 空格 ’ ’ ,换页 ‘f’ , 换行 ‘n’ , 回车 ‘r’ , 制表符 ‘t’ , 垂直制表符 ‘v
isdigit 进制数字 0-9
isxdigit 十六进制数字,包括所有十进制数字, 小写字母a-f,大写字母A-F
islower 小写字母a-z
isupper 大写字母A-Z
isalpha 字母a-z或A-Z
isalnum 字母或者数字,a-z,A-Z,0-9
ispunct 标点符号,任何不属于数字或者字母的图形字符 (可打印)
isgraph 任何图形字符
isprint 任何可打印字符,包括图形字符和空白字符

控制字符说明,当c在0x00-0x1F之间或等于0x7F(DEL)时,返回非零值,否则返回零

这些函数的使用方法类似,只讲解一个:

int islower (int c);

islower函数能够判断参数部分的c是否是小写字母
通过返回值说明是否是小写字母,如果是小写字母就返回非0的整数,如果不是小写字母,则返回0

练习

一个代码,将字符串中的小写字母转大写,其他字符不变

int i = 0;
char str[] = "test string.n";
char c;

while (str[i]) {

	c = str[i];
	if (islower(c)) {
		c -= 32;		
	}
	putchar(c);
	i++;
}

输出

在这里插入图片描述

2. 字符转换函数

c语言提供了两个字符转换函数:

int tolower ( int c ); //将参数大写字母转小写
int toupper ( int c ); //将参数的小写字母转大写

有了转换函数,就可以用这种方法实现上面的功能

int i = 0;
char str[] = "test string.n";
char c;

while (str[i]) {

	c = str[i];
	if (islower(c)) {
		c = toupper(c);		
	}
	putchar(c);
	i++;
}

3. strlen使用模拟实现

函数原型

size_t strlen ( const char * str);

使用

 const char* str1 = "abcdef";
 const char* str2 = "bbb";
 if(strlen(str2)-strlen(str1)>0)
 {
 printf("str2>str1n");
 } 
 else
 {
 printf("srt1>str2n");
 }

上面由于strlen返回的是无符号,所以短的减长的会是一个无符号,大于0,输出str2>str1

输出:

在这里插入图片描述

strlen模拟实现
方式1,计数器:

//计数器方式
int my_strlen(const char* str) {

	assert(str);
	int count = 0;
	while (*str) {

		count++;
		str++;
	}
	return count;
}

方式2,指针:

//指针-指针方式
int mystrlen(const char* str) {

	assert(str);
	char* p = str;
	while (*p) {
		p++;
	}

	return p - str;
}

方式3 递归:

int my_strlen(const char* str) {
	
	assert(str);
	if (*str == '') {
		return 0;
	}
	else {
		return 1 + my_strlen2(str+1);
	}
}

在这里插入图片描述
指针每次+1,往右挪一位,字符串的长度就会减1

4. strcpy函数的使用模拟实现

char* strcpy ( char* destination, const char* source );

复制源指针的字符串到目标指针,遇到空字符结束

模拟实现

char* my_strcpy(char* destination,const char* source) {

	char* ret = destination;
	assert(destination);
	assert(source);

	while (*source) {
		*destination = *source;
		destination++;
		source++;
	}
	*destination = '';
	return ret;
}

strcat的使用和模拟实现

拼接源字符串到目标字符串,目标字符串的结尾空字符被新字符第一个字符覆盖

模拟实现

char* my_strcat(char* dest,const char* src) {

	char* ret = dest;
	assert(dest);
	assert(src);

	while (*dest) {
		dest++;
	}

	while (*src) {
		*dest = *src;
		dest++;
		src++;
	}
	*dest='';
	return ret;
}

6. strcmp的使用和模拟实现

这个函数开始比较个字符串的第一个字符,如果相等继续比较一对,直到不同或者空字符结束

模拟实现

int my_strcmp(const char* str1,const char* str2) {

	assert(str1);
	assert(str2);

	while (*str1 == *str2) {

		if (*str1 == '') {
			return 0;
		}
		str1++;
		str2++;
	}
	return *str1 - *str2;
}

7. strncpy函数的使用

char* strncpy ( char* destination, const char* source, size_t num );

拷贝原字符串前num个字符到目标字符串,如果在复制num个字符后,原字符串的末尾被找到,目标指针用0填充直到写入num个字符

  • 从原字符串拷贝num个字符到目标空间
  • 如果原字符串长度小于num,则拷贝完原字符串后,在目标后边追加0,直到num

使用

   char string[32] = "Cats are nice";
   strncpy( string, "Dog", 3 );
   printf ( "After:  %sn", string );

输出:

在这里插入图片描述
这个函数拷贝时不会在末尾自动

8. strncat的使用

char* strncat ( char* destination, const char* source, size_t num );

 char str1[20];
 char str2[20];
 strcpy (str1,"To be ");
 strcpy (str2,"or not to be");
 strncat (str1, str2, 6);
 printf("%sn", str1);

拼接str2前6个字符到str1末尾,也就是or not

输出:

在这里插入图片描述

9. strncmp的使用

int strcmp ( const char* str1, const char* str2, size_t num ) ;

比较str1和str2的前num个字符,如果相等继续往后比较,最多比较num个,如果提前发现不一样就提前结束,如果num个字符都相等,就返回0

在这里插入图片描述

10. strstr的使用和模拟实现

char* strstr ( const char* str1, const char* str2) ;

 char str[] ="This is a simple string";
 char * pch;
 pch = strstr (str,"simple");
 printf("%pn", pch );

输出:

在这里插入图片描述模拟实现

char* my_strstr(const char* str1, const char* str2) {

	char* start1 = (char*)str1;
	char* s1, *s2;

	if (!*str2)
		return ((char*) str1);

	while (*start1) {
			
		s1 = start1;
		s2 = (char*) str2;

			while (!(*s2-*s1) && *s1 && *s2) {

				s1++;
				s2++;
			if (!*s2) {
				return start1;
			}
			
		}
			start1++;
	}
	return NULL;
}

11. strtok的使用

使用

char arr[] = "192.168@6.111";
char* sep = ".@";
char* s = NULL;

//分开输出
s = strtok(arr,sep);
printf("%s ",s);
s = strtok(NULL, sep);
printf("%s ", s);
s = strtok(NULL, sep);
printf("%s ", s);
s = strtok(NULL, sep);
printf("%s ", s);

//循环
for (s = strtok(arr, sep); s != NULL; s = strtok(NULL, sep)) {

	printf("%s ", s);
}

12. strerror的使用

char* strerror ( int errnum ) ;

可以吧参数部分错误对应的错误字符串返回

在不同的系统和c标准库都规定了一些错误码,一般放在 errno.h 头文件,用一个全局变量errno记录错误码,刚开始是0,表示没有错误,数字很难理解错误信息,所以可以通过数字打印错误信息

//打印0-10的错误信息
for (int i = 0; i <= 10; i++) {
	printf("%d :%srn", i, strerror(i));
}

输出 (win10 vs2022):

在这里插入图片描述

使用

FILE* pfile = fopen("exist.ent","r");
if (pfile == NULL) {
	printf("error opening file exist.ent:%s",strerror(errno));
}

由于目录不存在文件,会打印错误信息,不存在该文件

输出

在这里插入图片描述
可以使用perror函数,可以直接将错误信息打印出来,不需要错误码,还会先输出冒号和空格,再打印错误信息

FILE* pfile = fopen("exist.ent","r");
if (pfile == NULL) {
	//printf("error opening file exist.ent:%s",strerror(errno));
	perror("error opening file exist.ent");
}

输出

在这里插入图片描述

13. 字符串匹配优化-KMP算法

上面的strstr函数的使用使用的是一种BF算法,从T字符串和S字符串的第一个开始比较,如果相等继续比较第二个,不相等,比较T字符的第二个和S字符串的第一个,它的时间复杂度是 O (m*n),m和n可看做是两个字符串的长度。这种方法可以优化的,如果S字符串的长度是5,当T字符串剩下的长度不足5时,肯定是匹配失败的,这时,可以提前返回失败

这里讲解另一种匹配思路,KMP方法

简介
kmp算法是字符匹配算法,核心是利用匹配失败后的信息,尽量减少子串的匹配次数达到快速匹配的目的。具体通过next()的函数实现,本身包含模式串的局部匹配信息,时间复杂度为O(m+n)

KMP的i值不会回退,只回退j,j也不是一定回退到0位置

  1. 首先说明为什么主串不回退

在这里插入图片描述

上面的一个主串和子串,按照BF匹配,匹配到5下标,一个是d,一个是c,所以需要回退,j回退到0,i回退到刚开始匹配的下标的下一个,也就是1下标,继续开始匹配

我们再想一想,既然能匹配到最后一个字符,说明两个串前面有一部分是相同的,例如上图的第一个蓝色下划线ab子串中有两个ab,之所以用第一个ab,是因为中间有可能有其他字母漏匹配,以第一次匹配的为准。
在这种情况下,i没必要回退,继续匹配下一个字符d,j也没必要回退到0下标,因为ab和上面匹配到的ab是一样的,j完全可以回退到ab后的2下标继续匹配,这时同时移动两个下标,两个字符串完全匹配上了,就用没有回退的方式找到了子串的位置

上面可能是一个特定的例子,但可以总结出这种思路,根据不同的结果调整j返回的下标,,这个就是next数组,,它保存了每个结果匹配不成功j返回的下标,不同的j值对应了一个k值,k就是返回的位置

k值的计算规则是这样:

规则: 找到匹配成功部分两个相等的真子串 (不包括本身),一个以下标0开始,另一个以j-1下标结尾。若找到,k值找到的子串长度,没找到填0
不管什么数据,next[0] = -1,next[1] = 0

以下面的练习说明next数组填写规则

在这里插入图片描述
首先规则2说,0和1下标,总是填-1和0

在这里插入图片描述

接下来看2下标,按照规则1的说法,需要找两个真子串,一个以下标0开始,一个以j-1,也就是找以a开始以b结尾的两个真子串,只有一个ab,所以填0

在这里插入图片描述

然后看3下标,就是找以0下标开始,2下标结束的两个子串,也就是以a开始,a结尾的子串,有两个a,长度是1,填1

在这里插入图片描述

到下标4的时候,找以a开始b结尾的两个子串,0和1下标的ab和2和3下标的ab符合条件,ab长度是2,填2

按照上述规则继续往下,得出下面结果

在这里插入图片描述

那么如何将这种方法转换为程序呢?这里我们分为两种情况

第一种情况,要填的下标的前一个字母和匹配的第一个字串的下一个相等,下图所示

在这里插入图片描述
我们要填第9个下标,9下标需要根据8下标填,之前匹配到的字串是abc (绿色下划线),可以发现8下标和k下标的字母都是a,说明9下标肯定可以匹配abca,所以在8下标的基础上加1。
j看做需要填的下标,可以得出规则,k是next[j-1],如果next[j-1] == next[k] ,next[j] = next[j-1] +1

第二种情况,要填的j-1下标的字母和匹配到的第一个字串的下一个字符不一致

在这里插入图片描述

需要填6下标,i下标和k下标一个是a,一个是c,不一致。所以k需要回退,回退到k的位置,k的字符是a还是不一致,继续回到0下标,0下标时a,和i下标一致,所以填1,如果一直不相等,会回到0下标,值是-1的话,加1,填为最小值0就行

规则,如果next[j-1] != next[k] , k = next[k],一直重复,直到找到 next[k] == next [j-1],如果值为-1,填为0

得出以上两种情况,就可以写代码

代码

//获取next数组
void GetNext(char* sub,int* next) {

	next[0] = -1;
	next[1] = 0;
	int lensub = strlen(sub);
	int i = 2; //当前下标
	int k = 0; //前一项k值

	while (i<lensub) {

		if (k==-1 || sub[i - 1] == sub[k]) {
			next[i] = k + 1;
			i++;
			k++;
		}
		else {
			k = next[k];
		}
	}
}
//kmp
int KMP(const char* str,const char* sub ) {

	assert(str!=NULL && sub!=NULL);
	int lenstr = strlen(str);
	int lensub = strlen(sub);
	if (lenstr == 0 || lensub == 0) return-1;

	int* next = (int*)malloc(sizeof(int) * lensub);
	assert(next != NULL);
	GetNext(sub,next);

	int i = 0;   //遍历主串
	int j = 0;   //遍历子串

	while (i<lenstr && j<lensub) {

		if (j==-1 || str[i] == sub[j]) {

			i++;
			j++;
		}
		else {
			j = next[j];
		}

		if (j >= lensub) {
			//找到了,返回下标
			return i - j;
		}
	}

	//没找到,返回-1
	return -1;
}

测试

printf("%drn", KMP("ababcedfdab", "abab"));    //0
printf("%drn", KMP("ababcedfdab", "ababf"));   //-1
printf("%drn", KMP("ababcedfdab", "edfd"));    //5

输出

在这里插入图片描述

原文地址:https://blog.csdn.net/qq_43422358/article/details/134679151

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任

如若转载,请注明出处:http://www.7code.cn/show_18193.html

如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱:suwngjj01@126.com进行投诉反馈,一经查实,立即删除

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注