C语言数据结构串【BF\KMP算法】

  • 感谢UP主:凡三岁爱学习,就我等于苦海之中
  • 希望更多的同胞可以看到本篇文章,有空给up主加油呀!
    请添加图片描述

  • 定义:零个或者多个任意字符组成的有限序列
s="xxxxxx";//n>=0
  • s:串名
  • “xxxxx”:串值
  • n:串值【n=0 空串】

1 基本概念

  • 子串:一个串中任意个连续字符组成的子序列【含空串】称为该串的子串。
  • 主串:包含字串的串相应地称为主串
  • 字符位置:字符在序列中的序号为该字符在串中的位置
  • 字串位置:字串第一个字符在主串中的位置。
  • 空格串:有一个或多个空格组成的串【与空串不同】
  • 串相等:当且仅当2个串的长度相等,并且各个对应位置上的字符都相同时,这2个字符串才是相等的。
    • 所有的空字符串是相等的。

2 串的类型定义

在这里插入图片描述
在这里插入图片描述

3 串的字符结构

在这里插入图片描述

  • 串的顺序存储结构
#define MAXLEN 255
typedef struct {
	char ch[MAXLEN+1];//存储串的一维数组
	int length;//串的当前长度
} SString;

在这里插入图片描述

  • 串的链式存储结构–块链结构
#define CHUNKSIZE 80
typedef struct Chunk {
	char ch[CHUNKSIZE];
	struct Chunk *next;
}
typedef struct {
	Chunk *head,*tail;//串的头指针和尾指针
	int curlen;//串的当前长度
}LString;//字符串的块链结构

4 串的模式匹配算法

  • 目的:主串中所含字串(模式串)第一次出现的位置(定位)。

4.1 BF[Brute-Force]算法

  • 古典的,经典的,朴素的,穷举的算法。
  • Index(S,T,pos);
    • 将主串中的第pos个字符和模式串的第一个字符比较。
      • 若相等,继续逐个比较后面的字符。
        • pos的返回位置:i-t.length=3;
      • 若不相等,从主串的下一个字符开始,重新与模式串的第一个字符比较。
        • 回退的公式:i-(j-1)+1=i-j+2
    • 匹配成功,返回S串中匹配成功的第一个字符的序号:否则,返回值为0。
      在这里插入图片描述

在这里插入图片描述

4.1.1 案例代码一
//
// Created by HP on 2022/10/28.
//
#include <iostream>
#include <stdlib.h>
using  namespace std;
#define MAXLENGTH 255
typedef struct {
    char ch[MAXLENGTH + 1];//存储串的一维数组
    int length;//串的当前长度
} SString;

int Index_BF(SString S,SString T,int pos) {
    int i=pos,j=1;//Pos,确定此次匹配开始的位置。
    while (i<=S.length && j<=T.length) {
        if(S.ch[i]==T.ch[j]) {//比较的2个字符相同开始下一次的匹配。
            i++;
            j++;
        }
        else {//主串、子串指针回溯重新开始下一次匹配。
            i=i-j+2;
            j=1;
        }
    }
    if(j>=T.length) {
        return i-T.length;//匹配成功返回匹配的第一个字符的下标。
    }
    else return 0;//模式匹配不成功。
}

int main() {
    SString s1;
    int len,option;
    cout<<"请输入主串的字符个数:";
    cin>>len;
    s1.length=len;
    cout<<"请输入主串的内容:";
    for(int i=1;i<=len;i++) {
        cin>>s1.ch[i];
    }
    SString s2;
    cout<<"请输入模式串的字符个数:";
    cin>>len;
    s2.length=len;
    cout<<"请输入模式串的内容:";
    for(int i=1;i<=len;i++) {
        cin>>s2.ch[i];
    }
    cout<<"请输入开始匹配的位序:";
    cin>>option;
    int res=Index_BF(s1,s2,option);
    cout<<res<<endl;
}

在这里插入图片描述

4.1.2 案例代码二

