美文网首页
字符串匹配算法

字符串匹配算法

作者: TomyZhang | 来源:发表于2019-05-28 21:22 被阅读0次

暴力匹配算法

对主串及模式串进行遍历,如果匹配失败,则主串取下一位置,模式串取起始位置,继续进行遍历。

思路:
暴力匹配算法思路

代码实现:

#include<stdio.h>

int index(char S[], int sLen, char T[], int tLen) { //暴力匹配算法,返回匹配结果的起始位置索引
    int i = 0;
    int j = 0;
    while(i < sLen && j < tLen) {
        if(S[i] == T[j]) { //如果主串元素跟模式串元素相同
            i++;
            j++;
        } else { //如果主串元素跟模式串元素不同
            i = i - j + 1;
            j = 0;
        }          
    }
    if(j == tLen) {
        return i - tLen; 
    }
    return 0;
}

void main() {
    char S[] = "abaeabcdabaebabdeabadabaeabcdabab";
    int sLen = 33;
    char T[] = "abab";
    int tLen = 4;
    int result = index(S, sLen, T, tLen);
    printf("result: %d \n", result);
}

KMP算法

KMP算法的关键是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。匹配失败后的信息指的是模式串的子串前缀、后缀公共元素最大长度。

思路:
KMP算法思路

代码实现:

#include<stdio.h>
#define MAX 100

/*
1.关于前缀、后缀:
a   ==> 无前缀、无后缀
abc ==> 前缀:a ab、后缀:c bc

2.确定前缀、后缀公共元素最大长度:
0 1 2 3 4 5 6 7  ==> 索引
a b c d a b c a  ==> 模式串
    j       i    ==> 模式串起始位置到i组成一个子串,j用于记录该子串前缀、后缀公共元素最大长度(j+1)
0 0 0 0 1 2 3 ?  ==> 模式串起始位置到i组成的子串的前缀、后缀公共元素最大长度,该例子最终值为:0 0 0 0 1 2 3 1
*/

void get_next(char T[], int tLen, int next[]) { //确定模式串子串的前缀、后缀公共元素最大长度
    int j = 0;    
    int i = 1;
    next[0] = 0; //模式串起始位置无前缀、后缀,因此前缀、后缀公共元素最大长度为0
    while(i < tLen) { //遍历模式串
        if(T[i] == T[j]) { //子串结尾位置元素与j记录位置元素相同时
            next[i] = j + 1; //起始位置到i组成的子串的前缀、后缀公共元素最大长度为j+1
            ++i;
            ++j;
        } else { //子串结尾位置元素与j记录位置元素不相同时
            if(j != 0) { //如果j不为0,则取起始位置到j-1组成的子串的前缀、后缀公共元素最大长度,进行下一次比较
                j = next[j-1];
            } else { //如果j为0,则起始位置到i组成的子串的前缀、后缀公共元素最大长度为0
                next[i] = 0;
                ++i;
            }
        }
    }
}

int KMP(char S[], int sLen, char T[], int tLen) { //KMP算法,返回匹配结果的起始位置索引
    int next[MAX]; //存放前缀、后缀公共元素最大长度
    get_next(T, tLen, next);
    int i = 0;
    int j = 0;
    while(j<tLen && i<sLen) { //遍历主串与模式串
        if(T[j] == S[i]) { //主串与模式串元素相同
            ++i;
            ++j;
        } else { //主串与模式串元素不同
            if(j != 0) { //如果j不为0,则取起始位置到j-1组成的子串的前缀、后缀公共元素最大长度,进行下一次比较
                j = next[j-1];
            } else { //如果j为0,则对i递增,进行下一次比较
                ++i;
            }
        }
    }
    if(j == tLen) { //循环结束,如果j等于模式串长度,则匹配到结果
        return i - tLen;
    }
    return -1;
}

void main() {
    char S[] = "abaeabcdabaebabdeabadabaeabcdabab";
    int sLen = 33;
    char T[] = "abab";
    int tLen = 4;
    int next[MAX];
    get_next(T, tLen, next);
    for(int i=0; i<tLen; i++) {
        printf("%d ", next[i]);
    }
    printf("\n");
    int index = KMP(S, sLen, T, tLen);
    printf("result: %d \n", index);
}

BM算法

BM算法针对两个子串的比较是从后向前进行的,也就是按照下标从大到小进行比较。BM算法包含两部分,分别是坏字符规则和好后缀规则。

BM算法规则

思路:
BM算法思路

代码实现:

#include<stdio.h>
#define HASH_SIZE 256
#define MAX 100

