数据结构与算法-字符串与字符串匹配算法

先说说最基础的字符串的数组存储表示: C语言中顺序串的存储分配可分为两种:静态分配的数组表示和动态分配的数组表示

#define maxSize256
typedef char SeqString[maxSize];

长度定义为256,实际只能存储255个字符(最后用“\0”表示串值终结) 如需要记录字符串当前实际字符个数,则:

#define maxSize256
typedef struct{
   char ch[maxSize];
   int curLength;
} SeqString;

这种字符串表示简单,但存储空间是在程序编译时静态分配的,放满再存会产生溢出。数组空间不能扩展,程序中止。 可以使用new、delete等动态存储管理的函数,根据实际需要动态的分配和释放字符串的存储空间。 这样定义的顺序串类型也有两种形式:

typedef char *SeqString;

或者

#define MaxSize256
typedef struct{
   char *ch;
   int curLength;
} SeqString;

在创建字符串时可用new操作动态分配该字符串的存储空间

ch = new char[maxSize];
if(ch==NULL) exit(1);

这种存储方式处理简单,但预先定义了数组大小,不能适应扩展空间需要

#define maxSize256
typedef struct{
   char *ch;
   int maxSize;
   int curLength;
} SeqString;

在初始化时进行动态存储分配,对结构定义中所有数据成员赋值

void initString(SeqString s){
    s.ch = new char[defaultSize];
    if(s.ch==NULL) exit(1);
    s.ch[0] = ‘\0’;
    s.maxSize = defaultSize;
    s.curLength = 0
}

当数组空间放满,对其进行成倍扩充

#include<stdlib.h>
void overflowProcess(){
   char *newAddress = newchar[2*maxSize];
   if(newAddress==NULL) {cerr<<“Memory Allocation Error”<<endl;exit(1);}
   intn = maxSize = 2*maxSize;
   char *srcptr = ch;
   char *desptr = newAddress;
   while(n—) *desptr++ = *srcptr++;
   delete []ch;
   ch = newAddress;
}

 
string与string.h的区别: 标准C中是不存在string类型的,string是标准C++扩充字符串操作的一个类。而C++的string类操作对象是string类型字符串,该类重装了一些运算符,添加了一些字符串操作成员函数,使得操作字符串更加方便。有的时候我们要将string串和char*串配合使用,所以也会涉及到这两个类型的转化问题。 标准C中有string.h这个头文件,string.h这个头文件中定义了一些我们经常用到的操作字符串的函数,如:strcpy、strcat、strcmp等等,但是这些函数的操作对象都是char*指向的字符串。   
<string.h>常用方法:

  • 字符串复制:int strcpy(char *string1 , char *string2),把字符串string2的内容复制给string1,如果string1有内容则会被覆盖。如果string1点长度更大,则只覆盖前面部分。
  • 字符串部分复制:int strncpy(char *string1 , char *string2 , int n),覆盖前n个字符。
  • 字符串连接:int strcat(char *string1 , char *string2),连接字符串string2到string1后面。string2原内容保持不变。
  • 将特定数量字符串连接到另一字符串:int strncat(char *string1 , char *string2 , int n)
  • 预先配置内存,将字符串存入该内存中:char* _strdup(char*string1),为字符串string1分配内存空间并将其存入其中,返回值add为指向该内存开始地址点指针,数据类型为*char
  • 在给定字符串中搜索制定字符:char* strchr(char *string1 , char ch),返回指向字符ch的指针,若搜索失败则返回NULL
  • 在给定字符串中搜寻某个指定字符第一次出现的位置:int strcspn(char *string1 , char ch),从0开始计数。
  • 在给定字符串中搜寻某个指定字符最后一次出现的地址:int* strchr(char*string1,charch)
  • 在两个字符串中寻找首次出现的共同字符:char* strpbrk(const char *string1 , const char *string2),返回该字符在string1中的地址
  • 在两个字符串中寻找首次共同出现的公共子字符串:char* strata(const char *string1 , const char *string2),返回该子字符串第一个字符在string1中的地址。
  • 计算字符串的长度:int strlen(const char *string1)
  • 在给定的字符串中按指定数目将若干字符置换为指定字符:char*  _strnset(char *string1 , char ch , int m),将开始的m个字符全部设定为ch
  • 字符串比较大小:int strcmp(char * string1 , char *string2)

 
几种常见的字符串匹配算法:

#include <cstring>
#include <cstdio>
void search(char *pat,char *txt){
    int M=strlen(pat);
    int N=strlen(txt);
    for(int i=0; i<N-M; i++){
        int j;
        for(j=0; j<M; j++)
            if(txt[i+j]!=pat[j]) break;
        if(j==M) printf("Pattern found at index %d /n",i);
    }
}