//
// Created by HP on 2022/10/28.
//
#include <iostream>
#include <stdlib.h>
using  namespace std;

typedef struct String{
    char* data;
    int len;
}String;
String* InitString() {
    String *S=(String*)malloc(sizeof(String));
    S->data=NULL;
    S->len=0;
};
void stringAssign(String* s,char* data) {
    //如果s的指向不为空,就将s释放,方便下面对其进行赋值
    if(s->data) {
        free(s->data);
    }
    //统计data字符串的长度,但要注意不要改变data的指向,所以定义了一个临时指针
    int len=0;
    char* temp=data;
    while(*temp) {
        len++;
        temp++;
    }
    //如果输入的时只有一个空格的空格串,就做相应的处理
    //否则,利用temp指针将字符串赋值给s->data
    if(len==0) {
        s->data=NULL;
        s->len=0;
    }
    else {
        temp=data;
        s->len=len;
        //开辟的长度要加1,因为要保存最后的‘\0’字符
        s->data=(char*)malloc(sizeof(char) *(len+1));
        for(int i=0;i<len;i++,temp++) {
            s->data[i]=*temp;
        }
    }
}

void printString(String* s) {
    for(int i=0;i<s->len;i++) {
        printf(i==0 ? "%c":"-> %c",s->data[i]);
    }
    cout<<endl;
}

void forceMatch(String *master,String *sub) {
    int i=0,j=0;
    while(i<master->len && j<sub->len) {
        if(master->data[i]==sub->data[j]) {
            i++;
            j++;
        }
        else {
            i=i-j+2;
            j=0;
        }
    }
    if(j==sub->len) {
        cout<<"success"<<endl;
    }else {
        cout<<"fail"<<endl;
    }
}

int main() {
    //对字符串进行初始化
    String *s1=InitString();
    String *s2=InitString();
    int len;
    cout<<"请输入主串的字符个数:";
    cin>>len;
    cout<<"请输入主串的内容:";
    char arr[len+1];
    arr[len]='\0';
    for(int i=0;i<len;i++) {
        cin>>arr[i];
    }
    cout<<"请输入模式串的字符个数:";
    cin>>len;
    cout<<"请输入模式串的内容:";
    char arr2[len+1];
    arr2[len]='\0';
    for(int i=0;i<len;i++) {
        cin>>arr2[i];
    }
    stringAssign(s1,arr);
    stringAssign(s2,arr2);
    printString(s1);
    printString(s2);
    forceMatch(s1,s2);
    return 0;
}

在这里插入图片描述

4.2 KMP算法

  1. next数组定义:当主串与模式串的某一位字符不匹配时,模式串要回退的位置其值
  2. next[j]:第位字符前面j-1位字符组成的子串的前后重合字符数+1
4.2.-1 手算Next数组
  • 本文的手算方法,就是仅仅是看j前的字符串最长公共前后缀个数+1
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
4.2.0 图解

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • up主总结:
    • 看门牌算法
      在这里插入图片描述
4.2.1 案例代码一
//
// Created by HP on 2022/10/29.
//
#include <iostream>
using namespace std;
#define MAXSTRLEN 255//用户在255以内定义最大的串长
typedef unsigned char SString[MAXSTRLEN+1];//0号位置存放串的长度
void GetNext(int next[],SString S) {
    //求模式串的next数组函数值并存入数组next
    int i=1,j=0;
    next[i]=j;//默认next[1]=0;
    while(i<=S[0]) {
        if(j==0 || S[i]==S[j]) {
            next[++i]=++j;
        }else {
            j=next[j];
        }
    }
}

