字符串模式匹配

字符串模式匹配有以下几种算法:

  • BF算法
  • KMP算法
  • Sunday算法
  • AC自动机(多模匹配)
BF算法:

我们常用的暴力算法,时间复杂度 O(n2) O ( n 2 )

代码演示:

int BF(const char *text, const char *pattern) {
    int len1 = strlen(text);
    int len2 = strlen(pattern);
    for (int i = 0; i < len1 - len2 + 1; ++i) {
        int flag = 1;
        for (int j = 0; pattern[j]; ++j) {
            if (text[i + j] == pattern[j]) continue;
            flag = 0;
        }
        if (flag) return 1;
    }
    return 0;
}
KMP算法:

基于BF算法的优化,他根据字符串出现前缀与后缀相同的情况进行优化
这里写图片描述
这里写图片描述
假设这里SA、SB、TA、TB都相同,我们在这个位置失配,如在BF算法,我们将TA与SA之后的第一个字符进行匹配,这样效率显然很低,其实我们发现既然TB等于TA,那我们下一轮可以使用TA的下一个字符串与SB之后的第一个字符匹配即可。

我们这里要根据模式串计算一个next数组存储模式串中每个字符对应的前缀中字符位置,如若没有则标记为-1。

KMP算法的主要优点在于母串指针不会回溯。
代码演示:

int KMP(const char *text, const char *pattern) {
    int len_1 = strlen(text);
    int len_2 = strlen(pattern);
    int *next = (int *)malloc(sizeof(int) * len_2);
    next[0] = -1;
    int matrix = -1;
    for (int i = 1; pattern[i]; ++i) {
        while (matrix != -1 && pattern[matrix + 1] != pattern[i]) {
            matrix = next[matrix];
        }
        if (pattern[matrix + 1] == pattern[i]) {
            matrix = matrix + 1;
        }
        next[i] = matrix;
    }
    matrix = -1;
    for (int i = 0; text[i]; ++i) {
        while (matrix != -1 && pattern[matrix + 1] != text[i]) {
            matrix = next[matrix];
        }
        if (pattern[matrix + 1] == text[i]) {matrix++;}
        if (pattern[matrix + 1] == 0) return 1;
    }
    return 0;
}
Sunday算法:

这里写图片描述
当我们在这个地方发生失配时,Sunday算法主要采用的是对齐法,然后模式串指针跳跃到这个字符最后出现的位置。
这里写图片描述

在这里我们可以使用一个数组预处理模式串中每个字符最后出现的位置,那么我们不妨直接计算出遇到每个字符母串应该跳跃的位置。 如果是模式串中没有出现过的字符,那么我们母串的指针应该向前跳跃多少步? 答案必然是 模式串长度 + 1
这里写图片描述

我们已经明确直到  t    t   不在模式串中,所以我们的母串指针一定要跳到  t    t   之后。

如果当前字符是模式串第一个字符,那么我们母串的指针应该向前跳跃多少步? 答案必然是 模式串长度。

根据数学归纳法,我们可以推论实质上跳跃步数为 leni l e n − i

代码演示:

int sunday(const char *text, const char *pattern) {
    int len = strlen(pattern), len2 = strlen(text);
    int ind[127] = {0};
    for (int i = 0; i < 127; i++) ind[i] = len + 1;
    for (int i = 0; pattern[i]; i++) ind[pattern[i]] = len - i;
    for (int i = 0; i <= len2 - len;) {
        int j = 0;
        for (; j < len; j++) {
            if (pattern[j] != text[i + j]) break;
        }
        if (j == len) return 1;
        i += ind[text[i + len]];
    }
    return 0;
}
AC自动机算法:

本质:字典树 + KMP

数据结构用于数据表示

字典树暴力匹配其实也较为优秀,大型企业主要采用的还是字典树而非AC自动机,由于模式串增加后,重构fail指针较为麻烦。
这里写图片描述

添加fail指针的字典树叫做AC自动机(NFA)。单边关系转化
AC自动机采用的是KMP的思想:母串指针不回溯。
这里写图片描述

在编程实现的过程中需要进行层次遍历。采用队列的数据结构,父节点为子节点分配fail指针。

代码演示:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define BASE 26
#define MAX_N 100000
#define BL 'a'
typedef struct Node {
    char *str;
    int flag;
    struct Node* next[BASE];
    struct Node* fail;
} Node;
Node* getNode() {
    return (Node*)calloc(1, sizeof(Node));
}

void clear(Node *node) {
    if (node == NULL) return;
    for (int i = 0; i < BASE; ++i) {
        if (node->next[i] == NULL) continue;
        clear(node->next[i]);
    }
    if (node->flag) free(node->str);
    free(node);
    return;
}

Node* insert(Node *root, const char* str) {
    if (root == NULL) root = getNode();
    Node* p = root;
    for (int i = 0; str[i]; ++i) {
        if (p->next[str[i] - BL] == NULL) p->next[str[i] - BL] = getNode();
        p = p->next[str[i] - BL];
    }
    p->flag = 1;
    p->str = strdup(str);
    return root;
}
void build_ac(Node* root) {
    if (root == NULL) return;
    Node **queue = (Node**)malloc(sizeof(Node*) * MAX_N);
    int head = 0, tail = 0;
    queue[tail++] = root;
    while (head < tail) {
        Node* node = queue[head++];
        for (int i = 0; i < BASE; ++i) {
            if (node->next[i] == NULL) continue;
            Node *p = node->fail;
            while (p && p->next[i] == NULL) {
                p = p->fail;
            }
            if (p == NULL) {
                node->next[i]->fail = root;
            }else {
                node->next[i]->fail = p->next[i];
            }
            queue[tail++] = node->next[i];
        }
    }
    free(queue);
    return;
}

void search_ac(Node *root, char *text) {
    Node* p = root;
    for (int i = 0; text[i]; ++i) {
        while (p && p->next[text[i] - BL] == NULL) p = p->fail;
        if (p == NULL) p = root;
        else p = p->next[text[i] - BL];
        Node *q = p;
        while (q) {
            if (q->flag) printf("%s\n", q->str);
            q = q->fail;
        }
    }
    return;
}
int main() {
    Node* root = NULL;
    root = insert(root, "say");
    root = insert(root, "she");
    root = insert(root, "shr");
    root = insert(root, "he");
    root = insert(root, "her");
    build_ac(root);
    search_ac(root, "sasherhs");
    clear(root);
    system("pause");
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值