// 生成坏字符对应的散列表
void create_bad_char(char T[], int tLen, int bad_char[]) {
    // 所有坏字符对应的值默认为 -1
    for(int i=0; i<HASH_SIZE; i++) {
        bad_char[i] = -1;
    }

    for(int i=0; i<tLen; i++) { //遍历模式串
        int ascii = T[i] - '\0'; // 坏字符对应的 ASCII 码,作为下标
        bad_char[ascii] = i; // 坏字符在模式串中出现的位置,作为值
    }
}

// 求公共后缀子串
// suffix[i]:下标表示后缀子串的长度,值表示与这个后缀子串匹配的另一个子串在模式串中的起始位置
// prefix[i]:下标表示后缀子串的长度,值表示模式串的后缀子串是否能匹配其前缀子串
void create_good_suffix(char T[], int tLen, int suffix[], bool prefix[]) {
    for(int i=0; i<tLen; i++) {
        suffix[i] = -1; 
        prefix[i] = false;
    }

    // 求[0, i]的子串和模式串的公共后缀子串
    for (int i=0; i<tLen-1; i++) { //从0遍历到tLen-2
        int j = i; //i表示分界,j表示从分界往前求
        int k = 0; //k表示公共后缀子串的长度,从0开始求起
        while(j>=0 && T[j] == T[tLen-1-k]) { // 下标都向前移动
            j--;
            k++; 
        }

        if (k != 0) {
            suffix[k] = j + 1; // 公共后缀长度为k,起始位置为j+1
        }
        if (j == -1) {
            prefix[k] = true; // 公共后缀子串同时也是模式串的前缀子串
        }
    }
}

// 根据好后缀规则求下一次移动的位数
int move_by_good_suffix(int j, int tLen, int suffix[], bool prefix[]) { //j为主串坏字符对齐模式串中的位置
    int k = tLen - 1 - j; // 好后缀长度
    printf("好后缀长度:%d\n", k);
    //注意:对于公共后缀长度等于好后缀长度的情况,需要用到公共后缀起始位置来计算下一次移动的位数
    if (suffix[k] != -1) { //长度为k的公共后缀存在,suffix[k]表示公共后缀起始位置
        printf("找到好后缀,返回移动位数:%d\n", j - suffix[k] + 1);
        return j - suffix[k] + 1;
    }

    //长度为k的公共后缀不存在
    for (int r=j+2; r<tLen; r++) {
        printf("好后缀子串长度:%d,是否模式串前缀:%d\n", tLen-r, prefix[tLen-r]);
        //注意:对于公共后缀长度等于好后缀子串长度的情况,不需要用到公共后缀起始位置来计算下一次移动的位数,而是需要知道是否有公共后缀处于模式串起始位置,因此即使存在多个公共后缀也不影响后续计算
        //例如主串:fmewsedkedsed、模式串:ededsed,有两个公共后缀ed,在好后缀规则计算时,后面的ed位置会覆盖前面的ed位置,这个没有关系,我们只要标记好有ed处于模式串起始位置即可
        if (prefix[tLen-r] == true) { //tLen-r:tLen-1-r+1,即从r到tLen-1的长度。这里判断长度为tLen-r的公共后缀是否存在并且该公共后缀是否为模式串的前缀子串
            printf("找到模式串前缀,返回移动位数:%d\n", r);
            return r; //移动位数为r
        }
    }
    printf("返回默认移动位数:%d\n", tLen);
    return tLen; //移动位数为tLen
}

int BM(char S[], int sLen, char T[], int tLen) {
    int bad_char[HASH_SIZE]; // 记录每个字符在模式串中最后出现的位置,作为坏字符散列表
    create_bad_char(T, tLen, bad_char); //坏字符

    int suffix[MAX];
    bool prefix[MAX];
    create_good_suffix(T, tLen, suffix, prefix); //好后缀
    for(int m=0; m<tLen; m++) {
        printf("suffix[%d]: %d, prefix[%d]: %d\n", m, suffix[m], m, prefix[m]);
    }

    int i = 0; // 表示主串和模式串对齐时第一个字符的位置
    int si = 0; // 坏字符对齐于模式串中的位置
    int xi = -1; // 坏字符在模式串中最后出现的位置

    while (i <= sLen-tLen) {
        int j = 0; //表示主串坏字符对齐模式串中的位置
        // 从后向前进行匹配
        for (j=tLen-1; j >= 0; j--) {
            // 找到了第一个不匹配的字符
            if (S[i+j] != T[j]) {
                break;
            }
        }

        if (j < 0) { // 匹配成功
            return i; 
        }

        si = j;
        xi = bad_char[S[i+j] - '\0'];
        int x = si - xi; // 坏字符规则应该向后移动的位数
        printf("坏字符: si:%d - xi:%d = x:%d\n", si, xi, x);
        int y = 0; // 好后缀规则应该向后移动的位数

        if (j < tLen-1) {
            y = move_by_good_suffix(j, tLen, suffix, prefix);
        }
        printf("好后缀: %d\n", y);
        x = x > y ? x : y; //两个规则取大值
        i = i + x; //主串和模式串对齐位置加x
        printf("i: %d\n", i);
    }

    return -1;
}