int IndexKmp(SString S,SString T,int pos,int next[]) {
    int i=pos,j=1;
    while(i<=S[0] && j<=T[0]) {
        if(j==0 || S[i]==T[j]) {
            i++;j++;
        }else {
            j= next[j];
        }
    }
    if(j>T[0]) {
        return i-T[0];
    }else {
        return -1;
    }
}
int main() {
    SString s1,s2;
    int nums,pos;
    cout<<"请输入字符的总个数:";
    cin>>nums;
    s1[0]=nums;
    cout<<"请输入主字符串:";
    for(int i=1;i<=nums;i++) {
        cin>>s1[i];
    }
    cout<<"请输入字符的总个数:";
    cin>>nums;
    s2[0]=nums;
    cout<<"请输入模式符串:";
    for(int i=1;i<=nums;i++) {
        cin>>s2[i];
    }
    int next[s2[0]+1];//多开辟一个,因为next的下标是从1开始存储的
    for(int i=1;i<=s2[0];i++) {
        printf(i==1 ? "%c":"-> %c",s2[i]);
    }
    GetNext(next,s2);
    cout<<endl;
    for(int i=1;i<=s2[0];i++) {
        printf(i==1 ? "%d":"-> %d",next[i]);
    }
    cout<<endl;
    cout<<"请输入开始匹配的序号:";
    cin>>pos;
    cout<<"匹配主串的首字母序号:"<<IndexKmp(s1,s2,pos,next);
    return 0;
}

在这里插入图片描述

4.2.2 案例代码二
//
// Created by HP on 2022/10/28.
//
#include <iostream>
using namespace  std;
#include <stdlib.h>

typedef struct String {
    char* data;
    int len;
} String;
String* InitString() {
    String* s=(String*)malloc(sizeof(String));
    s->data=NULL;
    s->len=0;
    return s;
}

void stringAssign(String* s,char* data) {
    //如果s的指向不为空,就将s释放,方便下面对其进行赋值
    if(s->data) {
        free(s->data);
    }
    //统计data字符串的长度,但要注意不要改变data的指向,所以定义了一个临时指针
    int len=0;
    char* temp=data;
    while(*temp) {
        len++;
        temp++;
    }
    //如果输入的时只有一个空格的空格串,就做相应的处理
    //否则,利用temp指针将字符串赋值给s->data
    if(len==0) {
        s->data=NULL;
        s->len=0;
    }
    else {
        temp=data;
        s->len=len;
        //开辟的长度要加1,因为要保存最后的‘\0’字符
        s->data=(char*)malloc(sizeof(char) *(len+1));
        for(int i=0;i<len;i++,temp++) {
            s->data[i]=*temp;
        }
    }
}

void printString(String* s) {
    for(int i=0;i<s->len;i++) {
        printf(i==0 ? "%c":"-> %c",s->data[i]);
    }
    cout<<endl;
}

int* getNext(String* s) {
    int* next=(int*)malloc(sizeof(int)*s->len);
    int i=0,j=-1;
    next[i]=j;
    while(i<s->len-1) {
        if(j==-1 || s->data[i]==s->data[j]) {
            i++;
            j++;
            next[i]=j;
        }else {
            j=next[j];
        }
    }
    return next;
}
void printNext(int* next,int len) {
    for(int i=0;i<len;i++) {
        printf(i==0 ? "%d":"-> %d",next[i]+1);
    }
    cout<<endl;
}

void KmpMatch(String* master,String* sub,int* next) {
    int i=0,j=0;
    while( i<master->len && j<sub->len) {
        if(j==-1 || master->data[i]==sub->data[j]) {
            i++;
            j++;
        }else {
            j= next[j];
        }
    }
    if(j==sub->len) {
        cout<<"match success!"<<endl;
    }else {
        cout<<"match fail!"<<endl;
    }
}

int main() {
    //对字符串进行初始化
    String *s1=InitString();
    String *s2=InitString();
    int len;
    cout<<"请输入主串的字符个数:";
    cin>>len;
    cout<<"请输入主串的内容:";
    char arr[len+1];
    arr[len]='\0';
    for(int i=0;i<len;i++) {
        cin>>arr[i];
    }
    cout<<"请输入模式串的字符个数:";
    cin>>len;
    cout<<"请输入模式串的内容:";
    char arr2[len+1];
    arr2[len]='\0';
    for(int i=0;i<len;i++) {
        cin>>arr2[i];
    }
    stringAssign(s1,arr);
    stringAssign(s2,arr2);
    printString(s1);
    printString(s2);
    int *next= getNext(s2);
    printNext(next,len);
    KmpMatch(s1,s2,next);
    return 0;
}

