问题描述
已知字符串 text 和 字符串 pattern,
问 pattern 是否为 text 的子串
暴力搜索匹配
int main() {
const char *pattern = "ACTG";
const char *text = "CGTACGTACCTAGCACTGACTAGCCA";
int cnt = search(pattern, text);
printf("%d\n", cnt);
return 0;
}
int search(const char *pattern, const char *text) {
int M = strlen(pattern);
int N = strlen(text);
for (int i = 0; i < N - M; i++) {
int j;
for (j = 0; j < M; j++) {
if (*(pattern+j) != *(text+j+i))
break;
}
// pat 全都匹配了
if (j == M) return i;
}
return -1;
}
时间复杂度:O(M*N)
其中M为子串长度,N为母串长度
KMP匹配
图解:
1、构建最长公共前后缀表
(1)给定两个字符串T, P
(2)建立P的最长公共前后缀表
(举例)abab的最长公共前后缀是:ab
(3)获得结果
(4)为方便日后的边界判定,去掉最后一位(用不到),添加头部指示符
2、KMP匹配操作
(1)当 P[3] != T[3]时,前缀表prefix[3] = 1,即可以将P[1]移至T[3]处继续向后匹配(此时的P[1]之前部分 与 对应的T[3]之前的部分相同,故不用再做比较)
(2)当 P[1] != T[3+1]时,前缀表prefix[1] = 0,即可以将P[0]移至T[3+1]处继续向后匹配。
(3)当 P[0] != T[4+0]时,前缀表prefix[0] = -1,即此时T[4]不匹配 P 中任意字符,故 P 向后移一位对齐下一位 T。
内容来自视频:正月点灯笼.
总结:
本质:
对串的前缀进行去处理
得到所有可能出现匹配的位置
从而减少不必要的位移
1、关于 pattern 建立最长公共前后缀表prefix:
如何最快查找其公共前后缀 {参考动态规划}
2、通过 prefix 辅助 pattern 对 text 的匹配
如何借助prefix进行移位匹配操作
{匹配成功/失败(如何更新索引状态)}
代码:
#include <stdio.h>
#include <stdlib.h>
#define LENLIMIT 128
// 输入信息
int get_text(char demo[]);
// 建立pattern字符串的公共前后缀长度表prefix
void prefix_table(char pattern[], int prefix[], int n);
// 为方便匹配,故向右移动prefix
void move_prefix(int prefix[], int n);
void show_prefix(int prefix[], int n);
void kmp_search(char text[], char pattern[], \
int prefix[], int text_len, int pattern_len);
int main(int argc, char const *argv[]) {
char *text = (char *)malloc(sizeof(char)*LENLIMIT);
char *pattern = (char *)malloc(sizeof(char)*LENLIMIT);
int *prefix = (int *)malloc(sizeof(int)*LENLIMIT);
printf("typein text\t: ");
int text_len = get_text(text);
printf("typein pattern\t: ");
int pattern_len = get_text(pattern);
printf("text\t: %s\n", text);
printf("pattern\t: %s\n", pattern);
prefix_table(pattern, prefix, pattern_len);
show_prefix(prefix, pattern_len);
kmp_search(text, pattern, prefix, \
text_len, pattern_len);
free(pattern);
free(prefix);
return 0;
}
// 动态数组的赋值方法:
// 数组指针【char *demo】<=>【char demo[]】<=>【demo[i]】
int get_text(char demo[]) {
char ch;
int i = 0;
while((ch=getchar())!='\n') {
demo[i++] = ch;
demo[i] = '\0';
}
return i;
}
void prefix_table(char pattern[], int prefix[], int n) {
/* 操作对象:pattern的子数组
* i = 1~n-1
* len为当前最长公共前后缀记录
* 1、前一刻len的后一位(新晋前缀) 等于 当前i位置(新晋后缀):
* len+1并赋给pattern[i], i++
* 2、否则
* 2.1、len = 0: pattern[i] = 0, i++
* 2.2、len > 0: 更新len为len-1时的prefix, 继续寻找len
*/
prefix[0] = 0;
int i = 1;
int len = 0;
printf("%d\n", n);
while (i < n) {
// 通常情况,动态规划
if (pattern[i] == pattern[len]) {
prefix[i] = ++len;
i++;
}
else {
// 非边界异常情况,左斜处取长度
if (len > 0) {
len = prefix[len - 1];
}
// 边界异常情况,等0后移
else {
prefix[i] = len;
i++;
}
}
}
// 为方便后续匹配
move_prefix(prefix, n);
}
void move_prefix(int prefix[], int n) {
int i;
for (i=n-1; i>0; i--) {
prefix[i] = prefix[i-1];
}
prefix[0] = -1;
}
void show_prefix(int prefix[], int n) {
int i;
for (i=0; i<n; i++) {
printf("%d\t", prefix[i]);
}
printf("\n");
}
void kmp_search(char text[], char pattern[], \
int prefix[], int text_len, int pattern_len) {
/* 各数组的索引声明
* text[i]
* pattern[j]
*/
int i = 0;
int j = 0;
while (i<text_len) {
if (text[i]==pattern[j]) {
// pattern 完全匹配
if (j==pattern_len-1) {
printf("At: %d, get!\n", i-j);
j = prefix[j]+1; //不停下来,继续往下比
}
// 当前位匹配且继续往下比
else {
j++;
}
i++;
}
else {
// pattern 首位不匹配时,text后移(i+1)
if (prefix[j]==-1) {
i++;
}
// pattern 迁移到公共前缀记录处
else {
j = prefix[j];
}
}
}
printf("Over...\n");
}
时间复杂度:O(N+M)
其中M为子串长度,N为母串长度