void main() {
    char S[] = "fmewsedkedsed";
    int sLen = 13;
    char T[] = "ededsed";
    int tLen = 7;
    //char S[] = "abdeabcdabaeabddeabadabaeabcdaababdbab";
    //int sLen = 38;
    //char T[] = "ababd";
    //int tLen = 5;
    int result = BM(S, sLen, T, tLen);
    printf("result: %d\n", result);
}

Sunday算法

Sunday算法是对BM算法的小幅优化,与BM算法不同的是,Sunday算法是从前往后匹配,在匹配失败时关注主串中参加匹配的最末位字符的下一位字符。如果该字符没有在模式串中出现则移动位数为:模式串长度+1;否则移动位数为:模式串长度-该字符最右出现的位置(以0开始),即模式串中该字符最右出现的位置到尾部的距离+1。

思路:
Sunday算法思路

代码实现:

#include<stdio.h>
#define HASH_SIZE 256

int Sunday(char S[], int sLen, char T[], int tLen) {
    int move[HASH_SIZE];
    //所有字符默认移动位数为tLen+1
    for(int i= 0; i<HASH_SIZE; i++) {
        move[i] = tLen+1;
    }

    for (int i = 0; i<tLen; i++) { //遍历模式串,计算当模式串与主串不匹配时,模式串移动位数
        move[T[i] - '\0'] = tLen - i; 
    }
        
    int i = 0; //表示主串和模式串对齐时第一个字符的位置
        
    int j; //表示模式串已经匹配的位置
        
    while(i <= sLen-tLen) {
        j = 0;
        while(S[i+j] == T[j]) {
            j++;
            if (j >= tLen){
                return i;
            }
        }
        char next = S[i+tLen];
        i += move[next];
    }

    return -1;
}

void main() {
    char S[] = "abdeabcdabaeabddeabadabaeabcdaababdbab";
    int sLen = 38;
    char T[] = "ababd";
    int tLen = 5;
    int result = Sunday(S, sLen, T, tLen);
    printf("result: %d\n", result);
}

相关文章

  • 字符串匹配

    indexOf 底层就是使用字符串匹配算法 字符串匹配算法很多 BF( Brute Force)算法 暴力匹配算...

  • KMP字符串匹配算法

    KMP字符串匹配算法 先总结一下之前的几种字符串匹配算法 1 BF算法, 最简单的字符串匹配算法, 可以直接使用s...

  • KMP算法文章合集

    字符串的查找:朴素查找算法和KMP算法 暴力匹配算法与KMP算法(串的匹配) 字符串查找算法BF和KMP 字符串匹...

  • 一些有关算法的

    字符串模式匹配算法 字符串的KMP算法详解部分匹配表(即)向右移一位就可以得到next数组。字符串模式匹配算法 R...

  • 字符串匹配算法

    场景:字符串A为主串,字符串B为模式串,比较字符串B是否能够在字符串A中进行匹配? 匹配算法:BF算法和RK算法。...

  • 字符串匹配算法

    以下为学习 《数据结构与算法之美 -- 字符串匹配》 的记录。 BF算法 即暴力匹配算法,循环遍历匹配。 RK算法...

  • 2022-01-25

    1.字符串匹配BM算法 在文本中查找字符串匹配算法,坏字符串规则和好后缀规则坏字符串规则: 从后往前匹配,第一个不...

  • 20-字符串匹配

    字符串匹配 这章节,我们会讲到几大典型的字符串匹配算法 BF算法 BF算法是最最符合正常人逻辑思维的一种匹配模式,...

  • leetcode字符串匹配算法之KMP算法

    本篇介绍一种高效的字符串匹配算法——KMP算法。 KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J....

  • 中文分词的方法

    1、基于字符串匹配的方法 1.1 正向最大匹配分词算法1.2 逆向最大匹配分词算法1.3 双向最大匹配分词算法1....

网友评论

      本文标题:字符串匹配算法

      本文链接:https://www.haomeiwen.com/subject/kqwjtctx.html