在这里插入图片描述

4.3 KMP算法优化

4.3.1 问题·引子

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

//
// Created by HP on 2022/10/29.
//
#include <iostream>
using namespace std;
#define MAXSTRLEN 255//用户在255以内定义最大的串长
typedef unsigned char SString[MAXSTRLEN+1];//0号位置存放串的长度
// void GetLength(SString &S) {
//     int length=0;
//     unsigned char *temp=&S[1];
//     while(*temp) {
//         temp++;
//         length++;
//     }
//     S[0]=length;
// }
void GetNext(int next[],SString S) {
    //求模式串的next数组函数值并存入数组next
    int i=1,j=0;
    next[i]=j;//默认next[1]=0;
    while(i<=S[0]) {
        if(j==0 || S[i]==S[j]) {
            next[++i]=++j;
        }else {
            j=next[j];
        }
    }
}

void GetNextVal(int nextVal[],SString S) {
    //求模式串的next数组函数值并存入数组next
    int i=1,j=0;
    nextVal[i]=j;//默认next[1]=0;
    while(i<=S[0]) {
        if(j==0 || S[i]==S[j]) {
            ++i;
            ++j;
            if(S[i]!=S[j]) {
                nextVal[i]=j;
            }else {
                nextVal[i]=nextVal[j];
            }
        }else {
            j=nextVal[j];
        }
    }
}

int IndexKmp(SString S,SString T,int pos,int next[]) {
    int i=pos,j=1;
    while(i<=S[0] && j<=T[0]) {
        if(j==0 || S[i]==T[j]) {
            i++;j++;
        }else {
            j= next[j];
        }
    }
    if(j>T[0]) {
        return i-T[0];
    }else {
        return -1;
    }
}
int main() {
    SString s1,s2;
    int nums,pos;
    cout<<"请输入字符的总个数:";
    cin>>nums;
    s1[0]=nums;
    cout<<"请输入主字符串:";
    for(int i=1;i<=nums;i++) {
        cin>>s1[i];
    }
    //GetLength(s1);
    cout<<"请输入字符的总个数:";
    cin>>nums;
    s2[0]=nums;
    cout<<"请输入模式符串:";
    for(int i=1;i<=nums;i++) {
        cin>>s2[i];
    }
    //GetLength(s2);
    int next[s2[0]+1];//多开辟一个,因为next的下标是从1开始存储的
    //next[0]=s2[0];//记录长度
    for(int i=1;i<=s2[0];i++) {
        printf(i==1 ? "%c":"-> %c",s2[i]);
    }
    cout<<endl;
    GetNext(next,s2);
    for(int i=1;i<=s2[0];i++) {
        printf(i==1 ? "%d":"-> %d",next[i]);
    }
    cout<<endl;
    cout<<"请输入开始匹配的序号:";
    cin>>pos;
    cout<<"匹配主串的首字母序号:"<<IndexKmp(s1,s2,pos,next)<<endl;

    cout<<"======================"<<endl;

    GetNextVal(next,s2);
    for(int i=1;i<=s2[0];i++) {
        printf(i==1 ? "%c":"-> %c",s2[i]);
    }
    cout<<endl;
    for(int i=1;i<=s2[0];i++) {
        printf(i==1 ? "%d":"-> %d",next[i]);
    }
    cout<<endl;
    cout<<"请输入开始匹配的序号:";
    cin>>pos;
    cout<<"匹配主串的首字母序号:"<<IndexKmp(s1,s2,pos,next);
    return 0;
}

在这里插入图片描述
请添加图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值