串的一些个存储结构:
- 顺序存储结构的串
#define MAXSIZE 255
// 0下标位置的长度存放这个串的长度
typedef unsigned char String[MAXSIZE+1];
- 链式存储的串:
#define MAXSIZE 1024
typedef struct{
char *chars;
//定义一个串长度
int length;
}String;
- 块链式存储结构的串:一个或若干个字符形成一个块,并占用一个节点,串为这样的节点相连
// 每个块的大小
#define CHUNKSIZE 128
typedef struct Chunk{
char charArr[CHUNKSIZE];
struct Chunk *next;
}Chunk;
typedef struct{
Chunk *head,tail;
// 当前串长度
int curlen;
}String;
本篇博文关于串的操作都是 链式存储结构的。
串的操作
- 初始化一个串:
String* InitString(){
String *str = (String *)malloc(sizeof(String));
str->chars = (char *)malloc(sizeof(char) * MAXSIZE);
str->length = 0;
return str;
}
- 字符数组->串
void charArr2String(String *s,char *string){
int strLength = strlen(string);
if(strLength >= MAXSIZE){
s->chars = (char *)malloc(sizeof(char) * MAXSIZE * 2);
}
// 设置串的当前长度
s->length=strLength;
s->chars = string;
}
- 判断串是否为空
bool StringEmpty(String *str){
return str->length == 0;
}
- 截取子串
/*
从s串中指定位置开始取长度为len的字串,用sub返回。
sub:返回的字串
s:主串
pos:指定下标
len:要截取的长度
*/
bool SubString(String *sub,String *s,int pos,int len){
bool flag = pos < 0||pos >= s->length||len < 1||pos + len > s->length;
if(flag){
return false;
}
for(int i = pos; i < (pos + len); i++){
sub->chars[i-pos] = s->chars[i];
}
sub->chars[len] = '\0';
sub->length = len;
return true;
}
- 比较俩个串的大小
int StringCompare(String *s1,String *s2){
if(StringEmpty(s1)){
return -1;
} else(StringEmp2y(s2)){
return 1;
}
int i=0;
while(i < s1->length && i < s2->length){
if(s1->chars[i] == s2->chars[i]){
i++;
continue;
} else{
return s1->chars[i] - s2->chars[i];
}
}
return 0;
}
- 在主串s中第pos个字符后查找与t串相等的字串
int Index(String *s,String *t,int pos){
int i;
String *sub = InitString();
i = pos;
if(pos >= s->length || pos < 0){
return -1;
}
while(i <= s->length - t->length){
SubString(sub,s,pos++,t->length);
if(StringCompare(sub,t) == 0){
return i;
} else{
// 若取出的串不相等,继续
i++;
}
}
return -1;
}
- 在主串s中第pos个字符后查找与t串相等的字串(不考虑用串的其他操作,只用基本的数组来实现同样的算法)
int Index_Arr(String *s,String *t,int pos){
int j = pos;
int i = 0;
if(pos >= s->length || pos < 0){
return -1;
}
while(j < s->length && i< t->length){
if(s->chars[j] == t->chars[i]){
++i;
++j;
} else{
j = j-i+1;
i = 0;
}
}
if(i >= t->length){
return j - t->length;
}
return -1;
}
/*
不足:效率低
看这种情况:
s1:000000000000001
s2:001
串2的前两个字符,和串1的字符1之前都匹配,需要匹配13*3才可能匹配上,效率低下
所以就有几个牛人,构思出了一个算法(KMP算法),来提高效率。
*/
kmp算法代码实现:获取模式串的next数组
/*
通过循环,不断的将前缀的单个字符与后缀的单个字符进行比较,
若相等,则将其存储到数组中。改变记录前缀和后缀的下标变量。
不相等,将数组中的值给前缀的下标记录。
*/
void getNextArr(String str,int *nextArr){
// i表示前缀的单个字符,j表示后缀的单个字符
int i,j;
i = -1;
j = 0;
nextArr[0] = -1;
// 这个后缀的单个字符的下标是不会回溯的,只有前缀的单个字符的下标会进行回溯。
while(j < str.length - 1){
if(i == -1 || str.chars[i] == str.chars[j]){
nextArr[++j] = ++i;
} else {
// 若值不相等,则i值进行回溯
i = nextArr[i];
}
}
}
那么在使用kmp算法之后的index应该如何写呢
int Index_KMP(String S, String P, int pos){
int next[255];
getNextArr(P, next);
int i = pos; // S 的下标
int j = 0; // P 的下标
int s_len = S.length;
int p_len = P.length;
while (i < s_len && j < p_len){
if (j == -1 || S.chars[i] == P.chars[j]){
// P 的第一个字符不匹配或 S[i] == P[j]
i++;
j++;
}
else{
j = next[j];
// 当前字符匹配失败,退回到合适位置
}
}
if (j == p_len) // 匹配成功
return i - j;
return -1;
}
/*
那么这个算法有什么不足或者说缺陷呢:
在aaaabcdefghijk中找
串aaaaab
当出现不匹配(a和b)的时候,也就是next[j]等于5,
进行回溯,next[j]成了4,不匹配,继续回溯,直到第next[j]等于0。
b不相等的时候回溯了多次,因为前面5个字符都是相等的,那么这样一来之前的判断回溯就都是多余的了
*/
改进一下KMP模式匹配算法
void getNextValArr(String str,int *nextArr){
int i,j;
i = -1;
j = 0;
nextArr[0] = -1;
while(j < str.length - 1){
if(i == -1 || str.chars[i] == str.chars[j]){
// 修改的结果:
if (str.chars[++i] != str.chars[++j]){
// 当前字符和前缀字符不同的时候,当前的i为next在j位置的值。
nextArr[j] = i;
} else {
// 前缀和当前字符相同将前缀字符的nextArr的值赋值给nextArr在i位置的值
nextArr[j] = nextArr[i];
}
} else {
// 若值不相等,则i值进行回溯
i = nextArr[i];
}
}
}
/*
总结改进过的kmp:它是在计算花出next值的同时,
如果a位字符与它next值指向的b位字符相等,则该a位的nextArr就存储的b位的nextArr值
如果不等,则该a位的nextArr就是它自己a位的nextArr的值
*/
在两串中找出最大长度的共同串(可指定长度),若存在,返回这个串
void Intersection(String *res,String *s1, String *s2, int len){
if(StringEmpty(s1) ||StringEmpty(s2) || len > s1->length || len > s2->length || len == 0){
return;
}
// 从第i个位置开始拿子串
// 从第j个位置开始找
int i,j;
// 要取的字串长度
int tagetLen = s2->length;
// 存储s2的子串
String *s2sub = InitString();
// 给子串赋初始值
SubString(s2sub, s2, 0, tagetLen);
while(tagetLen >= len){
i = 0;
while(i <=s2->length - tagetLen){
SubString(s2sub,s2,i,tagetLen);
j = 0;
// 取出字串到s1串中查找
while(j <= s1->length - s2sub->length){
int index = Index_KMP(s1,s2sub,j++);
if(index != -1){
// 找到后进行赋值,然后结束循环
res->chars = s2sub->chars;
res->length = tagetLen;
return;
}
}
// 没找着从下一个位置开始拿子串
i++;
}
// 长度为tagetLen的所有字串在串s1中没找到,将要取的字符串长度自减
tagetLen--;
}
}
ps:一篇博文助你理解KMP