外层循环执行N-M+1次,内层循环执行M次,时间复杂度为O((N-M+1)*M) 2.KMP(Knuth-Morris-Pratt)算法 若求到next[q]=k,则前q+1个字符组成的字符串,相同的最长前缀和最长后缀长度为k+1 next[0]一定为-1(只有第一个字符的字符串,不存在相同的最长前缀和最长后缀) 求到next[q]=k后,若第k+2个字符ptr[k+1]和第q+2个字符是一样的,那么next[q+1]=k+1 如果是不一样的,k就变成next[k](next[k]必定是小于k的),直到k最后一直循环到-1为止,再比较若第k+2个字符ptr[k+1]和第q+2个字符是否是一样的 搜索的时候,如果出现str[s_i]和ptr[p_i]不一样,如果这时候p_i还是0那么自然s_i继续往前就行了;如果P_i已经不是0了,s_i就不用往前了,因为重合的那部分字符串前缀后缀有一部分是一样的,直接p_i=next[p_i-1]+1即可

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void cal_next(char *ptr, int *next, int plen) {
    next[0]=-1;     
    int k=-1;
    for (int q=1; q<=plen-1; q++) {
        while (k>-1 && ptr[k+1]!=ptr[q]) k=next[k];
        if (ptr[k+1]==ptr[q]) k=k+1; 
        next[q]=k;
    }
}
int KMP(char *str, int slen, char *ptr, int plen, int *next){   //在str中寻找ptr
    int s_i=0, p_i=0;
    while (s_i<slen && p_i<plen){
        if (str[s_i]==ptr[p_i]){
            s_i++;
            p_i++;
        }
        else {
            if(p_i==0) s_i++;
            else p_i=next[p_i-1]+1;
        }
    }
    return (p_i==plen)?(s_i-plen):-1;
}

看到好多博客上说k = next[k]可以用k–代替,但这样是不对的。
例如acceaccc,求到next[6]=2后,用这种方法会求出next[7]=1,实际上next[7]=-1
因为相同的最长前缀和最长后缀长度为k+1
,并不代表这k+1个长度的字符串随便从前从后取一串都是匹配的。只有从前从后取next[k]+1长度的字符串,才是一定前后匹配的。如,最大acceacc长度为3的字符串前后匹配都是acc,并不代表长度为2的字符串ac和cc是前后匹配的。只有next[2]长度的字符串才是一定前后匹配的。 3.BM(Boyer-Moore)算法 参考以下博客
https://blog.csdn.net/frostime/article/details/78294658?locationNum=1&fps=1

int bmMatch(const string & text, const string & pat){
    int *bc = getBc(pat);
    int *gs = getGs(pat);
    //patAt指向了当前pat和text对齐的位置
    int patAt = 0;
    //cmp指向了当前比较的位置
    int cmp;
    const size_t PATLASTID = pat.length() - 1;
    const size_t patLen = pat.length();
    const size_t textLen = text.length();
    while (patAt + patLen <= textLen){
        //如果匹配成功,cmp就会来到-1的位置上
        //patAt + cmp 指向了text上当前比较的字符
        for (cmp = PATLASTID; cmp >= 0 && pat[cmp] == text[patAt + cmp]; --cmp);     
        if (cmp == -1)
            break;
        else{
            patAt += max(gs[cmp], cmp - bc[text[patAt + cmp]]);
        }
    }
    delete []bc;
    delete []gs;
    return (patAt + patLen <= textLen)? patAt : -1;
}
int *getBc(const string& pattern){
    //坏后缀情况下建立bc表
    int *bc = new int[256];  //256是字符表的规模大小(ACSII)
    int len = pattern.length();
    for (int i = 0; i < 256; ++i) bc[i] = -1;   //坏字符不存在时,为-1  
    for (int i = 0; i < len; ++i) bc[pattern[i]] = i;
    return bc;
}
int *suffixes(const string& pat){
    //好后缀情况下构建gc表记录每次需要移动的距离比较困难  
    const int len = pat.length();
    int num;
    int *suff = new int[len];  //辅助表suffix[i]=x表示以i为边界向左,与模式串后缀匹配的最大长度
    suff[len - 1] = len;
    for (int i = len - 2; i >= 0; —i){
        for (num = 0; num <= i && pat[i-num] == pat[len-num-1]; ++num);
        suff[i] = num;
    }
    return suff;
}
int *getGs(const string& pat){
    //构建gc[i]表,记录遇到好后缀时模式串需要移动的距离,i表示好后缀左侧第一个坏字符
    const int len = pat.length();
    const int lastIndex = len - 1;
    int *suffix = suffixes(pat);
    int *gs = new int[len];
    for (int i = 0; i < len; ++i) gs[i] = len; //情况一:找不到对应的子串和前缀
    //找前缀
    for (int i = lastIndex; i >= 0; --i) //情况二:存在我们想要的前缀
        if (suffix[i] == i + 1)
            for (int j = 0; j < lastIndex - i; ++j)
                if (gs[j] == len) gs[j] = lastIndex - i;
    for (int i = 0; i < lastIndex; ++i) gs[lastIndex - suffix[i]] = lastIndex - i; //情况一:找中间的匹配子串
    delete []suffix;
    return gs;
}

 

    原文作者:扬羽流风
    原文地址: https://www.cnblogs.com/yangyuliufeng/p/9989782.html
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