先说说最基础的字符串的数组存储表示: 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; }