算法 字符串高效匹配KMP算法

66 阅读1分钟

简介

在主字符串中text查找子字符串 pattern 是否存在,按照暴力匹配其时间复杂度为O(m*n)

KMP算法改进了查找效率,核心是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的,按照KMP算法匹配其时间复杂度为O(m+n)

暴力匹配

  • 第一层循环为主字符串遍历匹配。
  • 第二层循环为子字符串遍历匹配。
  • 若子串中字符匹配失败,则进入下一轮主字符串匹配。
  • 若子串中每个字符匹配成功,则返回子串在主串中的索引位置。
  • 若主串遍历完后仍然未匹配,则返回-1表示匹配失败。
  • 时间复杂度为O(m*n),m和n分别为主串和子串长度。

源码示例

#include <stdio.h>
#include <string.h>

// 原始主字符串
#define STR_TEXT "abaacababcac"
// 匹配子字符串
#define STR_PATTERN "ababc"

// 暴力匹配
int ori_match(char *text, char *pattern)
{
    int i, j;
    int tlen = strlen(text);
    int plen = strlen(pattern);
    
    // 循环主字符串匹配
    for (i = 0; i < tlen - plen; i++) {
        // 循环子字符串匹配
        for (j = 0; j < plen; j++) {
            // 字符不匹配
            if (text[i+j] != pattern[j]) {
                break;
            }
        }
        // 子字符串匹配成功
        if (j == plen) {
            return i;
        }
    }

    return -1;
}


int main()
{
    printf("original match ret:%d\n", ori_match(STR_TEXT, STR_PATTERN));
    return 0;
}

运行结果

[xiaofeng@localhost kmp]$ gcc main.c 
[xiaofeng@localhost kmp]$ ./a.out 
original match ret:5
[xiaofeng@localhost kmp]$ 

KMP匹配

  • 需要提前根据子串初始化构造好next数组。
  • next[i]的值等于P[0]...P[i - 1]最长公共前后缀。
  • next[i]的值表示当第i个字符不匹配时,pi回溯的位置。

源码示例

#include <stdio.h>
#include <string.h>

// 原始主字符串
#define STR_TEXT "abaacababcac"
// 匹配子字符串
#define STR_PATTERN "ababc"
// 子字符串长度
#define PATTERN_LEN (sizeof(STR_PATTERN))

// next数组
int next_arr[PATTERN_LEN];

// 初始化next数组
void next_init(char *pattern, int next[]){
    next[0] = -1;
    int i = 0, j = -1;
    while (i < strlen(pattern)) {
        if (j == -1 || pattern[i] == pattern[j]) {
            next[++i] = ++j;
            printf("next[%d]=%d\n", i, next[i]);
        } else {
            j = next[j];
        }
    }
}

// KMP匹配
int kmp_match(char *text, char *pattern)
{
    int i = 0, j = 0;
    int tlen = strlen(text);
    int plen = strlen(pattern);

    while (i < tlen && j < plen) {
        printf("text[%d]:%c, pattern[%d]:%c\n", i, text[i], j, pattern[j]);
        if (j == 0 || text[i] == pattern[j]) {
            i++;
            j++;
        } else {
            j = next_arr[j];
        }
    }

    // 子字符串匹配成功
    if (j == plen) {
        return i - j;
    }
    
    return -1;
}


int main()
{
    next_init(STR_PATTERN, next_arr);
    printf("kmp match ret:%d\n", kmp_match(STR_TEXT, STR_PATTERN));
    return 0;
}

运行结果

[xiaofeng@localhost kmp]$ gcc main.c 
[xiaofeng@localhost kmp]$ ./a.out 
next[1]=0
next[2]=0
next[3]=1
next[4]=2
next[5]=0
text[0]:a, pattern[0]:a
text[1]:b, pattern[1]:b
text[2]:a, pattern[2]:a
text[3]:a, pattern[3]:b
text[3]:a, pattern[1]:b
text[3]:a, pattern[0]:a
text[4]:c, pattern[1]:b
text[4]:c, pattern[0]:a
text[5]:a, pattern[1]:b
text[5]:a, pattern[0]:a
text[6]:b, pattern[1]:b
text[7]:a, pattern[2]:a
text[8]:b, pattern[3]:b
text[9]:c, pattern[4]:c
kmp match ret:5
[xiaofeng@localhost kmp]$