串和KMP
串:有限的字符序列,告知空间首地址,约定一个结束标志。
不同语言的串设计不同,在数据结构中脱离语言给出一个新的串的定义
// 串头 typedef struct{ char *str; int length; }StrType; int strAssign(StrType *str,const char *ch){ if(str-str){ //str中已有数据,先释放再通过ch赋值 free(str->str); } // 求ch的长度 int len = 0; while(ch[len]){ ++len; } if(len==0){ str->str=NULL; str->len=0; }else{ // 多申请两个空间,把\0和首地址空出来,首地址存放监视哨 str->str=malloc(sizeof(char)*(len+2)); for(int i=0;i<=len;++i){ str->str[i+1]=ch[i]; } str->length=len; } return 1; }
-
暴力匹配
字符串模式匹配:在主串中找到与模式串相同的子串并返回其首个元素所在位置
时间复杂度:
O(mn),时间复杂度高// 从1号开始(0号存放了监视哨) // 假设法:主串两个指针(k是假设正确的位置,i用来从k开始遍历验证的指针), // 模式串一个指针(j是模式串的指针) int index_simple(const StrType *str,const StrType *substr){ int i=1; int j=1; int k=i; // 记录假设值 while(i<=str->length && j<=subStr->length){ if(str->str[i]==substr->str[j]){ ++i; ++J }else{ ++k; j=1; i=k; } } // 说明与模式串完全匹配 if(j>subStr->length){ return k; } return 0; } -
KMP
只回溯模式串,不一定是回溯到==1,比较指针i不回溯
next数组:当主串与模式串的某一个字符不匹配时,模式串要回退的位置专门用于存放子串失配后从模式串哪个位置进行比较。当失效点出现时,要回退到哪个位置,就是next数组存放的元素
-
next[j] = 第 j 个字符前面 j-1位字符组成的子串的前后缀重合字符数 + 1
-
next[0]=next[1]=1
例:
a b a a b c |---------- | | j-1 j
前后缀都不包含自己本身,因为包含本身的话前后缀最大公共序列就是本身了
-
前缀:a ab aba abaa
-
后缀:b ab aab baab
-
void getNext(const StrType *substr,int next[] ){
int i=1,j=0; // i表示当前处理到的字符位置,j表示前后缀匹配的长度
next[1]=0; // next[1]永远是0,因为第一个字符前面没有前缀
while(i < substr->length){
// 若j==0,说明退无可退重新回到最开始的位置进行匹配
// 若substr->str[i] == substr->str[j]则说明前后缀匹配
// 更新i、j并且更新next[j]=j
if(j==0 || substr->str[i] == substr->str[j]){
++i;
++j;
next[i]=j;
}else{
j=next[j]; // 若不满足则回退回原先记录的最大公共序列数
}
}
}
int indexKMP(const StrType *str,const StrType *substr){
int i=1;
int j=1;
while(i<=str->length && j<=substr->length){
if(j==0 || str->str[i] == substr->str[i]){
i++;
j++;
}else{
j=next[j];
}
if(j>substr->length){
return i-substr->length;
}
return 0;
}
}
假设模式串是 "abab"
| i | j | 当前匹配 | next[j] 说明 |
|---|---|---|---|
| 1 | 0 | a vs - | j==0 → next[1]=0 |
| 2 | 1 | b vs a | 不匹配 → j=next[1]=0 |
| 2 | 0 | b vs - | j==0 → i=3, j=1, next[2]=1 |
| 3 | 1 | a vs a | 匹配 → i=4, j=2, next[3]=2 |
| 4 | 2 | b vs b | 匹配 → i=5, j=3, next[4]=3 |
🪄疑问:为什么不需要去枚举前后缀所有的结果最后找最大公共序列?
答:
因为上述算法在求next[i]时,服用了前面已经算过的结果
利用了动态递推
代码中两个指针 i 和 j 的含义
在下面这行逻辑里:
if (j == 0 || substr->str[i] == substr->str[j]) { ++i; ++j; next[i] = j; } else { j = next[j]; }
i:当前要扩展的位置(新字符)
j:当前前后缀相等的“长度”当我们试图把
P[i]加入已有匹配的前缀后缀时:
如果
P[i] == P[j]: 那么前后缀匹配长度就能再多 1 → 更新next[i+1] = j+1如果
P[i] != P[j]: 说明现在这个后缀不行了 → 但也许更短的后缀可以匹配 → 所以回退到next[j]再尝试j = next[j];(这一步就体现了“复用之前的匹配结果”)
把算法思想一句话总结:
这段代码通过在遍历中动态维护“前后缀相等长度 j”, 复用之前计算过的 next 信息,避免了重新枚举全部前缀后缀, 实现了“线性时间”求出最长公共前后缀长度数组。
2840

被折叠的 条评论
为什么被折叠?



