串的匹配的应用场景
例如:
- 查找子串问题
- 说一篇文章里搜索一条词句
- 论文查重
匹配算法介绍
一般都是这两种算法法来匹配字符串,BF算法与KMP算法。
BF算法
原理:匹配串与主串按字符两两匹配;相同时,继续按字符匹配;不相同时,主串回溯到匹配开始加1的位置,匹配串回溯到起始位置。重复以上步骤,完整的匹配到子串,则表示成功,反之失败。
废话不多说,直接上代码。
#include <stdio.h>
#include <string.h>
int deal_pos (int pos, int slen) {
return (pos + slen) % slen;
}
/**
* return -1 error, else ok.
*/
int index_BF (char *search, char *pattern, int pos) {
int slen = strlen(search), plen = strlen(pattern);
int i = deal_pos(pos, slen), j = 0;
// 此种方式利于查找所有位置的算法改进
// while ( i < slen ) {
// if ( search[i] == pattern[j] ) {
// if (j == plen-1) {
// return i-j;
// }
// i++;
// j++;
// } else {
// i = i - j + 1;
// j = 0;
// }
// }
//
// return -1;
//此种方式利于仅找第一个位置
while ( i < slen && j < plen ) {
if ( search[i] == pattern[j] ) {
i++;
j++;
} else {
i = i - j + 1;
j = 0;
}
}
if ( j == plen ) {
return i-j;
}
return -1;
}
int main() {
char s[] = "aabaaab";
char p[] = "aaab";
int index = index_BF (s, p, 0);
printf("search position index=%d\n", index);
return 0;
}
结果:
search position index=3
算法平均复杂度:O(n*m)
KMP算法
简介:KMP算法它是有Knuth、Morris和Pratt三个人同时发现的,所以我们称之为KMP算法。
原理:通过对模式串的一个预处理,求得回溯的位置,匹配不成功的时候,直接拿到回溯位置,然后继续比较,它的时间复杂度降到了线性水平。
推荐学习视频
KMP1 https://www.bilibili.com/video/BV1Px411z7Yo
KMP2 https://www.bilibili.com/video/BV1hW411a7ys
预处理即求出前缀表,里边存放的是回溯位置。
index | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|---|
pattern | A | B | A | B | C | A | B | A | A |
prifix-index | 0 | 1 | 1 | 2 | 0 | 1 | 2 | 3 | 1 |
求模式串的所有长度的最长的公共前后缀:
子串的最长的公共前后缀的长度 | 子串 |
---|---|
0 | A |
0 | AB |
1 | ABA |
2 | ABAB |
0 | ABABC |
1 | ABABCA |
2 | ABABCAB |
3 | ABABCABA |
1 | ABABCABAA |
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void move_prefix_table (int *prefix, int n);
void prefix_table(char *pattern, int *prefix, int n);
void prefix_table(char *pattern, int *prefix, int n) {
int len = 0, i = 1; //初始化最长前后公共前后缀的长度为0,从1开始比较
prefix[0] = 0; //初始化首个元素为 0
while (i < n) {
if ( pattern[i] == pattern[len] ) { //公共前后缀相同时 长度加1,继续计算下个子串
prefix[i++] = ++len; //比如说子串从 AB 变化到 ABA时或 ABA到ABAB
} else {
if ( len > 0 ) { //比如说从 ABAB 到ABABC时
len = prefix[len-1];
} else { //比如说从 A 到 AB 时,此条件忽略会陷入死循环
prefix[i++] = len;
}
}
}
move_prefix_table(prefix, n);
}
void move_prefix_table (int *prefix, int n) {
for (int i = n-1; i > 0; i--) {
prefix[i] = prefix[i-1];
}
prefix[0] = -1;
}
void kmp_search(char *text, char *pattern) {
int m = strlen(text);
int n = strlen(pattern);
int i = 0, j = 0;
int *prefix = (int *)calloc(n, sizeof(int));
prefix_table(pattern, prefix, n);
while (i < m ) {
if (j == n-1 && text[i] == pattern[j]) {
printf("Found pattern at %d\n", i - j); //打印匹配的位置
j = prefix[j]; //继续匹配的时候,拿到匹配串最后j的前缀回溯位置
}
if (text[i] == pattern[j]) { //匹配成功,继续匹配
i++;
j++;
} else {
j = prefix[j]; //匹配失败的,拿到匹配串最后j的前缀回溯位置,继续匹配
if (j == -1 ) { //当等于-1时,表示当前匹配位置的匹配串与主串都需向右移动一个字符
i++;
j++;
}
}
}
}
int main() {
// char pattern[] = "ababc";
char pattern[] = "ABABCABAA";
char text[] = "ABABABCABAABABCABAABB";
/*
int n = strlen(pattern);
int *prefix = (int *)calloc(n, sizeof(int));
prefix_table(pattern, prefix, n);
for (int i = 0; i < n; i++) {
printf("%d\n", prefix[i]);
}
*/
kmp_search(text, pattern);
return 0;
}
结果:
Found pattern at 2
Found pattern at 10
算法复杂度为O(m+n)