这是我参与11月更文挑战的第3天,活动详情查看:2021最后一次更文挑战
1.串
由0个或多个字符组成的序列
2.存储结构(C语言)
- 定长顺序存储(顺序存储结构)但串长是固定的
#define MAXSIZE 100 //定义长度为100
typedef char {
char ch[MAXSIZE];
int length;
} SString;
- 堆分配存储(顺序存储结构)但串长是动态变化的
typedef struct {
char * ch; //ch指向串的基地址
int length;
} SString;
//当我们使用堆分配存储时 我们需要动态的分配内存空间
(char*)malloc(i*sizeof(char)) //分配串空间
- 块链存储表 这样存储密度高
#define SIZE 4
typedef struct StringNode{
char ch[SIZE]; //每一个结点存多个字符
struct StringNode *next;
}StringNode,*Node;
3.KMP算法(模式匹配算法)
- 基础知识 子串:串中任意个连续的字符组成的子序列
主串:包含字串的串
前缀集:除去最后一个字符后,一个字符串的全部头部组合 例如:aba的前缀集:{a,ab};
后缀集:指除了第一个字符以外,一个字符串的全部尾部组合 例如:aba的后缀集:{a,ba};
//求模式串的next数组
void getNext(SString T,int next[]){
int i=1,j=0;
next[1] = 0;
while(i < T.length){
if(j==0 || T.ch[i] == T.ch[j]) {
i++;
j++;
next[i] = j;
}else{
j=next[j];
}
}
//KMP算法 其中S时主串 T是模式串
int KMP(SSTring S,SString T) {
int i=1,j=1;
int next[T.length+1];
getNext(T,next); //获取模式串的next数组
while(i<=S.length && j<=T.length){
if(j=0 || S.ch[i] == T.ch[j]){
i++;
j++;
}else {
j=next[j];
}
}
if(j>T.length) {
return i-T.length;
}else {
return 0;
}
}
而在KMP算法中,与传统的模式匹配算法相比,传统的模式匹配算法是在主串有一个指针i 在子串有一个指针j 若S.ch[i]≠T.ch[j] 两个指针就要都要回溯 而KMP算法的优化就在 只让j指针回溯 i指针不动。那么问题就来了,j指针回溯到那个位置呢?这就是我们要求next数组的意义了 所以KMP算法的关键就要理解next数组是如何求出的 从上面getNext这个方法直接去看代码是很难一下就看懂的,我结合我看的视频来分享一下我的理解:
- 首先我们规定next[1] = 0;
- 在最理想的情况下:next[j+1] = next[j]+1; 但如果在不理想的情况下 Pk ≠ Pj 那么next[j+1]他可能第二个最大值是next[next[j]]+1 依次类推 这其中就有一个思想(递推)
- 流程: 1)求next[j+1] (求j=5 即next[6])
2)已知next[j]=k,则有P1…Pk-1 = Pj-k+1…Pj-1 (next[5]=k=2,P[1] = P[4])
3)如果Pk=Pj,则P1…Pk-1PK = Pj-k+1…Pj-1Pj,则next[j+1]=k+1,否则 (p[2] = p[5] 则next[6]=next[5]+1-3)
4)已知next[k]=k2,则有P1…Pk2-1 = Pk-k2+1…Pk-1
5)第二第三步联合得到:P1…Pk2-1 = Pk-k2+1…Pk-1 = Pj-k+1…Pk2-k+j-1 = Pj-k2+1…Pj-1 即四段重合
6)Pk2=Pj,则P1…Pk2-1P~k2 = Pj-k2+1…Pj-1Pj,则next[j+1]=k2+1;
| j | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
| T | a | b | a | a | b | c | a | c |
| next[j] | 0 | 1 | 1 | 2 | 2 | 3 | 1 | 2 |
在上面的基础之上再去理解这段代码 就很好理解了