前言:
我学习数据结构的方式是看书加看视频,视频看的是哔哩哔哩up主的数据结构-字符串暴力匹配我总结并补充他所讲的内容,他的视频适合有c语言基础的看。
一、初始化字符串结构体
通过顺序存储方式进行初始化,结构体里定义了char型指针来代表数组的首地址,定义了length来确定串的长度。
//初始化字符串
typedef struct String
{
char* data;
int length;
}String;
二、创建空串
定义一个字符串结构体指针,为其开辟空间,初始化数据。
//创建空串
String* initString()
{
String* s = (String*)malloc(sizeof(String));
s->data = NULL;
s->length = 0;
return s;
}
三、给串分值
void stringAssign(String* s, char* arr) //传进字符串
{
if (s->data) {
free(s->data); //释放其地址
}
int len = 0; //创建len 计算 数组的长度
char* temp = arr;
while (*temp) { //计算加入字符串的长度 注意: *temp 解引用 指向字符元素的ascll表值 指向\0则结束循环
temp++;
len++;
}
if (len == 0) {
s->data = NULL; //传来的字符串为空
s->length = 0;
}
else {
temp = arr;//将temp重新指向data的初地址
s->length = len;
s->data = (char*)malloc(sizeof(char) * (len + 1));//"\0"也占内存 注意: 是char * 型 并用s.data 首地址来接收
for (int i = 0; i <= len; i++, temp++) {
s->data[i] = *temp;//指针数组
}
}
}
注意:c语言中,没有字符串,仍是char 型数组 只不过末尾自带了\0,仅此而已!
代码解释:
给串分值,传进一个字符串结构体指针地址和一个 char型符数组的地址。即主函数中调用函数是直接将一个字符串放入其中: stringAssign(s, "hello");(双引号表示,即使用了字符串,只是一个自带\0的char型数组)
1、在进行给字符串结构体分值在前,需要判断当前传进来的结构体指针的data指针(代表数组的首地址)是否有地址(即代表是否有数组了),如果有应该先释放其地址,因为我们后面还得为data指针重新开辟空间,并为data数组重新赋值。
2、用len和指针temp分别接收字符串(char型数组)的长度和地址。通过解引用解出char型数组地址中的每个元素(即字符隐式转换成int型用Asiica值表示),随着数组地址的增加,长度len也随之增加。当*temp = '\0'时,隐式转换成int型 为 0,while循环结束。
3、判断如果len长度为0,即代表传进来的字符串(char型数组)为空,否则就进行下一步,进行开辟空间和赋值操作。
4、将temp重新指向data的初地址,即数组的首地址,再给指针data进行开辟空间(因为是字符串,所以需要多加一个空间给\0存储)。开辟完空间,就用for循环来给data数组进行依次赋值。每赋完一个值,就更新i值和temp的地址。
四、暴力匹配
//暴力匹配
void forceMatch(String* master, String* sub)
{
int i = 0;
int j = 0;
while (i < master->length && j < sub->length)
{
if (master->data[i] = sub->data[j]) {
i++;
j++;
}
else {
i = i - j + 1;
//i从下标0出发 第一次 i ==j 第二次 i要从下标1出发 i-j+1 =1 ,第三次 i要从下标2出发,j返回下标0元素,i-j+1 = 2
j = 0; //不成功匹配 j只能等于0
}
}
//如果循环结束 j == sub.length 即为成功
if (j == sub->length) {
printf("force match success.\n");
}
else {
printf("force match fail. \n");
}
}
代码解释:
关键:每次匹配失败,指向主串的箭头会回溯,指向子串的箭头会重新回到起点。
如图:
传进主串和子串,进行逐一匹配,从下标0的元素开始匹配,若当前匹配成功,则继续i++,j++,进行下一个元素的匹配,前两次匹配均成功,第三次匹配时,主串和子串下标为2时,主串元素为A,子串为C,匹配失败,将主串进行回溯。每次回溯,主串进行匹配的初始下标(每次进行匹配时的初始下标)需要更新,即在原来初始下标基础上加1,子串仍要从下标0开始,此时成为下一次匹配时 新的 i 和 j 的初始值。
那么怎么快速的表达出发生不匹配后,下一次匹配时主串更新后的初始下标的值为多少呢 解决方法:主串更新后的初始下标值 = 将发生不匹配的当前下标进行相减再加一。
解释:发生不匹配时:i - j 的值 = 这一次进行匹配的主串初始下标,下一次匹配时,更新主串的初始下标需要让这一次初始下标往前多走一步,即 i - j 再 + 1 。
找到当前发生不匹配的下标i和j,即主串i = 2和子串j = 2,进行相减此时的值为这次主串的初始值下标 0,再+1为更新后的初始下标 1 即(i = 2 - 2 -1 = 1) 。继续下一轮匹配 主串初始下标 i = 1,子串初始下标 j = 0,主串元素为B,子串为A,匹配失败,继续回溯。
找到当前发生不匹配的下标i和j,即主串i = 1和子串j = 0,进行相减此时的值为这次主串的初始值下标 1,再+1为更新后的初始下标 2 即(i = 1 - 0 + 1 = 2)。继续下一轮匹配 主串初始下标 i = 2,子串初始下标 j = 0,主串元素为A,子串为A,匹配成功,继续下一轮匹配,匹配成功,最后一轮匹配,匹配成功。此时 j = 子串的长度,即代表匹配成功。
五、遍历
void printString(String* s)
{
for (int i = 0; i < s->length; i++) {
printf(i == 0 ? "%c" : "->%c", s->data[i]);
}
printf("\n");
}
六、完整代码
#include<stdio.h>
#include<stdlib.h>
//初始化字符串
typedef struct String
{
char* data;
int length;
}String;
//创建空串
String* initString()
{
String* s = (String*)malloc(sizeof(String));
s->data = NULL;
s->length = 0;
return s;
}
//给串分配赋值
void stringAssign(String* s, char* arr) //传进字符串
{
if (s->data) {
free(s->data); //释放其地址
}
int len = 0; //创建len 计算 数组的长度
char* temp = arr;
while (*temp) { //计算加入字符串的长度 注意: *temp 解引用 指向字符元素的ascll表值 指向\0则结束循环
temp++;
len++;
}
if (len == 0) {
s->data = NULL; //传来的字符串为空
s->length = 0;
}
else {
temp = arr;//将temp重新指向data的初地址
s->length = len;
s->data = (char*)malloc(sizeof(char) * (len + 1));//"\0"也占内存 注意: 是char * 型 并用s.data 首地址来接收
for (int i = 0; i <= len; i++, temp++) {
s->data[i] = *temp;//指针数组
}
}
}
//暴力匹配
void forceMatch(String* master, String* sub)
{
int i = 0;
int j = 0;
while (i < master->length && j < sub->length)
{
if (master->data[i] = sub->data[j]) {
i++;
j++;
}
else {
i = i - j + 1;
//i从下标0出发 第一次 i ==j 第二次 i要从下标1出发 i-j+1 =1 ,第三次 i要从下标2出发,j返回下标0元素,i-j+1 = 2
j = 0; //不成功匹配 j只能等于0
}
}
//如果循环结束 j == sub.length 即为成功
if (j == sub->length) {
printf("force match success.\n");
}
else {
printf("force match fail. \n");
}
}
//遍历
void printString(String* s)
{
for (int i = 0; i < s->length; i++) {
printf(i == 0 ? "%c" : "->%c", s->data[i]);
}
printf("\n");
}
int main()
{
String * s = initString();
String * s1 = initString();
String * s3 = initString();
stringAssign(s, "hello");
stringAssign(s1, "lo");
printString(s);
printString(s1);
forceMatch(s, s1);
return 0;
}
七、运行结果
总结
暴力匹配原理是指在字符串匹配过程中,通过对目标字符串中的每个字符与模式字符串中的每个字符依次进行比较,来确定匹配位置的一种简单的匹配方法。这种方法要求从目标字符串的每个位置开始,不断地与模式字符串进行匹配,直到匹配成功或者字符串结束。
暴力匹配原理的时间复杂度较高,最坏情况下需要比较目标字符串中的每个字符和模式字符串中的每个字符,因此其时间复杂度为 O(mn),m 和 n 分别为目标字符串和模式字符串的长度。因此,在实际应用中,一般会采用一些更加高效的字符串匹配算法,例如 KMP 算法、Boyer-Moore 算法等,来加速字符串的匹配过程。
制作不易,真心想让你懂,还是有不足的地方,望见谅嘞。